Theory Matrix_Kleene_Algebras

(* Title:      Matrix Kleene Algebras
   Author:     Walter Guttmann
   Maintainer: Walter Guttmann <walter.guttmann at canterbury.ac.nz>
*)

section ‹Matrix Kleene Algebras›

text ‹
This theory gives a matrix model of Stone-Kleene relation algebras.
The main result is that matrices over Kleene algebras form Kleene algebras.
The automata-based construction is due to Conway cite"Conway1971".
An implementation of the construction in Isabelle/HOL that extends cite"ArmstrongGomesStruthWeber2016" was given in cite"Asplund2014" without a correctness proof.

For specifying the size of matrices, Isabelle/HOL's type system requires the use of types, not sets.
This creates two issues when trying to implement Conway's recursive construction directly.
First, the matrix size changes for recursive calls, which requires dependent types.
Second, some submatrices used in the construction are not square, which requires typed Kleene algebras cite"Kozen1998", that is, categories of Kleene algebras.

Because these instruments are not available in Isabelle/HOL, we use square matrices with a constant size given by the argument of the Kleene star operation.
Smaller, possibly rectangular submatrices are identified by two lists of indices: one for the rows to include and one for the columns to include.
Lists are used to make recursive calls deterministic; otherwise sets would be sufficient.
›

theory Matrix_Kleene_Algebras

imports Stone_Relation_Algebras.Matrix_Relation_Algebras Kleene_Relation_Algebras

begin

subsection ‹Matrix Restrictions›

text ‹
In this section we develop a calculus of matrix restrictions.
The restriction of a matrix to specific row and column indices is implemented by the following function, which keeps the size of the matrix and sets all unused entries to bot›.
›

definition restrict_matrix :: "'a list  ('a,'b::bot) square  'a list  ('a,'b) square" ("_ _ _" [90,41,90] 91)
  where "restrict_matrix as f bs = (λ(i,j) . if List.member as i  List.member bs j then f (i,j) else bot)"

text ‹
The following function captures Conway's automata-based construction of the Kleene star of a matrix.
An index k› is chosen and s› contains all other indices.
The matrix is split into four submatrices a›, b›, c›, d› including/not including row/column k›.
Four matrices are computed containing the entries given by Conway's construction.
These four matrices are added to obtain the result.
All matrices involved in the function have the same size, but matrix restriction is used to set irrelevant entries to bot›.
›

primrec star_matrix' :: "'a list  ('a,'b::{star,times,bounded_semilattice_sup_bot}) square  ('a,'b) square" where
"star_matrix' Nil g = mbot" |
"star_matrix' (k#s) g = (
  let r = [k] in
  let a = rgr in
  let b = rgs in
  let c = sgr in
  let d = sgs in
  let as = rstar o ar in
  let ds = star_matrix' s d in
  let e = a  b  ds  c in
  let es = rstar o er in
  let f = d  c  as  b in
  let fs = star_matrix' s f in
  es  as  b  fs  ds  c  es  fs
)"

text ‹
The Kleene star of the whole matrix is obtained by taking as indices all elements of the underlying type 'a›.
This is conveniently supplied by the enum› class.
›

fun star_matrix :: "('a::enum,'b::{star,times,bounded_semilattice_sup_bot}) square  ('a,'b) square" ("_" [100] 100) where "star_matrix f = star_matrix' (enum_class.enum::'a list) f"

text ‹
The following lemmas deconstruct matrices with non-empty restrictions.
›

lemma restrict_empty_left:
  "[]fls = mbot"
  by (unfold restrict_matrix_def List.member_def bot_matrix_def) auto

lemma restrict_empty_right:
  "ksf[] = mbot"
  by (unfold restrict_matrix_def List.member_def bot_matrix_def) auto

lemma restrict_nonempty_left:
  fixes f :: "('a,'b::bounded_semilattice_sup_bot) square"
  shows "(k#ks)fls = [k]fls  ksfls"
  by (unfold restrict_matrix_def List.member_def sup_matrix_def) auto

lemma restrict_nonempty_right:
  fixes f :: "('a,'b::bounded_semilattice_sup_bot) square"
  shows "ksf(l#ls) = ksf[l]  ksfls"
  by (unfold restrict_matrix_def List.member_def sup_matrix_def) auto

lemma restrict_nonempty:
  fixes f :: "('a,'b::bounded_semilattice_sup_bot) square"
  shows "(k#ks)f(l#ls) = [k]f[l]  [k]fls  ksf[l]  ksfls"
  by (unfold restrict_matrix_def List.member_def sup_matrix_def) auto

text ‹
The following predicate captures that two index sets are disjoint.
This has consequences for composition and the unit matrix.
›

abbreviation "disjoint ks ls  ¬(x . List.member ks x  List.member ls x)"

lemma times_disjoint:
  fixes f g :: "('a,'b::idempotent_semiring) square"
  assumes "disjoint ls ms"
    shows "ksfls  msgns = mbot"
proof (rule ext, rule prod_cases)
  fix i j
  have "(ksfls  msgns) (i,j) = (⨆⇩k (ksfls) (i,k) * (msgns) (k,j))"
    by (simp add: times_matrix_def)
  also have "... = (⨆⇩k (if List.member ks i  List.member ls k then f (i,k) else bot) * (if List.member ms k  List.member ns j then g (k,j) else bot))"
    by (simp add: restrict_matrix_def)
  also have "... = (⨆⇩k if List.member ms k  List.member ns j then bot * g (k,j) else (if List.member ks i  List.member ls k then f (i,k) else bot) * bot)"
    using assms by (auto intro: sup_monoid.sum.cong)
  also have "... = (⨆⇩(k::'a) bot)"
    by (simp add: sup_monoid.sum.neutral)
  also have "... = bot"
    by (simp add: eq_iff le_funI)
  also have "... = mbot (i,j)"
    by (simp add: bot_matrix_def)
  finally show "(ksfls  msgns) (i,j) = mbot (i,j)"
    .
qed

lemma one_disjoint:
  assumes "disjoint ks ls"
    shows "ks(mone::('a,'b::idempotent_semiring) square)ls = mbot"
proof (rule ext, rule prod_cases)
  let ?o = "mone::('a,'b) square"
  fix i j
  have "(ks?ols) (i,j) = (if List.member ks i  List.member ls j then if i = j then 1 else bot else bot)"
    by (simp add: restrict_matrix_def one_matrix_def)
  also have "... = bot"
    using assms by auto
  also have "... = mbot (i,j)"
    by (simp add: bot_matrix_def)
  finally show "(ks?ols) (i,j) = mbot (i,j)"
    .
qed

text ‹
The following predicate captures that an index set is a subset of another index set.
This has consequences for repeated restrictions.
›

abbreviation "is_sublist ks ls  x . List.member ks x  List.member ls x"

lemma restrict_sublist:
  assumes "is_sublist ls ks"
      and "is_sublist ms ns"
    shows "lsksfnsms = lsfms"
proof (rule ext, rule prod_cases)
  fix i j
  show "(lsksfnsms) (i,j) = (lsfms) (i,j)"
  proof (cases "List.member ls i  List.member ms j")
    case True thus ?thesis
      by (simp add: assms restrict_matrix_def)
  next
    case False thus ?thesis
      by (unfold restrict_matrix_def) auto
  qed
qed

lemma restrict_superlist:
  assumes "is_sublist ls ks"
      and "is_sublist ms ns"
    shows "kslsfmsns = lsfms"
proof (rule ext, rule prod_cases)
  fix i j
  show "(kslsfmsns) (i,j) = (lsfms) (i,j)"
  proof (cases "List.member ls i  List.member ms j")
    case True thus ?thesis
      by (simp add: assms restrict_matrix_def)
  next
    case False thus ?thesis
      by (unfold restrict_matrix_def) auto
  qed
qed

text ‹
The following lemmas give the sizes of the results of some matrix operations.
›

lemma restrict_sup:
  fixes f g :: "('a,'b::bounded_semilattice_sup_bot) square"
  shows "ksf  gls = ksfls  ksgls"
  by (unfold restrict_matrix_def sup_matrix_def) auto

lemma restrict_times:
  fixes f g :: "('a,'b::idempotent_semiring) square"
  shows "ksksfls  lsgmsms = ksfls  lsgms"
proof (rule ext, rule prod_cases)
  fix i j
  have "(ks(ksfls  lsgms)ms) (i,j) = (if List.member ks i  List.member ms j then (⨆⇩k (ksfls) (i,k) * (lsgms) (k,j)) else bot)"
    by (simp add: times_matrix_def restrict_matrix_def)
  also have "... = (if List.member ks i  List.member ms j then (⨆⇩k (if List.member ks i  List.member ls k then f (i,k) else bot) * (if List.member ls k  List.member ms j then g (k,j) else bot)) else bot)"
    by (simp add: restrict_matrix_def)
  also have "... = (if List.member ks i  List.member ms j then (⨆⇩k if List.member ls k then f (i,k) * g (k,j) else bot) else bot)"
    by (auto intro: sup_monoid.sum.cong)
  also have "... = (⨆⇩k if List.member ks i  List.member ms j then (if List.member ls k then f (i,k) * g (k,j) else bot) else bot)"
    by auto
  also have "... = (⨆⇩k (if List.member ks i  List.member ls k then f (i,k) else bot) * (if List.member ls k  List.member ms j then g (k,j) else bot))"
    by (auto intro: sup_monoid.sum.cong)
  also have "... = (⨆⇩k (ksfls) (i,k) * (lsgms) (k,j))"
    by (simp add: restrict_matrix_def)
  also have "... = (ksfls  lsgms) (i,j)"
    by (simp add: times_matrix_def)
  finally show "(ks(ksfls  lsgms)ms) (i,j) = (ksfls  lsgms) (i,j)"
    .
qed

lemma restrict_star:
  fixes g :: "('a,'b::kleene_algebra) square"
  shows "tstar_matrix' t gt = star_matrix' t g"
proof (induct arbitrary: g rule: list.induct)
  case Nil show ?case
    by (simp add: restrict_empty_left)
next
  case (Cons k s)
  let ?t = "k#s"
  assume "g::('a,'b) square . sstar_matrix' s gs = star_matrix' s g"
  hence 1: "g::('a,'b) square . ?tstar_matrix' s g?t = star_matrix' s g"
    by (metis member_rec(1) restrict_superlist)
  show "?tstar_matrix' ?t g?t = star_matrix' ?t g"
  proof -
    let ?r = "[k]"
    let ?a = "?rg?r"
    let ?b = "?rgs"
    let ?c = "sg?r"
    let ?d = "sgs"
    let ?as = "?rstar o ?a?r"
    let ?ds = "star_matrix' s ?d"
    let ?e = "?a  ?b  ?ds  ?c"
    let ?es = "?rstar o ?e?r"
    let ?f = "?d  ?c  ?as  ?b"
    let ?fs = "star_matrix' s ?f"
    have 2: "?t?as?t = ?as  ?t?b?t = ?b  ?t?c?t = ?c  ?t?es?t = ?es"
      by (simp add: restrict_superlist member_def)
    have 3: "?t?ds?t = ?ds  ?t?fs?t = ?fs"
      using 1 by simp
    have 4: "?t?t?as?t  ?t?b?t  ?t?fs?t?t = ?t?as?t  ?t?b?t  ?t?fs?t"
      by (metis (no_types) restrict_times)
    have 5: "?t?t?ds?t  ?t?c?t  ?t?es?t?t = ?t?ds?t  ?t?c?t  ?t?es?t"
      by (metis (no_types) restrict_times)
    have "?tstar_matrix' ?t g?t = ?t?es  ?as  ?b  ?fs  ?ds  ?c  ?es  ?fs?t"
      by (metis star_matrix'.simps(2))
    also have "... = ?t?es?t  ?t?as  ?b  ?fs?t  ?t?ds  ?c  ?es?t  ?t?fs?t"
      by (simp add: restrict_sup)
    also have "... = ?es  ?as  ?b  ?fs  ?ds  ?c  ?es  ?fs"
      using 2 3 4 5 by simp
    also have "... = star_matrix' ?t g"
      by (metis star_matrix'.simps(2))
    finally show ?thesis
      .
  qed
qed

lemma restrict_one:
  assumes "¬ List.member ks k"
    shows "(k#ks)(mone::('a,'b::idempotent_semiring) square)(k#ks) = [k]mone[k]  ksmoneks"
  by (subst restrict_nonempty) (simp add: assms member_rec one_disjoint)

lemma restrict_one_left_unit:
  "ks(mone::('a::finite,'b::idempotent_semiring) square)ks  ksfls = ksfls"
proof (rule ext, rule prod_cases)
  let ?o = "mone::('a,'b::idempotent_semiring) square"
  fix i j
  have "(ks?oks  ksfls) (i,j) = (⨆⇩k (ks?oks) (i,k) * (ksfls) (k,j))"
    by (simp add: times_matrix_def)
  also have "... = (⨆⇩k (if List.member ks i  List.member ks k then ?o (i,k) else bot) * (if List.member ks k  List.member ls j then f (k,j) else bot))"
    by (simp add: restrict_matrix_def)
  also have "... = (⨆⇩k (if List.member ks i  List.member ks k then (if i = k then 1 else bot) else bot) * (if List.member ks k  List.member ls j then f (k,j) else bot))"
    by (unfold one_matrix_def) auto
  also have "... = (⨆⇩k (if i = k then (if List.member ks i then 1 else bot) else bot) * (if List.member ks k  List.member ls j then f (k,j) else bot))"
    by (auto intro: sup_monoid.sum.cong)
  also have "... = (⨆⇩k if i = k then (if List.member ks i then 1 else bot) * (if List.member ks i  List.member ls j then f (i,j) else bot) else bot)"
    by (rule sup_monoid.sum.cong) simp_all
  also have "... = (if List.member ks i then 1 else bot) * (if List.member ks i  List.member ls j then f (i,j) else bot)"
    by simp
  also have "... = (if List.member ks i  List.member ls j then f (i,j) else bot)"
    by simp
  also have "... = (ksfls) (i,j)"
    by (simp add: restrict_matrix_def)
  finally show "(ks?oks  ksfls) (i,j) = (ksfls) (i,j)"
    .
qed

text ‹
The following lemmas consider restrictions to singleton index sets.
›

lemma restrict_singleton:
  "([k]f[l]) (i,j) = (if i = k  j = l then f (i,j) else bot)"
  by (simp add: restrict_matrix_def List.member_def)

lemma restrict_singleton_list:
  "([k]fls) (i,j) = (if i = k  List.member ls j then f (i,j) else bot)"
  by (simp add: restrict_matrix_def List.member_def)

lemma restrict_list_singleton:
  "(ksf[l]) (i,j) = (if List.member ks i  j = l then f (i,j) else bot)"
  by (simp add: restrict_matrix_def List.member_def)

lemma restrict_singleton_product:
  fixes f g :: "('a::finite,'b::kleene_algebra) square"
  shows "([k]f[l]  [m]g[n]) (i,j) = (if i = k  l = m  j = n then f (i,l) * g (m,j) else bot)"
proof -
  have "([k]f[l]  [m]g[n]) (i,j) = (⨆⇩h ([k]f[l]) (i,h) * ([m]g[n]) (h,j))"
    by (simp add: times_matrix_def)
  also have "... = (⨆⇩h (if i = k  h = l then f (i,h) else bot) * (if h = m  j = n then g (h,j) else bot))"
    by (simp add: restrict_singleton)
  also have "... = (⨆⇩h if h = l then (if i = k then f (i,h) else bot) * (if h = m  j = n then g (h,j) else bot) else bot)"
    by (rule sup_monoid.sum.cong) auto
  also have "... = (if i = k then f (i,l) else bot) * (if l = m  j = n then g (l,j) else bot)"
    by simp
  also have "... = (if i = k  l = m  j = n then f (i,l) * g (m,j) else bot)"
    by simp
  finally show ?thesis
    .
qed

text ‹
The Kleene star unfold law holds for matrices with a single entry on the diagonal.
›

lemma restrict_star_unfold:
  "[l](mone::('a::finite,'b::kleene_algebra) square)[l]  [l]f[l]  [l]star o f[l] = [l]star o f[l]"
proof (rule ext, rule prod_cases)
  let ?o = "mone::('a,'b::kleene_algebra) square"
  fix i j
  have "([l]?o[l]  [l]f[l]  [l]star o f[l]) (i,j) = ([l]?o[l]) (i,j)  ([l]f[l]  [l]star o f[l]) (i,j)"
    by (simp add: sup_matrix_def)
  also have "... = ([l]?o[l]) (i,j)  (⨆⇩k ([l]f[l]) (i,k) * ([l]star o f[l]) (k,j))"
    by (simp add: times_matrix_def)
  also have "... = ([l]?o[l]) (i,j)  (⨆⇩k (if i = l  k = l then f (i,k) else bot) * (if k = l  j = l then (f (k,j)) else bot))"
    by (simp add: restrict_singleton o_def)
  also have "... = ([l]?o[l]) (i,j)  (⨆⇩k if k = l then (if i = l then f (i,k) else bot) * (if j = l then (f (k,j)) else bot) else bot)"
    apply (rule arg_cong2[where f=sup])
    apply simp
    by (rule sup_monoid.sum.cong) auto
  also have "... = ([l]?o[l]) (i,j)  (if i = l then f (i,l) else bot) * (if j = l then (f (l,j)) else bot)"
    by simp
  also have "... = (if i = l  j = l then 1  f (l,l) * (f (l,l)) else bot)"
    by (simp add: restrict_singleton one_matrix_def)
  also have "... = (if i = l  j = l then (f (l,l)) else bot)"
    by (simp add: star_left_unfold_equal)
  also have "... = ([l]star o f[l]) (i,j)"
    by (simp add: restrict_singleton o_def)
  finally show "([l]?o[l]  [l]f[l]  [l]star o f[l]) (i,j) = ([l]star o f[l]) (i,j)"
    .
qed

lemma restrict_all:
  "enum_class.enumfenum_class.enum = f"
  by (simp add: restrict_matrix_def List.member_def enum_UNIV)

text ‹
The following shows the various components of a matrix product.
It is essentially a recursive implementation of the product.
›

lemma restrict_nonempty_product:
  fixes f g :: "('a::finite,'b::idempotent_semiring) square"
  assumes "¬ List.member ls l"
    shows "(k#ks)f(l#ls)  (l#ls)g(m#ms) = ([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)"
proof -
  have "(k#ks)f(l#ls)  (l#ls)g(m#ms) = ([k]f[l]  [k]fls  ksf[l]  ksfls)  ([l]g[m]  [l]gms  lsg[m]  lsgms)"
    by (metis restrict_nonempty)
  also have "... = [k]f[l]  ([l]g[m]  [l]gms  lsg[m]  lsgms)  [k]fls  ([l]g[m]  [l]gms  lsg[m]  lsgms)  ksf[l]  ([l]g[m]  [l]gms  lsg[m]  lsgms)  ksfls  ([l]g[m]  [l]gms  lsg[m]  lsgms)"
    by (simp add: matrix_idempotent_semiring.mult_right_dist_sup)
  also have "... = ([k]f[l]  [l]g[m]  [k]f[l]  [l]gms  [k]f[l]  lsg[m]  [k]f[l]  lsgms)  ([k]fls  [l]g[m]  [k]fls  [l]gms  [k]fls  lsg[m]  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksf[l]  [l]gms  ksf[l]  lsg[m]  ksf[l]  lsgms)  (ksfls  [l]g[m]  ksfls  [l]gms  ksfls  lsg[m]  ksfls  lsgms)"
    by (simp add: matrix_idempotent_semiring.mult_left_dist_sup)
  also have "... = ([k]f[l]  [l]g[m]  [k]f[l]  [l]gms)  ([k]fls  lsg[m]  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksf[l]  [l]gms)  (ksfls  lsg[m]  ksfls  lsgms)"
    using assms by (simp add: List.member_def times_disjoint)
  also have "... = ([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)"
    by (simp add: matrix_bounded_semilattice_sup_bot.sup_monoid.add_assoc matrix_semilattice_sup.sup_left_commute)
  finally show ?thesis
    .
qed

text ‹
Equality of matrices is componentwise.
›

lemma restrict_nonempty_eq:
  "(k#ks)f(l#ls) = (k#ks)g(l#ls)  [k]f[l] = [k]g[l]  [k]fls = [k]gls  ksf[l] = ksg[l]  ksfls = ksgls"
proof
  assume 1: "(k#ks)f(l#ls) = (k#ks)g(l#ls)"
  have 2: "is_sublist [k] (k#ks)  is_sublist ks (k#ks)  is_sublist [l] (l#ls)  is_sublist ls (l#ls)"
    by (simp add: member_rec)
  hence "[k]f[l] = [k](k#ks)f(l#ls)[l]  [k]fls = [k](k#ks)f(l#ls)ls  ksf[l] = ks(k#ks)f(l#ls)[l]  ksfls = ks(k#ks)f(l#ls)ls"
    by (simp add: restrict_sublist)
  thus "[k]f[l] = [k]g[l]  [k]fls = [k]gls  ksf[l] = ksg[l]  ksfls = ksgls"
    using 1 2 by (simp add: restrict_sublist)
next
  assume 3: "[k]f[l] = [k]g[l]  [k]fls = [k]gls  ksf[l] = ksg[l]  ksfls = ksgls"
  show "(k#ks)f(l#ls) = (k#ks)g(l#ls)"
  proof (rule ext, rule prod_cases)
    fix i j
    have 4: "f (k,l) = g (k,l)"
      using 3 by (metis restrict_singleton)
    have 5: "List.member ls j  f (k,j) = g (k,j)"
      using 3 by (metis restrict_singleton_list)
    have 6: "List.member ks i  f (i,l) = g (i,l)"
      using 3 by (metis restrict_list_singleton)
    have "(ksfls) (i,j) = (ksgls) (i,j)"
      using 3 by simp
    hence 7: "List.member ks i  List.member ls j  f (i,j) = g (i,j)"
      by (simp add: restrict_matrix_def)
    have "((k#ks)f(l#ls)) (i,j) = (if (i = k  List.member ks i)  (j = l  List.member ls j) then f (i,j) else bot)"
      by (simp add: restrict_matrix_def List.member_def)
    also have "... = (if i = k  j = l then f (i,j) else if i = k  List.member ls j then f (i,j) else if List.member ks i  j = l then f (i,j) else if List.member ks i  List.member ls j then f (i,j) else bot)"
      by auto
    also have "... = (if i = k  j = l then g (i,j) else if i = k  List.member ls j then g (i,j) else if List.member ks i  j = l then g (i,j) else if List.member ks i  List.member ls j then g (i,j) else bot)"
      using 4 5 6 7 by simp
    also have "... = (if (i = k  List.member ks i)  (j = l  List.member ls j) then g (i,j) else bot)"
      by auto
    also have "... = ((k#ks)g(l#ls)) (i,j)"
      by (simp add: restrict_matrix_def List.member_def)
    finally show "((k#ks)f(l#ls)) (i,j) = ((k#ks)g(l#ls)) (i,j)"
      .
  qed
qed

text ‹
Inequality of matrices is componentwise.
›

lemma restrict_nonempty_less_eq:
  fixes f g :: "('a,'b::idempotent_semiring) square"
  shows "(k#ks)f(l#ls)  (k#ks)g(l#ls)  [k]f[l]  [k]g[l]  [k]fls  [k]gls  ksf[l]  ksg[l]  ksfls  ksgls"
  by (unfold matrix_semilattice_sup.sup.order_iff) (metis (no_types, lifting) restrict_nonempty_eq restrict_sup)

text ‹
The following lemmas treat repeated restrictions to disjoint index sets.
›

lemma restrict_disjoint_left:
  assumes "disjoint ks ms"
    shows "msksflsns = mbot"
proof (rule ext, rule prod_cases)
  fix i j
  have "(msksflsns) (i,j) = (if List.member ms i  List.member ns j then if List.member ks i  List.member ls j then f (i,j) else bot else bot)"
    by (simp add: restrict_matrix_def)
  thus "(msksflsns) (i,j) = mbot (i,j)"
    using assms by (simp add: bot_matrix_def)
qed

lemma restrict_disjoint_right:
  assumes "disjoint ls ns"
    shows "msksflsns = mbot"
proof (rule ext, rule prod_cases)
  fix i j
  have "(msksflsns) (i,j) = (if List.member ms i  List.member ns j then if List.member ks i  List.member ls j then f (i,j) else bot else bot)"
    by (simp add: restrict_matrix_def)
  thus "(msksflsns) (i,j) = mbot (i,j)"
    using assms by (simp add: bot_matrix_def)
qed

text ‹
The following lemma expresses the equality of a matrix and a product of two matrices componentwise.
›

lemma restrict_nonempty_product_eq:
  fixes f g h :: "('a::finite,'b::idempotent_semiring) square"
  assumes "¬ List.member ks k"
      and "¬ List.member ls l"
      and "¬ List.member ms m"
    shows "(k#ks)f(l#ls)  (l#ls)g(m#ms) = (k#ks)h(m#ms)  [k]f[l]  [l]g[m]  [k]fls  lsg[m] = [k]h[m]  [k]f[l]  [l]gms  [k]fls  lsgms = [k]hms  ksf[l]  [l]g[m]  ksfls  lsg[m] = ksh[m]  ksf[l]  [l]gms  ksfls  lsgms = kshms"
proof -
  have 1: "disjoint [k] ks  disjoint [m] ms"
    by (simp add: assms(1,3) member_rec)
  have 2: "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = [k]f[l]  [l]g[m]  [k]fls  lsg[m]"
  proof -
    have "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = [k]([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)[m]"
      by (simp add: assms(2) restrict_nonempty_product)
    also have "... = [k][k]f[l]  [l]g[m][m]  [k][k]fls  lsg[m][m]  [k][k]f[l]  [l]gms[m]  [k][k]fls  lsgms[m]  [k]ksf[l]  [l]g[m][m]  [k]ksfls  lsg[m][m]  [k]ksf[l]  [l]gms[m]  [k]ksfls  lsgms[m]"
      by (simp add: matrix_bounded_semilattice_sup_bot.sup_monoid.add_assoc restrict_sup)
    also have "... = [k]f[l]  [l]g[m]  [k]fls  lsg[m]  [k][k][k]f[l]  [l]gmsms[m]  [k][k][k]fls  lsgmsms[m]  [k]ksksf[l]  [l]g[m][m][m]  [k]ksksfls  lsg[m][m][m]  [k]ksksf[l]  [l]gmsms[m]  [k]ksksfls  lsgmsms[m]"
      by (simp add: restrict_times)
    also have "... = [k]f[l]  [l]g[m]  [k]fls  lsg[m]"
      using 1 by (metis restrict_disjoint_left restrict_disjoint_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_right)
    finally show ?thesis
      .
  qed
  have 3: "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)ms = [k]f[l]  [l]gms  [k]fls  lsgms"
  proof -
    have "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)ms = [k]([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)ms"
      by (simp add: assms(2) restrict_nonempty_product)
    also have "... = [k][k]f[l]  [l]g[m]ms  [k][k]fls  lsg[m]ms  [k][k]f[l]  [l]gmsms  [k][k]fls  lsgmsms  [k]ksf[l]  [l]g[m]ms  [k]ksfls  lsg[m]ms  [k]ksf[l]  [l]gmsms  [k]ksfls  lsgmsms"
      by (simp add: matrix_bounded_semilattice_sup_bot.sup_monoid.add_assoc restrict_sup)
    also have "... = [k][k][k]f[l]  [l]g[m][m]ms  [k][k][k]fls  lsg[m][m]ms  [k]f[l]  [l]gms  [k]fls  lsgms  [k]ksksf[l]  [l]g[m][m]ms  [k]ksksfls  lsg[m][m]ms  [k]ksksf[l]  [l]gmsmsms  [k]ksksfls  lsgmsmsms"
      by (simp add: restrict_times)
    also have "... = [k]f[l]  [l]gms  [k]fls  lsgms"
      using 1 by (metis restrict_disjoint_left restrict_disjoint_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_left)
    finally show ?thesis
      .
  qed
  have 4: "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = ksf[l]  [l]g[m]  ksfls  lsg[m]"
  proof -
    have "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = ks([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)[m]"
      by (simp add: assms(2) restrict_nonempty_product)
    also have "... = ks[k]f[l]  [l]g[m][m]  ks[k]fls  lsg[m][m]  ks[k]f[l]  [l]gms[m]  ks[k]fls  lsgms[m]  ksksf[l]  [l]g[m][m]  ksksfls  lsg[m][m]  ksksf[l]  [l]gms[m]  ksksfls  lsgms[m]"
      by (simp add: matrix_bounded_semilattice_sup_bot.sup_monoid.add_assoc restrict_sup)
    also have "... = ks[k][k]f[l]  [l]g[m][m][m]  ks[k][k]fls  lsg[m][m][m]  ks[k][k]f[l]  [l]gmsms[m]  ks[k][k]fls  lsgmsms[m]  ksf[l]  [l]g[m]  ksfls  lsg[m]  ksksksf[l]  [l]gmsms[m]  ksksksfls  lsgmsms[m]"
      by (simp add: restrict_times)
    also have "... = ksf[l]  [l]g[m]  ksfls  lsg[m]"
      using 1 by (metis restrict_disjoint_left restrict_disjoint_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_left)
    finally show ?thesis
      .
  qed
  have 5: "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)ms = ksf[l]  [l]gms  ksfls  lsgms"
  proof -
    have "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)ms = ks([k]f[l]  [l]g[m]  [k]fls  lsg[m])  ([k]f[l]  [l]gms  [k]fls  lsgms)  (ksf[l]  [l]g[m]  ksfls  lsg[m])  (ksf[l]  [l]gms  ksfls  lsgms)ms"
      by (simp add: assms(2) restrict_nonempty_product)
    also have "... = ks[k]f[l]  [l]g[m]ms  ks[k]fls  lsg[m]ms  ks[k]f[l]  [l]gmsms  ks[k]fls  lsgmsms  ksksf[l]  [l]g[m]ms  ksksfls  lsg[m]ms  ksksf[l]  [l]gmsms  ksksfls  lsgmsms"
      by (simp add: matrix_bounded_semilattice_sup_bot.sup_monoid.add_assoc restrict_sup)
    also have "... = ks[k][k]f[l]  [l]g[m][m]ms  ks[k][k]fls  lsg[m][m]ms  ks[k][k]f[l]  [l]gmsmsms  ks[k][k]fls  lsgmsmsms  ksksksf[l]  [l]g[m][m]ms  ksksksfls  lsg[m][m]ms  ksf[l]  [l]gms  ksfls  lsgms"
      by (simp add: restrict_times)
    also have "... = ksf[l]  [l]gms  ksfls  lsgms"
      using 1 by (metis restrict_disjoint_left restrict_disjoint_right matrix_bounded_semilattice_sup_bot.sup_monoid.add_0_left)
    finally show ?thesis
      .
  qed
  have "(k#ks)f(l#ls)  (l#ls)g(m#ms) = (k#ks)h(m#ms)  (k#ks)(k#ks)f(l#ls)  (l#ls)g(m#ms)(m#ms) = (k#ks)h(m#ms)"
    by (simp add: restrict_times)
  also have "...  [k](k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = [k]h[m]  [k](k#ks)f(l#ls)  (l#ls)g(m#ms)ms = [k]hms  ks(k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = ksh[m]  ks(k#ks)f(l#ls)  (l#ls)g(m#ms)ms = kshms"
    by (meson restrict_nonempty_eq)
  also have "...  [k]f[l]  [l]g[m]  [k]fls  lsg[m] = [k]h[m]  [k]f[l]  [l]gms  [k]fls  lsgms = [k]hms  ksf[l]  [l]g[m]  ksfls  lsg[m] = ksh[m]  ksf[l]  [l]gms  ksfls  lsgms = kshms"
    using 2 3 4 5 by simp
  finally show ?thesis
    by simp
qed

text ‹
The following lemma gives a componentwise characterisation of the inequality of a matrix and a product of two matrices.
›

lemma restrict_nonempty_product_less_eq:
  fixes f g h :: "('a::finite,'b::idempotent_semiring) square"
  assumes "¬ List.member ks k"
      and "¬ List.member ls l"
      and "¬ List.member ms m"
    shows "(k#ks)f(l#ls)  (l#ls)g(m#ms)  (k#ks)h(m#ms)  [k]f[l]  [l]g[m]  [k]fls  lsg[m]  [k]h[m]  [k]f[l]  [l]gms  [k]fls  lsgms  [k]hms  ksf[l]  [l]g[m]  ksfls  lsg[m]  ksh[m]  ksf[l]  [l]gms  ksfls  lsgms  kshms"
proof -
  have 1: "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = [k]f[l]  [l]g[m]  [k]fls  lsg[m]"
    by (metis assms restrict_nonempty_product_eq restrict_times)
  have 2: "[k](k#ks)f(l#ls)  (l#ls)g(m#ms)ms = [k]f[l]  [l]gms  [k]fls  lsgms"
    by (metis assms restrict_nonempty_product_eq restrict_times)
  have 3: "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)[m] = ksf[l]  [l]g[m]  ksfls  lsg[m]"
    by (metis assms restrict_nonempty_product_eq restrict_times)
  have 4: "ks(k#ks)f(l#ls)  (l#ls)g(m#ms)ms = ksf[l]  [l]gms  ksfls  lsgms"
    by (metis assms restrict_nonempty_product_eq restrict_times)
  have "(k#ks)f(l#ls)  (l#ls)g(m#ms)  (k#ks)h(m#ms)  (k#ks)(k#ks)f(l#ls)  (l#ls)g(m#ms)(m#ms)  (k#ks)h(m#ms)"
    by (simp add: restrict_times)
  also have "...  [k](k#ks)f(l#ls)  (l#ls)g(m#ms)[m]  [k]h[m]  [k](k#ks)f(l#ls)  (l#ls)g(m#ms)ms  [k]hms  ks(k#ks)f(l#ls)  (l#ls)g(m#ms)[m]  ksh[m]  ks(k#ks)f(l#ls)  (l#ls)g(m#ms)ms  kshms"
    by (meson restrict_nonempty_less_eq)
  also have "...  [k]f[l]  [l]g[m]  [k]fls  lsg[m]  [k]h[m]  [k]f[l]  [l]gms  [k]fls  lsgms  [k]hms  ksf[l]  [l]g[m]  ksfls  lsg[m]  ksh[m]  ksf[l]  [l]gms  ksfls  lsgms  kshms"
    using 1 2 3 4 by simp
  finally show ?thesis
    by simp
qed

text ‹
The Kleene star induction laws hold for matrices with a single entry on the diagonal.
The matrix g› can actually contain a whole row/colum at the appropriate index.
›

lemma restrict_star_left_induct:
  fixes f g :: "('a::finite,'b::kleene_algebra) square"
  shows "distinct ms  [l]f[l]  [l]gms  [l]gms  [l]star o f[l]  [l]gms  [l]gms"
proof (induct ms)
  case Nil thus ?case
    by (simp add: restrict_empty_right)
next
  case (Cons m ms)
  assume 1: "distinct ms  [l]f[l]  [l]gms  [l]gms  [l]star o f[l]  [l]gms  [l]gms"
  assume 2: "distinct (m#ms)"
  assume 3: "[l]f[l]  [l]g(m#ms)  [l]g(m#ms)"
  have 4: "[l]f[l]  [l]g[m]  [l]g[m]  [l]f[l]  [l]gms  [l]gms"
    using 2 3 by (metis distinct.simps(2) matrix_semilattice_sup.sup.bounded_iff member_def member_rec(2) restrict_nonempty_product_less_eq)
  hence 5: "[l]star o f[l]  [l]gms  [l]gms"
    using 1 2 by simp
  have "f (l,l) * g (l,m)  g (l,m)"
    using 4 by (metis restrict_singleton_product restrict_singleton less_eq_matrix_def)
  hence 6: "(f (l,l)) * g (l,m)  g (l,m)"
    by (simp add: star_left_induct_mult)
  have "[l]star o f[l]  [l]g[m]  [l]g[m]"
  proof (unfold less_eq_matrix_def, rule allI, rule prod_cases)
    fix i j
    have "([l]star o f[l]  [l]g[m]) (i,j) = (⨆⇩k ([l]star o f[l]) (i,k) * ([l]g[m]) (k,j))"
      by (simp add: times_matrix_def)
    also have "... = (⨆⇩k (if i = l  k = l then (f (i,k)) else bot) * (if k = l  j = m then g (k,j) else bot))"
      by (simp add: restrict_singleton o_def)
    also have "... = (⨆⇩k if k = l then (if i = l then (f (i,k)) else bot) * (if j = m then g (k,j) else bot) else bot)"
      by (rule sup_monoid.sum.cong) auto
    also have "... = (if i = l then (f (i,l)) else bot) * (if j = m then g (l,j) else bot)"
      by simp
    also have "... = (if i = l  j = m then (f (l,l)) * g (l,m) else bot)"
      by simp
    also have "...  ([l]g[m]) (i,j)"
      using 6 by (simp add: restrict_singleton)
    finally show "([l]star o f[l]  [l]g[m]) (i,j)  ([l]g[m]) (i,j)"
      .
  qed
  thus "[l]star o f[l]  [l]g(m#ms)  [l]g(m#ms)"
    using 2 5 by (metis (no_types, opaque_lifting) matrix_idempotent_semiring.mult_left_dist_sup matrix_semilattice_sup.sup.mono restrict_nonempty_right)
qed

lemma restrict_star_right_induct:
  fixes f g :: "('a::finite,'b::kleene_algebra) square"
  shows "distinct ms  msg[l]  [l]f[l]  msg[l]  msg[l]  [l]star o f[l]  msg[l]"
proof (induct ms)
  case Nil thus ?case
    by (simp add: restrict_empty_left)
next
  case (Cons m ms)
  assume 1: "distinct ms  msg[l]  [l]f[l]  msg[l]  msg[l]  [l]star o f[l]  msg[l]"
  assume 2: "distinct (m#ms)"
  assume 3: "(m#ms)g[l]  [l]f[l]  (m#ms)g[l]"
  have 4: "[m]g[l]  [l]f[l]  [m]g[l]  msg[l]  [l]f[l]  msg[l]"
    using 2 3 by (metis distinct.simps(2) matrix_semilattice_sup.sup.bounded_iff member_def member_rec(2) restrict_nonempty_product_less_eq)
  hence 5: "msg[l]  [l]star o f[l]  msg[l]"
    using 1 2  by simp
  have "g (m,l) * f (l,l)  g (m,l)"
    using 4 by (metis restrict_singleton_product restrict_singleton less_eq_matrix_def)
  hence 6: "g (m,l) * (f (l,l))  g (m,l)"
    by (simp add: star_right_induct_mult)
  have "[m]g[l]  [l]star o f[l]  [m]g[l]"
  proof (unfold less_eq_matrix_def, rule allI, rule prod_cases)
    fix i j
    have "([m]g[l]  [l]star o f[l]) (i,j) = (⨆⇩k ([m]g[l]) (i,k) * ([l]star o f[l]) (k,j))"
      by (simp add: times_matrix_def)
    also have "... = (⨆⇩k (if i = m  k = l then g (i,k) else bot) * (if k = l  j = l then (f (k,j)) else bot))"
      by (simp add: restrict_singleton o_def)
    also have "... = (⨆⇩k if k = l then (if i = m then g (i,k) else bot) * (if j = l then (f (k,j)) else bot) else bot)"
      by (rule sup_monoid.sum.cong) auto
    also have "... = (if i = m then g (i,l) else bot) * (if j = l then (f (l,j)) else bot)"
      by simp
    also have "... = (if i = m  j = l then g (m,l) * (f (l,l)) else bot)"
      by simp
    also have "...  ([m]g[l]) (i,j)"
      using 6 by (simp add: restrict_singleton)
    finally show "([m]g[l]  [l]star o f[l]) (i,j)  ([m]g[l]) (i,j)"
      .
  qed
  thus "(m#ms)g[l]  [l]star o f[l]  (m#ms)g[l]"
    using 2 5 by (metis (no_types, opaque_lifting) matrix_idempotent_semiring.mult_right_dist_sup matrix_semilattice_sup.sup.mono restrict_nonempty_left)
qed

lemma restrict_pp:
  fixes f :: "('a,'b::p_algebra) square"
  shows "ksfls = (ksfls)"
  by (unfold restrict_matrix_def uminus_matrix_def) auto

lemma pp_star_commute:
  fixes f :: "('a,'b::stone_kleene_relation_algebra) square"
  shows "(star o f) = star o f"
  by (simp add: uminus_matrix_def o_def pp_dist_star)

subsection ‹Matrices form a Kleene Algebra›

text ‹
Matrices over Kleene algebras form a Kleene algebra using Conway's construction.
It remains to prove one unfold and two induction axioms of the Kleene star.
Each proof is by induction over the size of the matrix represented by an index list.
›