Session Smooth_Manifolds

Theory Analysis_More

section ‹Library Additions›
theory Analysis_More
  imports "HOL-Analysis.Equivalence_Lebesgue_Henstock_Integration"
    "HOL-Library.Function_Algebras"
    "HOL-Types_To_Sets.Linear_Algebra_On"
begin


lemma openin_open_Int'[intro]:
  "open S  openin (top_of_set U) (S  U)"
  by (auto simp: openin_open)

subsection ‹Parametricity rules for topology›

text ‹TODO: also check with theory Transfer_Euclidean_Space_Vector› in AFP/ODE...›

context includes lifting_syntax begin

lemma Sigma_transfer[transfer_rule]:
  "(rel_set A ===> (A ===> rel_set B) ===> rel_set (rel_prod A B)) Sigma Sigma"
  unfolding Sigma_def
  by transfer_prover

lemma filterlim_transfer[transfer_rule]:
  "((A ===> B) ===> rel_filter B ===> rel_filter A ===> (=)) filterlim filterlim"
  if [transfer_rule]: "bi_unique B"
  unfolding filterlim_iff
  by transfer_prover

lemma nhds_transfer[transfer_rule]:
  "(A ===> rel_filter A) nhds nhds"
  if [transfer_rule]: "bi_unique A" "bi_total A" "(rel_set A ===> (=)) open open"
  unfolding nhds_def
  by transfer_prover

lemma at_within_transfer[transfer_rule]:
  "(A ===> rel_set A ===> rel_filter A) at_within at_within"
  if [transfer_rule]: "bi_unique A" "bi_total A" "(rel_set A ===> (=)) open open"
  unfolding at_within_def
  by transfer_prover

lemma continuous_on_transfer[transfer_rule]:
  "(rel_set A ===> (A ===> B) ===> (=)) continuous_on continuous_on"
  if [transfer_rule]: "bi_unique A" "bi_total A" "(rel_set A ===> (=)) open open"
    "bi_unique B" "bi_total B" "(rel_set B ===> (=)) open open"
  unfolding continuous_on_def
  by transfer_prover

lemma continuous_on_transfer_right_total[transfer_rule]:
  "(rel_set A ===> (A ===> B) ===> (=)) (λX::'a::t2_space set. continuous_on (X  Collect AP)) (λY::'b::t2_space set. continuous_on Y)"
  if DomainA: "Domainp A = AP"
    and [folded DomainA, transfer_rule]: "bi_unique A" "right_total A" "(rel_set A ===> (=)) (openin (top_of_set (Collect AP))) open"
    "bi_unique B" "bi_total B" "(rel_set B ===> (=)) open open"
  unfolding DomainA[symmetric]
proof (intro rel_funI)
  fix X Y f g
  assume H[transfer_rule]: "rel_set A X Y" "(A ===> B) f g"
  from H(1) have XA: "x  X  Domainp A x" for x
    by (auto simp: rel_set_def)
  then have *: "X  Collect (Domainp A) = X" by auto
  have "openin (top_of_set (Collect (Domainp A))) (Collect (Domainp A))" by auto
  show " continuous_on (X  Collect (Domainp A)) f = continuous_on Y g"
    unfolding continuous_on_eq_continuous_within continuous_within_topological *
    apply transfer
    apply safe
    subgoal for x B
      apply (drule bspec, assumption, drule spec, drule mp, assumption, drule mp, assumption)
      apply clarsimp
      subgoal for AA
        apply (rule exI[where x="AA  Collect (Domainp A)"])
        by (auto intro: XA)
      done
    subgoal using XA by (force simp: openin_subtopology)
    done
qed

lemma continuous_on_transfer_right_total2[transfer_rule]:
  "(rel_set A ===> (A ===> B) ===> (=)) (λX::'a::t2_space set. continuous_on X) (λY::'b::t2_space set. continuous_on Y)"
  if DomainB: "Domainp B = BP"
  and [folded DomainB, transfer_rule]: "bi_unique A" "bi_total A" "(rel_set A ===> (=)) open open"
    "bi_unique B" "right_total B" "(rel_set B ===> (=)) ((openin (top_of_set (Collect BP)))) open"
  unfolding DomainB[symmetric]
proof (intro rel_funI)
  fix X Y f g
  assume H[transfer_rule]: "rel_set A X Y" "(A ===> B) f g"
  show "continuous_on X f = continuous_on Y g"
    unfolding continuous_on_eq_continuous_within continuous_within_topological
    apply transfer
    apply safe
    subgoal for x C
      apply (clarsimp simp: openin_subtopology)
      apply (drule bspec, assumption, drule spec, drule mp, assumption, drule mp, assumption)
      apply clarsimp
      by (meson Domainp_applyI H(1) H(2) rel_setD1)
    subgoal for x C
    proof -
      let ?sub = "top_of_set (Collect (Domainp B))"
      assume cont: "xX. Ba{A. Ball A (Domainp B)}.
          openin (top_of_set (Collect (Domainp B))) Ba  f x  Ba  (Aa.  open Aa  x  Aa  (yX. y  Aa  f y  Ba))"
        and x: "x  X" "open C" "f x  C"
      let ?B = "C  Collect (Domainp B)"
      have "?B  {A. Ball A (Domainp B)}" by auto
      have "openin ?sub (Collect (Domainp B))" by auto
      then have "openin ?sub ?B" using ‹open C by auto
      moreover have "f x  ?B" using x
        apply transfer apply auto
        by (meson Domainp_applyI H(1) H(2) rel_setD1)
      ultimately obtain D where "open D  x  D  (yX. y  D  f y  ?B)"
        using cont x
        by blast
      then show "A. open A  x  A  (yX. y  A  f y  C)" by auto
    qed
    done
qed


lemma generate_topology_transfer[transfer_rule]:
  includes lifting_syntax
  assumes [transfer_rule]: "right_total A" "bi_unique A"
  shows "(rel_set (rel_set A) ===> rel_set A ===> (=)) (generate_topology o (insert (Collect (Domainp A)))) generate_topology"
proof (intro rel_funI)
  fix B C X Y assume t[transfer_rule]: "rel_set (rel_set A) B C" "rel_set A X Y"
  then have "X  Collect (Domainp A)" by (auto simp: rel_set_def)
  with t have rI: "rel_set A (X  Collect (Domainp A)) Y"
    by (auto simp: inf_absorb1)
  have eq_UNIV_I: "Z = UNIV" if [transfer_rule]: "rel_set A {a. Domainp A a} Z" for Z
    using that assms
    apply (auto simp: right_total_def rel_set_def)
    using bi_uniqueDr by fastforce
  show "(generate_topology  insert (Collect (Domainp A))) B X = generate_topology C Y"
    unfolding o_def
  proof (rule iffI)
    fix x
    assume "generate_topology (insert (Collect (Domainp A)) B) X"
    then show "generate_topology C Y" unfolding o_def
      using rI
    proof (induction X arbitrary: Y)
      case [transfer_rule]: UNIV
      with eq_UNIV_I[of Y] show ?case
        by (simp add: generate_topology.UNIV)
    next
      case (Int a b)
      note [transfer_rule] = Int(5)
      obtain a' where a'[transfer_rule]: "rel_set A (a  Collect (Domainp A)) a'"
        by (metis Domainp_iff Domainp_set Int_Collect)
      obtain b' where b'[transfer_rule]: "rel_set A (b  Collect (Domainp A)) b'"
        by (metis Domainp_iff Domainp_set Int_Collect)
      from Int.IH(1)[OF a'] Int.IH(2)[OF b']
      have "generate_topology C a'" "generate_topology C b'" by auto
      from generate_topology.Int[OF this] have "generate_topology C (a'  b')" .
      also have "a'  b' = Y"
        by transfer auto
      finally show ?case
        by (simp add: generate_topology.Int)
    next
      case (UN K)
      note [transfer_rule] = UN(3)
      have "K'. k. rel_set A (k  Collect (Domainp A)) (K' k)"
        by (rule choice) (metis Domainp_iff Domainp_set Int_Collect)
      then obtain K' where K': "k. rel_set A (k  Collect (Domainp A)) (K' k)" by metis
      from UN.IH[OF _ this] have "generate_topology C k'" if "k'  K'`K" for k' using that by auto
      from generate_topology.UN[OF this] have "generate_topology C ((K' ` K))" .
      also
      from K' have [transfer_rule]: "(rel_set (=) ===> rel_set A) (λx. x  Collect (Domainp A)) K'"
        by (fastforce simp: rel_fun_def rel_set_def)
      have "(K' ` K) = Y"
        by transfer auto
      finally show ?case
        by (simp add: generate_topology.UN)
    next
      case (Basis s)
      from this(1) show ?case
      proof
        assume "s = Collect (Domainp A)" 
        with eq_UNIV_I[of Y] Basis(2)
        show ?case
          by (simp add: generate_topology.UNIV)
      next
        assume "s  B"
        with Basis(2) obtain t where [transfer_rule]: "rel_set A (s  Collect (Domainp A)) t" by auto
        from Basis(1) t(1) have s: "s  Collect (Domainp A) = s"
          by (force simp: rel_set_def)
        have "t  C" using s  B s
          by transfer auto
        also note [transfer_rule] = Basis(2)
        have "t = Y"
          by transfer auto
        finally show ?case
          by (rule generate_topology.Basis)
      qed
    qed
  next
    assume "generate_topology C Y"
    then show "generate_topology (insert (Collect (Domainp A)) B) X"
      using ‹rel_set A X Y
    proof (induction arbitrary: X)
      case [transfer_rule]: UNIV
      have "UNIV = (UNIV::'b set)" by auto
      then have "X = {a. Domainp A a}" by transfer
      then show ?case by (intro generate_topology.Basis) auto
    next
      case (Int a b)
      obtain a' b' where [transfer_rule]: "rel_set A a' a" "rel_set A b' b"
        by (meson assms(1) right_total_def right_total_rel_set)
      from generate_topology.Int[OF Int.IH(1)[OF this(1)] Int.IH(2)[OF this(2)]]
      have "generate_topology (insert {a. Domainp A a} B) (a'  b')" by simp
      also
      define I where "I = a  b"
      from ‹rel_set A X (a  b) have [transfer_rule]: "rel_set A X I" by (simp add: I_def)
      from I_def
      have "a'  b' = X" by transfer simp
      finally show ?case .
    next
      case (UN K)
      have "K'. k. rel_set A (K' k) k"
        by (rule choice) (meson assms(1) right_total_def right_total_rel_set)
      then obtain K' where K': "k. rel_set A (K' k) k" by metis
      from UN.IH[OF _ this] have "generate_topology (insert {a. Domainp A a} B) k"
        if "k  K'`K" for k using that by auto
      from generate_topology.UN[OF this]
      have "generate_topology (insert {a. Domainp A a} B) ((K'`K))" by auto
      also
      from K' have [transfer_rule]: "(rel_set (=) ===> rel_set A) K' id"
        by (fastforce simp: rel_fun_def rel_set_def)
      define U where "U =  ((id ` K))"
      from ‹rel_set A X _ have [transfer_rule]: "rel_set A X U" by (simp add: U_def)
      from U_def have "(K' ` K) = X" by transfer simp
      finally show ?case .
    next
      case (Basis s)
      note [transfer_rule] = ‹rel_set A X s
      from s  C have "X  B" by transfer
      then show ?case by (intro generate_topology.Basis) auto
    qed
  qed
qed

end


subsection ‹Miscellaneous›

lemmas [simp del] = mem_ball

lemma in_closureI[intro, simp]: "x  X  x  closure X"
  using closure_subset by auto

lemmas open_continuous_vimage = continuous_on_open_vimage[THEN iffD1, rule_format]
lemma open_continuous_vimage': "open s  continuous_on s f  open B  open (s  f -` B)"
  using open_continuous_vimage[of s f B] by (auto simp: Int_commute)

lemma support_on_mono: "support_on carrier f  support_on carrier g"
  if "x. x  carrier  f x  0  g x  0"
  using that
  by (auto simp: support_on_def)


lemma image_prod: "(λ(x, y). (f x, g y)) ` (A × B) = f ` A × g ` B" by auto


subsection ‹Closed support›

definition "csupport_on X S = closure (support_on X S)"

lemma closed_csupport_on[intro, simp]: "closed (csupport_on carrier φ)"
  by (auto simp: csupport_on_def)

lemma not_in_csupportD: "x  csupport_on carrier φ  x  carrier  φ x = 0"
  by (auto simp: csupport_on_def support_on_def)

lemma csupport_on_mono: "csupport_on carrier f  csupport_on carrier g"
  if "x. x  carrier  f x  0  g x  0"
  unfolding csupport_on_def
  apply (rule closure_mono)
  using that
  by (rule support_on_mono)

subsection ‹Homeomorphism›

lemma homeomorphism_empty[simp]:
  "homeomorphism {} t f f'  t = {}"
  "homeomorphism s {} f f'  s = {}"
  by (auto simp: homeomorphism_def)

lemma homeomorphism_add:
  "homeomorphism UNIV UNIV (λx. x + c) (λx. x - c)"
  for c::"_::real_normed_vector"
  unfolding homeomorphism_def
  by (auto simp: algebra_simps continuous_intros intro!: image_eqI[where x="x - c" for x])

lemma in_range_scaleR_iff: "x  range ((*R) c)  c = 0  x = 0"
  for x::"_::real_vector"
  by (auto simp: intro!: image_eqI[where x="x /R c"])

lemma homeomorphism_scaleR:
  "homeomorphism UNIV UNIV (λx. c *R x::_::real_normed_vector) (λx. x /R c)"
  if "c  0"
  using that
  unfolding homeomorphism_def
  by (auto simp: in_range_scaleR_iff algebra_simps intro!: continuous_intros)


lemma homeomorphism_prod:
  "homeomorphism (a × b) (c × d) (λ(x, y). (f x, g y)) (λ(x, y). (f' x, g' y))"
  if "homeomorphism a c f f'"
     "homeomorphism b d g g'"
  using that by (simp add: homeomorphism_def image_prod)
    (auto simp add: split_beta intro!: continuous_intros elim: continuous_on_compose2)


subsection ‹Generalizations›

lemma openin_subtopology_eq_generate_topology:
  "openin (top_of_set S) x = generate_topology (insert S ((λB. B  S) ` BB)) x"
  if open_gen: "open = generate_topology BB" and subset: "x  S"
proof -
  have "generate_topology (insert S ((λB. B  S) ` BB)) (T  S)"
    if "generate_topology BB T"
    for T
    using that
  proof (induction)
    case UNIV
    then show ?case by (auto intro!: generate_topology.Basis)
  next
    case (Int a b)
    have "generate_topology (insert S ((λB. B  S) ` BB)) (a  S  (b  S))"
      by (rule generate_topology.Int) (use Int in auto)
    then show ?case by (simp add: ac_simps)
  next
    case (UN K)
    then have "generate_topology (insert S ((λB. B  S) ` BB)) (kK. k  S)"
      by (intro generate_topology.UN) auto
    then show ?case by simp
  next
    case (Basis s)
    then show ?case
      by (intro generate_topology.Basis) auto
  qed
  moreover
  have "T. generate_topology BB T  x = T  S"
    if "generate_topology (insert S ((λB. B  S) ` BB)) x" "x  UNIV"
    using that
  proof (induction)
    case UNIV
    then show ?case by simp
  next
    case (Int a b)
    then show ?case
      using generate_topology.Int 
      by auto
  next
    case (UN K)
    from UN.IH have "kK-{UNIV}. T. generate_topology BB T  k = T  S" by auto
    from this[THEN bchoice] obtain T where T: "k. k  T ` (K - {UNIV})  generate_topology BB k" "k. k  K - {UNIV}  k = (T k)  S"
      by auto
    from generate_topology.UN[OF T(1)]
    have "generate_topology BB ((T ` (K - {UNIV})))" by auto
    moreover have "K = ((T ` (K - {UNIV})))  S" if "UNIV  K" using T(2) UN that by auto
    ultimately show ?case
      apply (cases "UNIV  K") subgoal using UN by auto
      subgoal by auto
      done
  next
    case (Basis s)
    then show ?case
      using generate_topology.UNIV generate_topology.Basis by blast
  qed
  moreover
  have "T. generate_topology BB T  UNIV = T  S" if "generate_topology (insert S ((λB. B  S) ` BB)) x"
     "x = UNIV"
  proof -
    have "S = UNIV"
      using that x  S
      by auto
    then show ?thesis by (simp add: generate_topology.UNIV)
  qed
  ultimately show ?thesis
    by (metis open_gen open_openin openin_open_Int' openin_subtopology)
qed

subsection ‹Equal topologies›

lemma topology_eq_iff: "t = s  (topspace t = topspace s 
  (xtopspace t. openin t x = openin s x))"
  by (metis (full_types) openin_subset topology_eq)


subsection ‹Finer topologies›

definition finer_than (infix "(finer'_than)" 50)
  where "T1 finer_than T2  continuous_map T1 T2 (λx. x)"

lemma finer_than_iff_nhds:
  "T1 finer_than T2  (X. openin T2 X  openin T1 (X  topspace T1))  (topspace T1  topspace T2)"
  by (auto simp: finer_than_def continuous_map_alt)

lemma continuous_on_finer_topo:
  "continuous_map s t f"
  if "continuous_map s' t f" "s finer_than s'"
  using that
  by (auto simp: finer_than_def o_def dest: continuous_map_compose)

lemma continuous_on_finer_topo2:
  "continuous_map s t f"
  if "continuous_map s t' f" "t' finer_than t"
  using that
  by (auto simp: finer_than_def o_def dest: continuous_map_compose)

lemma antisym_finer_than: "S = T" if "S finer_than T" "T finer_than S"
  using that
  apply (auto simp: finer_than_def topology_eq_iff continuous_map_alt)
  apply (metis inf.orderE)+
  done

lemma subtopology_finer_than[simp]: "top_of_set X finer_than euclidean"
  by (auto simp: finer_than_iff_nhds openin_subtopology)

subsection ‹Support›

lemma support_on_nonneg_sum:
  "support_on X (λx. iS. f i x) = (iS. support_on X (f i))"
  if "finite S" "x i . x  X  i  S  f i x  0"
  for f::"___::ordered_comm_monoid_add"
  using that by (auto simp: support_on_def sum_nonneg_eq_0_iff)

lemma support_on_nonneg_sum_subset:
  "support_on X (λx. iS. f i x)  (iS. support_on X (f i))"
  for f::"___::ordered_comm_monoid_add"
by (cases "finite S") (auto simp: support_on_def, meson sum.neutral)

lemma support_on_nonneg_sum_subset':
  "support_on X (λx. iS x. f i x)  (xX. (iS x. support_on X (f i)))"
  for f::"___::ordered_comm_monoid_add"
  by (auto simp: support_on_def, meson sum.neutral)

subsection ‹Final topology (Bourbaki, General Topology I, 4.)›

definition "final_topology X Y f =
  topology (λU. U  X 
    (i. openin (Y i) (f i -` U  topspace (Y i))))"

lemma openin_final_topology:
  "openin (final_topology X Y f) =
    (λU. U  X  (i. openin (Y i) (f i -` U  topspace (Y i))))"
  unfolding final_topology_def
  apply (rule topology_inverse')
  unfolding istopology_def
proof safe
  fix S T i
  assume "i. openin (Y i) (f i -` S  topspace (Y i))"
    "i. openin (Y i) (f i -` T  topspace (Y i))"
  then have "openin (Y i) (f i -` S  topspace (Y i)  (f i -` T  topspace (Y i)))"
    (is "openin _ ?I")
    by auto
  also have "?I = f i -` (S  T)  topspace (Y i)"
    (is "_ = ?R")
    by auto
  finally show "openin (Y i) ?R" .
next
  fix K i
  assume "UK. U  X  (i. openin (Y i) (f i -` U  topspace (Y i)))"
  then have "openin (Y i) (XK. f i -` X  topspace (Y i))"
    by (intro openin_Union) auto
  then show "openin (Y i) (f i -` K  topspace (Y i))"
    by (auto simp: vimage_Union)
qed force+

lemma topspace_final_topology:
  "topspace (final_topology X Y f) = X"
  if "i. f i  topspace (Y i)  X"
proof -
  have *: "f i -` X  topspace (Y i) = topspace (Y i)" for i
    using that
    by auto
  show ?thesis
    unfolding topspace_def
    unfolding openin_final_topology
    apply (rule antisym)
     apply force
    apply (rule subsetI)
    apply (rule UnionI[where X=X])
    using that
    by (auto simp: *)
qed

lemma continuous_on_final_topologyI2:
  "continuous_map (Y i) (final_topology X Y f) (f i)"
  if "i. f i  topspace (Y i)  X"
  using that
  by (auto simp: openin_final_topology continuous_map_alt topspace_final_topology)

lemma continuous_on_final_topologyI1:
  "continuous_map (final_topology X Y f) Z g"
  if hyp: "i. continuous_map (Y i) Z (g o f i)"
    and that: "i. f i  topspace (Y i)  X" "g  X  topspace Z"
  unfolding continuous_map_alt
proof safe
  fix V assume V: "openin Z V"
  have oV: "openin (Y i) (f i -` g -` V  topspace (Y i))"
    for i
    using hyp[rule_format, of i] V
    by (auto simp: continuous_map_alt vimage_comp dest!: spec[where x=V])
  have *: "f i -` g -` V  f i -` X  topspace (Y i) =
      f i -` g -` V  topspace (Y i)"
    (is "_ = ?rhs i")
    for i using that
    by auto
  show "openin (final_topology X Y f) (g -` V  topspace (final_topology X Y f))"
    by (auto simp: openin_final_topology oV topspace_final_topology that *)
qed (use that in auto simp: topspace_final_topology›)


lemma continuous_on_final_topology_iff:
  "continuous_map (final_topology X Y f) Z g  (i. continuous_map (Y i) Z (g o f i))"
  if "i. f i  topspace (Y i)  X" "g  X  topspace Z"
  using that
  by (auto intro!: continuous_on_final_topologyI1[OF _ that]
      intro: continuous_map_compose[OF continuous_on_final_topologyI2[OF that(1)]])


subsection ‹Quotient topology›

definition map_topology :: "('a  'b)  'a topology  'b topology" where
  "map_topology p X = final_topology (p ` topspace X) (λ_. X) (λ(_::unit). p)"

lemma openin_map_topology:
  "openin (map_topology p X) = (λU. U  p ` topspace X  openin X (p -` U  topspace X))"
  by (auto simp: map_topology_def openin_final_topology)

lemma topspace_map_topology[simp]: "topspace (map_topology f T) = f ` topspace T"
  unfolding map_topology_def
  by (subst topspace_final_topology) auto

lemma continuous_on_map_topology:
  "continuous_map T (map_topology f T) f"
  unfolding continuous_map_alt openin_map_topology
  by auto

lemma continuous_map_composeD:
  "continuous_map T X (g  f)  g  f ` topspace T  topspace X"
  by (auto simp: continuous_map_def)

lemma continuous_on_map_topology2:
  "continuous_map T X (g  f)  continuous_map (map_topology f T) X g"
  unfolding map_topology_def
  apply safe
  subgoal
    apply (rule continuous_on_final_topologyI1)
    subgoal by assumption
    subgoal by force
    subgoal by (rule continuous_map_composeD)
    done
  subgoal
    apply (erule continuous_map_compose[rotated])
    apply (rule continuous_on_final_topologyI2)
    by force
  done

lemma map_sub_finer_than_commute:
  "map_topology f (subtopology T (f -` X)) finer_than subtopology (map_topology f T) X"
  by (auto simp: finer_than_def continuous_map_def openin_subtopology openin_map_topology
      topspace_subtopology)

lemma sub_map_finer_than_commute:
  "subtopology (map_topology f T) X finer_than map_topology f (subtopology T (f -` X))"
  if "openin T (f -` X)"― ‹this is more or less the condition from
    🌐‹https://math.stackexchange.com/questions/705840/quotient-topology-vs-subspace-topology›
  unfolding finer_than_def continuous_map_alt
proof (rule conjI, clarsimp)
  fix U
  assume "openin (map_topology f (subtopology T (f -` X))) U"
  then obtain W where W: "U  f ` (topspace T  f -` X)" "openin T W" "f -` U  (topspace T  f -` X) = W  f -` X"
    by (auto simp: topspace_subtopology openin_subtopology openin_map_topology)
  have "(f -` f ` W  f -` X)  topspace T = W  topspace T  f -` X"
    apply auto
    by (metis Int_iff W(3) vimage_eq)
  also have "openin T "
    by (auto intro!: W that)
  finally show "openin (subtopology (map_topology f T) X) (U  (f ` topspace T  X))"
    using W
    unfolding topspace_subtopology topspace_map_topology openin_subtopology openin_map_topology
    by (intro exI[where x="(f ` W  X)"]) auto
qed auto

lemma subtopology_map_topology:
  "subtopology (map_topology f T) X = map_topology f (subtopology T (f -` X))"
  if "openin T (f -` X)"
  apply (rule antisym_finer_than)
  using sub_map_finer_than_commute[OF that] map_sub_finer_than_commute[of f T X]
  by auto

lemma quotient_map_map_topology:
  "quotient_map X (map_topology f X) f"
  by (auto simp: quotient_map_def openin_map_topology ac_simps)
    (simp_all add: vimage_def Int_def)

lemma topological_space_quotient: "class.topological_space (openin (map_topology f euclidean))"
  if "surj f"
  apply standard
    apply (auto simp: )
  using that
  by (auto simp: openin_map_topology)

lemma t2_space_quotient: "class.t2_space (open::'b set  bool)"
  if open_def: "open = (openin (map_topology (p::'a::t2_space'b::topological_space) euclidean))"
    "surj p" and open_p: "X. open X  open (p ` X)" and "closed {(x, y). p x = p y}" (is "closed ?R")
  apply (rule class.t2_space.intro)
  subgoal by (unfold open_def, rule topological_space_quotient; fact)
proof standard
  fix a b::'b
  obtain x y where a_def: "a = p x" and b_def: "b = p y" using ‹surj p by fastforce
  assume "a  b"
  with ‹closed ?R have "open (-?R)" "(x, y)  -?R" by (auto simp add: a_def b_def)
  from open_prod_elim[OF this]
  obtain Nx Ny where "open Nx" "open Ny" "(x, y)  Nx × Ny" "Nx × Ny  -?R" .
  then have "p ` Nx  p ` Ny = {}" by auto
  moreover
  from ‹open Nx ‹open Ny have "open (p ` Nx)" "open (p ` Ny)"
    using open_p by blast+
  moreover have "a  p ` Nx" "b  p ` Ny" using (x, y)  _ × _ by (auto simp: a_def b_def)
  ultimately show "U V. open U  open V  a  U  b  V  U  V = {}" by blast
qed

lemma second_countable_topology_quotient: "class.second_countable_topology (open::'b set  bool)"
  if open_def: "open = (openin (map_topology (p::'a::second_countable_topology'b::topological_space) euclidean))"
    "surj p" and open_p: "X. open X  open (p ` X)"
  apply (rule class.second_countable_topology.intro)
  subgoal by (unfold open_def, rule topological_space_quotient; fact)
proof standard
  have euclidean_def: "euclidean = map_topology p euclidean"
    by (simp add: openin_inverse open_def)
  have continuous_on: "continuous_on UNIV p"
    using continuous_map_iff_continuous2 continuous_on_map_topology euclidean_def by fastforce
  from ex_countable_basis[where 'a='a] obtain A::"'a set set" where "countable A" "topological_basis A"
    by auto
  define B where "B = (λX. p ` X) ` A"
  have "countable (B::'b set set)"
    by (auto simp: B_def intro!: ‹countable A)
  moreover have "topological_basis B"
  proof (rule topological_basisI)
    fix B' assume "B'  B" then show "open B'" using ‹topological_basis A
      by (auto simp: B_def topological_basis_open intro!: open_p)
  next
    fix x::'b and O' assume "open O'" "x  O'"
    have "open (p -` O')"
      using ‹open O'
      by (rule open_vimage) (auto simp: continuous_on)
    obtain y where y: "y  p -` {x}"
      using x  O'
      by auto (metis UNIV_I open_def(2) rangeE)
    then have "y  p -` O'" using x  O' by auto
    from topological_basisE[OF ‹topological_basis A ‹open (p -` O') this]
    obtain C where "C  A" "y  C" "C  p -` O'" .
    let ?B' = "p ` C"
    have "?B'  B"
      using C  A by (auto simp: B_def)
    moreover
    have "x  ?B'" using y y  C x  O'
      by auto
    moreover
    have "?B'  O'"
      using C  _ by auto
    ultimately show "B'B. x  B'  B'  O'" by metis
  qed
  ultimately show "B::'b set set. countable B  open = generate_topology B"
    by (auto simp: topological_basis_imp_subbasis)
qed


subsection ‹Closure›

lemma closure_Union: "closure (X) = (xX. closure x)" if "finite X"
  using that
  by (induction X) auto

subsection ‹Compactness›

lemma compact_if_closed_subset_of_compact:
  "compact S" if "closed S" "compact T" "S  T"
proof (rule compactI)
  fix UU assume UU: "tUU. open t" "S  UU"
  have "T  (insert (- S) (UU))" "B. B  insert (- S) UU  open B"
    using UU S  T
    by (auto simp: open_Compl ‹closed S)
  from compactE[OF ‹compact T this]
  obtain 𝒯' where 𝒯: "𝒯'  insert (- S) UU" "finite 𝒯'" "T  𝒯'"
    by metis
  show "C'UU. finite C'  S  C'"
    apply (rule exI[where x="𝒯' - {-S}"])
    using 𝒯 UU
    apply auto
  proof -
    fix x assume "x  S"
    with 𝒯 S  T obtain U where "x  U" "U  𝒯'" using 𝒯
      by auto
    then show "X𝒯' - {- S}. x  X"
      using 𝒯 UU x  S
      apply -
      apply (rule bexI[where x=U])
      by auto
  qed
qed

subsection ‹Locally finite›

definition "locally_finite_on X I U  (pX. N. pN  open N  finite {iI. U i  N  {}})"

lemmas locally_finite_onI = locally_finite_on_def[THEN iffD2, rule_format]

lemma locally_finite_onE:
  assumes "locally_finite_on X I U"
  assumes "p  X"
  obtains N where "p  N" "open N" "finite {iI. U i  N  {}}"
  using assms
  by (auto simp: locally_finite_on_def)

lemma locally_finite_onD:
  assumes "locally_finite_on X I U"
  assumes "p  X"
  shows "finite {iI. p  U i}"
  apply (rule locally_finite_onE[OF assms])
  apply (rule finite_subset)
  by auto

lemma locally_finite_on_open_coverI: "locally_finite_on X I U"
  if fin: "j. j  I  finite {iI. U i  U j  {}}"
    and open_cover: "X  (iI. U i)" "i. i  I  open (U i)"
proof (rule locally_finite_onI)
  fix p assume "p  X"
  then obtain i where i: "i  I" "p  U i" "open (U i)"
    using open_cover
    by blast
  show "N. p  N  open N  finite {i  I. U i  N  {}}"
    by (intro exI[where x="U i"] conjI i fin)
qed

lemma locally_finite_compactD:
  "finite {iI. U i  V  {}}"
  if lf: "locally_finite_on X I U"
    and compact: "compact V"
    and subset: "V  X"
proof -
  have "N. p  X. p  N p  open (N p)  finite {iI. U i  N p  {}}"
    by (rule bchoice) (auto elim!: locally_finite_onE[OF lf, rule_format])
  then obtain N where N: "p. p  X  p  N p"
    "p. p  X  open (N p)"
    "p. p  X  finite {iI. U i  N p  {}}"
    by blast
  have "V  (pX. N p)" "B. B  N ` X  open B"
    using N subset by force+
  from compactE[OF compact this]
  obtain C where C: "C  X" "finite C" "V  (N ` C)"
    by (metis finite_subset_image)
  then have "{iI. U i  V  {}}  {iI. U i  (N ` C)  {}}"
    by force
  also have "  (cC. {iI. U i  N c  {}})"
    by force
  also have "finite "
    apply (rule finite_Union)
    using C by (auto intro!: C N)
  finally (finite_subset) show ?thesis .
qed

lemma closure_Int_open_eq_empty: "open S  (closure T  S) = {}  T  S = {}"
  by (auto simp: open_Int_closure_eq_empty ac_simps)

lemma locally_finite_on_subset:
  assumes "locally_finite_on X J U"
  assumes "i. i  I  V i  U i" "I  J"
  shows "locally_finite_on X I V"
proof (rule locally_finite_onI)
  fix p assume "p  X"
  from locally_finite_onE[OF assms(1) this]
  obtain N where "p  N" "open N" "finite {i  J. U i  N  {}}" .
  then show "N. p  N  open N  finite {i  I. V i  N  {}}"
    apply (intro exI[where x=N])
    using assms
    by (auto elim!: finite_subset[rotated])
qed

lemma locally_finite_on_closure:
  "locally_finite_on X I (λx. closure (U x))"
  if "locally_finite_on X I U"
proof (rule locally_finite_onI)
  fix p assume "p  X"
  from locally_finite_onE[OF that this] obtain N
    where "p  N" "open N" "finite {i  I. U i  N  {}}" .
  then show "N. p  N  open N  finite {i  I. closure (U i)  N  {}}"
    by (auto intro!: exI[where x=N] simp: closure_Int_open_eq_empty)
qed


lemma locally_finite_on_closedin_Union_closure:
  "closedin (top_of_set X) (iI. closure (U i))"
  if "locally_finite_on X I U" "i. i  I  closure (U i)  X"
  unfolding closedin_def
  apply safe
  subgoal using that(2) by auto
  subgoal
    apply (subst openin_subopen)
  proof clarsimp
    fix x
    assume x: "x  X" "iI. x  closure (U i)"
    from locally_finite_onE[OF that(1) x  X]
    obtain N where N: "x  N" "open N" "finite {i  I. U i  N  {}}" (is "finite ?I").
    define N' where "N' = N - (i  ?I. closure (U i))"
    have "open N'"
      by (auto simp: N'_def intro!: N)
    then have "openin (top_of_set X) (X  N')"
      by (rule openin_open_Int)
    moreover
    have "x  X  N'" using x
      by (auto simp: N'_def N)
    moreover
    have "X  N'  X - (iI. closure (U i))"
      using x that(2)
      apply (auto simp: N'_def)
      by (meson N(2) closure_iff_nhds_not_empty dual_order.refl)
    ultimately show "T. openin (top_of_set X) T  x  T  T  X - (iI. closure (U i))"
      by auto
  qed
  done

lemma closure_subtopology_minimal:
  "S  T  closedin (top_of_set X) T  closure S  X  T"
  apply (auto simp: closedin_closed)
  using closure_minimal by blast

lemma locally_finite_on_closure_Union:
  "(iI. closure (U i)) = closure (iI. (U i))  X"
  if "locally_finite_on X I U" "i. i  I  closure (U i)  X"
proof (rule antisym)
  show "(iI. closure (U i))  closure (iI. U i)  X"
    using that
    apply auto
    by (metis (no_types, lifting) SUP_le_iff closed_closure closure_minimal closure_subset subsetCE)
  show "closure (iI. U i)  X  (iI. closure (U i))"
    apply (rule closure_subtopology_minimal)
    apply auto
    using that
    by (auto intro!: locally_finite_on_closedin_Union_closure)
qed


subsection ‹Refinement of cover›

definition refines :: "'a set set  'a set set  bool" (infix "refines" 50)
  where "A refines B  (sA. (t. t  B  s  t))"

lemma refines_subset: "x refines y" if "z refines y" "x  z"
  using that by (auto simp: refines_def)

subsection ‹Functions as vector space›

instantiation "fun" :: (type, scaleR) scaleR begin

definition scaleR_fun :: "real  ('a  'b)  'a  'b" where
  "scaleR_fun r f = (λx. r *R f x)"

lemma scaleR_fun_beta[simp]: "(r *R f) x = r *R f x"
  by (simp add: scaleR_fun_def)

instance ..

end

instance "fun" :: (type, real_vector) real_vector
  by standard (auto simp: scaleR_fun_def algebra_simps)


subsection ‹Additional lemmas›

lemmas [simp del] = vimage_Un vimage_Int

lemma finite_Collect_imageI: "finite {U  f ` X. P U}" if "finite {xX. P (f x)}"
proof -
  have "{d  f ` X. P d}  f ` {c  X. P (f c)}"
    by blast
  then show ?thesis
    using finite_surj that by blast
qed

lemma plus_compose: "(x + y)  f = (x  f) + (y  f)"
  by auto

lemma mult_compose: "(x * y)  f = (x  f) * (y  f)"
  by auto

lemma scaleR_compose: "(c *R x)  f = c *R (x  f)"
  by (auto simp:)

lemma image_scaleR_ball:
  fixes a :: "'a::real_normed_vector"
  shows "c  0  (*R) c ` ball a r = ball (c *R a) (abs c *R r)"
proof (auto simp: mem_ball dist_norm, goal_cases)
  case (1 b)
  have "norm (c *R a - c *R b)  = abs c * norm (a - b)"
    by (auto simp: norm_scaleR[symmetric] algebra_simps simp del: norm_scaleR)
  also have " < abs c * r"
    apply (rule mult_strict_left_mono)
    using 1 by auto
  finally show ?case .
next
  case (2 x)
  have "norm (a - x /R c) < r"
  proof -
    have "norm (a - x /R c) = abs c *R norm (a - x /R c) /R abs c"
      using 2 by auto
    also have "abs c *R norm (a - x /R c) = norm (c *R a - x)"
      using 2
      by (auto simp: norm_scaleR[symmetric] algebra_simps simp del: norm_scaleR)
    also have " < ¦c¦ * r"
      by fact
    also have "¦c¦ * r /R ¦c¦ = r" using 2 by auto
    finally show ?thesis using 2 by auto
  qed
  then have xdc: "x /R c  ball a r"
    by (auto simp: mem_ball dist_norm)
  show ?case
    apply (rule image_eqI[OF _ xdc])
    using 2 by simp
qed


subsection ‹Continuity›

lemma continuous_within_topologicalE:
  assumes "continuous (at x within s) f"
    "open B" "f x  B"
  obtains A where "open A" "x  A" "y. y  s  y  A  f y  B"
  using assms continuous_within_topological by metis

lemma continuous_within_topologicalE':
  assumes "continuous (at x) f"
    "open B" "f x  B"
  obtains A where "open A" "x  A" "f ` A  B"
  using assms continuous_within_topologicalE[OF assms]
  by (metis UNIV_I image_subsetI)

lemma continuous_on_inverse: "continuous_on S f  0  f ` S  continuous_on S (λx. inverse (f x))"
  for f::"__::real_normed_div_algebra"
  by (auto simp: continuous_on_def intro!: tendsto_inverse)


subsection @{term "(has_derivative)"}

lemma has_derivative_plus_fun[derivative_intros]:
  "(x + y has_derivative x' + y') (at a within A)"
  if [derivative_intros]:
    "(x has_derivative x') (at a within A)"
    "(y has_derivative y') (at a within A)"
  by (auto simp: plus_fun_def intro!: derivative_eq_intros)

lemma has_derivative_scaleR_fun[derivative_intros]:
  "(x *R y has_derivative x *R y') (at a within A)"
  if [derivative_intros]:
    "(y has_derivative y') (at a within A)"
  by (auto simp: scaleR_fun_def intro!: derivative_eq_intros)

lemma has_derivative_times_fun[derivative_intros]:
  "(x * y has_derivative (λh. x a * y' h + x' h * y a)) (at a within A)"
  if [derivative_intros]:
    "(x has_derivative x') (at a within A)"
    "(y has_derivative y') (at a within A)"
  for x y::"_'a::real_normed_algebra"
  by (auto simp: times_fun_def intro!: derivative_eq_intros)

lemma real_sqrt_has_derivative_generic:
  "x  0  (sqrt has_derivative (*) ((if x > 0 then 1 else -1) * inverse (sqrt x) / 2)) (at x within S)"
  apply (rule has_derivative_at_withinI)
  using DERIV_real_sqrt_generic[of x "(if x > 0 then 1 else -1) * inverse (sqrt x) / 2"] at_within_open[of x "UNIV - {0}"]
  by (auto simp: has_field_derivative_def open_delete ac_simps split: if_splits)

lemma sqrt_has_derivative:
  "((λx. sqrt (f x)) has_derivative (λxa. (if 0 < f x then 1 else - 1) / (2 * sqrt (f x)) * f' xa)) (at x within S)"
  if "(f has_derivative f') (at x within S)" "f x  0"
  by (rule has_derivative_eq_rhs[OF has_derivative_compose[OF that(1) real_sqrt_has_derivative_generic, OF that(2)]])
    (auto simp: divide_simps)

lemmas has_derivative_norm_compose[derivative_intros] = has_derivative_compose[OF _ has_derivative_norm]

subsection ‹Differentiable›

lemmas differentiable_on_empty[simp]

lemma differentiable_transform_eventually: "f differentiable (at x within X)"
  if "g differentiable (at x within X)"
    "f x = g x"
    "F x in (at x within X). f x = g x"
  using that
  apply (auto simp: differentiable_def)
  subgoal for D
    apply (rule exI[where x=D])
    apply (auto simp: has_derivative_within)
    by (simp add: eventually_mono Lim_transform_eventually)
  done

lemma differentiable_within_eqI: "f differentiable at x within X"
  if "g differentiable at x within X" "x. x  X  f x = g x"
    "x  X" "open X"
  apply (rule differentiable_transform_eventually)
    apply (rule that)
   apply (auto simp: that)
proof -
  have "F x in at x within X. x  X"
    using ‹open X
    using eventually_at_topological by blast
  then show " F x in at x within X. f x = g x"
    by eventually_elim (auto simp: that)
qed

lemma differentiable_eqI: "f differentiable at x"
  if "g differentiable at x" "x. x  X  f x = g x" "x  X" "open X"
  using that
  unfolding at_within_open[OF that(3,4), symmetric]
  by (rule differentiable_within_eqI)

lemma differentiable_on_eqI:
  "f differentiable_on S"
  if "g differentiable_on S" "x. x  S  f x = g x" "open S"
  using that differentiable_eqI[of g _ S f]
  by (auto simp: differentiable_on_eq_differentiable_at)

lemma differentiable_on_comp: "(f o g) differentiable_on S"
  if "g differentiable_on S" "f differentiable_on (g ` S)"
  using that
  by (auto simp: differentiable_on_def intro: differentiable_chain_within)

lemma differentiable_on_comp2: "(f o g) differentiable_on S"
  if  "f differentiable_on T" "g differentiable_on S" "g ` S  T"
  apply (rule differentiable_on_comp)
   apply (rule that)
  apply (rule differentiable_on_subset)
  apply (rule that)
  apply (rule that)
  done

lemmas differentiable_on_compose2 = differentiable_on_comp2[unfolded o_def]

lemma differentiable_on_openD: "f differentiable at x"
  if "f differentiable_on X" "open X" "x  X"
  using differentiable_on_eq_differentiable_at that by blast

lemma differentiable_on_add_fun[intro, simp]:
  "x differentiable_on UNIV  y differentiable_on UNIV  x + y differentiable_on UNIV"
  by (auto simp: plus_fun_def)

lemma differentiable_on_mult_fun[intro, simp]:
  "x differentiable_on UNIV  y differentiable_on UNIV  x * y differentiable_on UNIV"
  for x y::"_'a::real_normed_algebra"
  by (auto simp: times_fun_def)

lemma differentiable_on_scaleR_fun[intro, simp]:
  "y differentiable_on UNIV  x *R y differentiable_on UNIV"
  by (auto simp: scaleR_fun_def)

lemma sqrt_differentiable:
  "(λx. sqrt (f x)) differentiable (at x within S)"
  if "f differentiable (at x within S)" "f x  0"
  using that
  using sqrt_has_derivative[of f _ x S]
  by (auto simp: differentiable_def)

lemma sqrt_differentiable_on: "(λx. sqrt (f x)) differentiable_on S"
  if "f differentiable_on S" "0  f ` S"
  using sqrt_differentiable[of f _ S] that
  by (force simp: differentiable_on_def)

lemma differentiable_on_inverse: "f differentiable_on S  0  f ` S  (λx. inverse (f x)) differentiable_on S"
  for f::"__::real_normed_field"
  by (auto simp: differentiable_on_def intro!: differentiable_inverse)

lemma differentiable_on_openI:
  "f differentiable_on S"
  if "open S" "x. x  S  f'. (f has_derivative f') (at x)"
  using that
  by (auto simp: differentiable_on_def at_within_open[where S=S] differentiable_def)

lemmas differentiable_norm_compose_at = differentiable_compose[OF differentiable_norm_at]

lemma differentiable_on_Pair:
  "f differentiable_on S  g differentiable_on S  (λx. (f x, g x)) differentiable_on S"
  unfolding differentiable_on_def
  using differentiable_Pair[of f _ S g] by auto

lemma differentiable_at_fst:
  "(λx. fst (f x)) differentiable at x within X" if "f differentiable at x within X"
  using that
  by (auto simp: differentiable_def dest!: has_derivative_fst)

lemma differentiable_at_snd:
  "(λx. snd (f x)) differentiable at x within X" if "f differentiable at x within X"
  using that
  by (auto simp: differentiable_def dest!: has_derivative_snd)

lemmas frechet_derivative_worksI = frechet_derivative_works[THEN iffD1]

lemma sin_differentiable_at: "(λx. sin (f x::real)) differentiable at x within X"
  if "f differentiable at x within X"
  using differentiable_def has_derivative_sin that by blast

lemma cos_differentiable_at: "(λx. cos (f x::real)) differentiable at x within X"
  if "f differentiable at x within X"
  using differentiable_def has_derivative_cos that by blast


subsection ‹Frechet derivative›

lemmas frechet_derivative_transform_within_open_ext =
  fun_cong[OF frechet_derivative_transform_within_open]

lemmas frechet_derivative_at' = frechet_derivative_at[symmetric]

lemma frechet_derivative_plus_fun:
  "x differentiable at a  y differentiable at a 
  frechet_derivative (x + y) (at a) =
    frechet_derivative x (at a) + frechet_derivative y (at a)"
  by (rule frechet_derivative_at')
    (auto intro!: derivative_eq_intros frechet_derivative_worksI)

lemmas frechet_derivative_plus = frechet_derivative_plus_fun[unfolded plus_fun_def]

lemma frechet_derivative_zero_fun: "frechet_derivative 0 (at a) = 0"
  by (auto simp: frechet_derivative_const zero_fun_def)

lemma frechet_derivative_sin:
  "frechet_derivative (λx. sin (f x)) (at x) = (λxa. frechet_derivative f (at x) xa * cos (f x))"
  if "f differentiable (at x)"
  for f::"_real"
  by (rule frechet_derivative_at'[OF has_derivative_sin[OF frechet_derivative_worksI[OF that]]])

lemma frechet_derivative_cos:
  "frechet_derivative (λx. cos (f x)) (at x) = (λxa. frechet_derivative f (at x) xa * - sin (f x))"
  if "f differentiable (at x)"
  for f::"_real"
  by (rule frechet_derivative_at'[OF has_derivative_cos[OF frechet_derivative_worksI[OF that]]])

lemma differentiable_sum_fun:
  "(i. i  I  (f i differentiable at a))  sum f I differentiable at a"
  by (induction I rule: infinite_finite_induct) (auto simp: zero_fun_def plus_fun_def)

lemma frechet_derivative_sum_fun:
  "(i. i  I  (f i differentiable at a)) 
  frechet_derivative (iI. f i) (at a) = (iI. frechet_derivative (f i) (at a))"
  by (induction I rule: infinite_finite_induct)
    (auto simp: frechet_derivative_zero_fun frechet_derivative_plus_fun differentiable_sum_fun)

lemma sum_fun_def: "(iI. f i) = (λx. iI. f i x)"
  by (induction I rule: infinite_finite_induct) auto

lemmas frechet_derivative_sum = frechet_derivative_sum_fun[unfolded sum_fun_def]


lemma frechet_derivative_times_fun:
  "f differentiable at a  g differentiable at a 
  frechet_derivative (f * g) (at a) =
  (λx. f a * frechet_derivative g (at a) x + frechet_derivative f (at a) x * g a)"
  for f g::"_'a::real_normed_algebra"
  by (rule frechet_derivative_at') (auto intro!: derivative_eq_intros frechet_derivative_worksI)

lemmas frechet_derivative_times = frechet_derivative_times_fun[unfolded times_fun_def]

lemma frechet_derivative_scaleR_fun:
  "y differentiable at a 
  frechet_derivative (x *R y) (at a) =
    x *R frechet_derivative y (at a)"
  by (rule frechet_derivative_at')
    (auto intro!: derivative_eq_intros frechet_derivative_worksI)

lemmas frechet_derivative_scaleR = frechet_derivative_scaleR_fun[unfolded scaleR_fun_def]

lemma frechet_derivative_compose:
  "frechet_derivative (f o g) (at x) = frechet_derivative (f) (at (g x)) o frechet_derivative g (at x)"
  if "g differentiable at x" "f differentiable at (g x)"
  by (meson diff_chain_at frechet_derivative_at' frechet_derivative_works that)

lemma frechet_derivative_compose_eucl:
  "frechet_derivative (f o g) (at x) =
    (λv. iBasis. ((frechet_derivative g (at x) v)  i) *R frechet_derivative f (at (g x)) i)"
  (is "?l = ?r")
  if "g differentiable at x" "f differentiable at (g x)"
proof (rule ext)
  fix v
  interpret g: linear "frechet_derivative g (at x)"
    using that(1)
    by (rule linear_frechet_derivative)
  interpret f: linear "frechet_derivative f (at (g x))"
    using that(2)
    by (rule linear_frechet_derivative)
  have "frechet_derivative (f o g) (at x) v =
    frechet_derivative f (at (g x)) (iBasis. (frechet_derivative g (at x) v  i) *R i)"
    unfolding frechet_derivative_compose[OF that] o_apply
    by (simp add: euclidean_representation)
  also have " = ?r v"
    by (auto simp: g.sum g.scaleR f.sum f.scaleR)
  finally show "?l v = ?r v" .
qed


lemma frechet_derivative_works_on_open:
  "f differentiable_on X  open X  x  X 
    (f has_derivative frechet_derivative f (at x)) (at x)"
  and frechet_derivative_works_on:
  "f differentiable_on X  x  X 
    (f has_derivative frechet_derivative f (at x within X)) (at x within X)"
  by (auto simp: differentiable_onD differentiable_on_openD frechet_derivative_worksI)

lemma frechet_derivative_inverse: "frechet_derivative (λx. inverse (f x)) (at x) =
    (λh. - 1 / (f x)2 * frechet_derivative f (at x) h)"
  if "f differentiable at x" "f x  0" for f::"__::real_normed_field"
  apply (rule frechet_derivative_at')
  using that
  by (auto intro!: derivative_eq_intros frechet_derivative_worksI
      simp: divide_simps algebra_simps power2_eq_square)

lemma frechet_derivative_sqrt: "frechet_derivative (λx. sqrt (f x)) (at x) =
  (λv. (if f x > 0 then 1 else -1) / (2 * sqrt (f x)) * frechet_derivative f (at x) v)"
  if "f differentiable at x" "f x  0" 
  apply (rule frechet_derivative_at')
  apply (rule sqrt_has_derivative[THEN has_derivative_eq_rhs])
  by (auto intro!: frechet_derivative_worksI that simp: divide_simps)

lemma frechet_derivative_norm: "frechet_derivative (λx. norm (f x)) (at x) =
    (λv. frechet_derivative f (at x) v  sgn (f x))"
  if "f differentiable at x" "f x  0" 
  for f::"__::real_inner"
  apply (rule frechet_derivative_at')
  by (auto intro!: derivative_eq_intros frechet_derivative_worksI that simp: divide_simps)

lemma (in bounded_linear) frechet_derivative:
  "frechet_derivative f (at x) = f"
  apply (rule frechet_derivative_at')
  apply (rule has_derivative_eq_rhs)
   apply (rule has_derivative)
  by (auto intro!: derivative_eq_intros)

bundle no_matrix_mult begin
no_notation matrix_matrix_mult (infixl "**" 70)
end

lemma (in bounded_bilinear) frechet_derivative:
  includes no_matrix_mult
  shows
    "x differentiable at a  y differentiable at a 
      frechet_derivative (λa. x a ** y a) (at a) =
        (λh. x a ** frechet_derivative y (at a) h + frechet_derivative x (at a) h ** y a)"
  by (rule frechet_derivative_at') (auto intro!: FDERIV frechet_derivative_worksI)

lemma frechet_derivative_divide: "frechet_derivative (λx. f x / g x) (at x) =
    (λh. frechet_derivative f (at x) h / (g x) -frechet_derivative g (at x) h * f x / (g x)2)"
  if "f differentiable at x" "g differentiable at x" "g x  0" for f::"__::real_normed_field"
  using that
  by (auto simp: divide_inverse_commute bounded_bilinear.frechet_derivative[OF bounded_bilinear_mult]
      frechet_derivative_inverse)

lemma frechet_derivative_pair:
  "frechet_derivative (λx. (f x, g x)) (at x) = (λv. (frechet_derivative f (at x) v, frechet_derivative g (at x) v))"
  if "f differentiable (at x)" "g differentiable (at x)"
  apply (rule frechet_derivative_at')
  apply (rule derivative_eq_intros)
    apply (rule frechet_derivative_worksI) apply fact    
    apply (rule frechet_derivative_worksI) apply fact
  ..

lemma frechet_derivative_fst:
  "frechet_derivative (λx. fst (f x)) (at x) = (λxa. fst (frechet_derivative f (at x) xa))"
  if "(f differentiable at x)"
  for f::"_(_::real_normed_vector × _::real_normed_vector)"
  apply (rule frechet_derivative_at')
  using that
  by (auto intro!: derivative_eq_intros frechet_derivative_worksI)

lemma frechet_derivative_snd:
  "frechet_derivative (λx. snd (f x)) (at x) = (λxa. snd (frechet_derivative f (at x) xa))"
  if "(f differentiable at x)"
  for f::"_(_::real_normed_vector × _::real_normed_vector)"
  apply (rule frechet_derivative_at')
  using that
  by (auto intro!: derivative_eq_intros frechet_derivative_worksI)

lemma frechet_derivative_eq_vector_derivative_1:
  assumes "f differentiable at t"
  shows "frechet_derivative f (at t) 1 = vector_derivative f (at t)"
  apply (subst frechet_derivative_eq_vector_derivative)
   apply (rule assms) by auto


subsection ‹Linear algebra›

lemma (in vector_space) dim_pos_finite_dimensional_vector_spaceE:
  assumes "dim (UNIV::'b set) > 0"
  obtains basis where "finite_dimensional_vector_space scale basis"
proof -
  from assms obtain b where b: "local.span b = local.span UNIV" "local.independent b"
    by (auto simp: dim_def split: if_splits)
  then have "dim UNIV = card b"
    by (rule dim_eq_card)
  with assms have "finite b" by (auto simp: card_ge_0_finite)
  then have "finite_dimensional_vector_space scale b"
    by unfold_locales (auto simp: b)
  then show ?thesis ..
qed

context vector_space_on begin

context includes lifting_syntax assumes "(Rep::'s  'b) (Abs::'b  's). type_definition Rep Abs S" begin

interpretation local_typedef_vector_space_on S scale "TYPE('s)" by unfold_locales fact

lemmas_with [var_simplified explicit_ab_group_add,
    unoverload_type 'd,
    OF type.ab_group_add_axioms type_vector_space_on_with,
    folded dim_S_def,
    untransferred,
    var_simplified implicit_ab_group_add]:
    lt_dim_pos_finite_dimensional_vector_spaceE = vector_space.dim_pos_finite_dimensional_vector_spaceE

end

lemmas_with [cancel_type_definition,
    OF S_ne,
    folded subset_iff',
    simplified pred_fun_def, folded finite_dimensional_vector_space_on_with,
    simplified―‹too much?›]:
    dim_pos_finite_dimensional_vector_spaceE = lt_dim_pos_finite_dimensional_vector_spaceE

end


subsection ‹Extensional function space›

text ‹f is zero outside A. We use such functions to canonically represent
  functions whose domain is A›
definition extensional0 :: "'a set  ('a  'b::zero)  bool"
  where "extensional0 A f = (x. x  A  f x = 0)"

lemma extensional0_0[intro, simp]: "extensional0 X 0"
  by (auto simp: extensional0_def)

lemma extensional0_UNIV[intro, simp]: "extensional0 UNIV f"
  by (auto simp: extensional0_def)

lemma ext_extensional0:
  "f = g" if "extensional0 S f" "extensional0 S g" "x. x  S  f x = g x"
  using that by (force simp: extensional0_def fun_eq_iff)

lemma extensional0_add[intro, simp]:
  "extensional0 S f  extensional0 S g  extensional0 S (f + g::_'a::comm_monoid_add)"
  by (auto simp: extensional0_def)

lemma extensinoal0_mult[intro, simp]:
  "extensional0 S x  extensional0 S y  extensional0 S (x * y)"
  for x y::"_'a::mult_zero"
  by (auto simp: extensional0_def)

lemma extensional0_scaleR[intro, simp]: "extensional0 S f  extensional0 S (c *R f::_'a::real_vector)"
  by (auto simp: extensional0_def)

lemma extensional0_outside: "x  S  extensional0 S f  f x = 0"
  by (auto simp: extensional0_def)

lemma subspace_extensional0: "subspace (Collect (extensional0 X))"
  by (auto simp: subspace_def)

text ‹Send the function f to its canonical representative as a function with domain A›
definition restrict0 :: "'a set  ('a  'b::zero)  'a  'b"
  where "restrict0 A f x = (if x  A then f x else 0)"

lemma restrict0_UNIV[simp]: "restrict0 UNIV = (λx. x)"
  by (intro ext) (auto simp: restrict0_def)

lemma extensional0_restrict0[intro, simp]: "extensional0 A (restrict0 A f)"
  by (auto simp: extensional0_def restrict0_def)

lemma restrict0_times: "restrict0 A (x * y) = restrict0 A x * restrict0 A y"
  for x::"'a'b::mult_zero"
  by (auto simp: restrict0_def[abs_def])

lemma restrict0_apply_in[simp]: "x  A  restrict0 A f x = f x"
  by (auto simp: restrict0_def)

lemma restrict0_apply_out[simp]: "x  A  restrict0 A f x = 0"
  by (auto simp: restrict0_def)

lemma restrict0_scaleR: "restrict0 A (c *R f::_'a::real_vector) = c *R restrict0 A f"
  by (auto simp: restrict0_def[abs_def])

lemma restrict0_add: "restrict0 A (f + g::_'a::real_vector) = restrict0 A f + restrict0 A g"
  by (auto simp: restrict0_def[abs_def])

lemma restrict0_restrict0: "restrict0 X (restrict0 Y f) = restrict0 (X  Y) f"
  by (auto simp: restrict0_def)

end

Theory Smooth

section ‹Smooth Functions between Normed Vector Spaces›
theory Smooth
  imports
    Analysis_More
begin

subsection ‹From/To Multivariate_Taylor.thy›

lemma multivariate_Taylor_integral:
  fixes f::"'a::real_normed_vector  'b::banach"
    and Df::"'a  nat  'a  'b"
  assumes "n > 0"
  assumes Df_Nil: "a x. Df a 0 H = f a"
  assumes Df_Cons: "a i d. a  closed_segment X (X + H)  i < n 
      ((λa. Df a i H) has_derivative (Df a (Suc i))) (at a within G)"
  assumes cs: "closed_segment X (X + H)  G"
  defines "i  λx.
      ((1 - x) ^ (n - 1) / fact (n - 1)) *R Df (X + x *R H) n H"
  shows multivariate_Taylor_has_integral:
    "(i has_integral f (X + H) - (i<n. (1 / fact i) *R Df X i H)) {0..1}"
  and multivariate_Taylor:
    "f (X + H) = (i<n. (1 / fact i) *R Df X i H) + integral {0..1} i"
  and multivariate_Taylor_integrable:
    "i integrable_on {0..1}"
proof goal_cases
  case 1
  let ?G = "closed_segment X (X + H)"
  define line where "line t = X + t *R H" for t
  have segment_eq: "closed_segment X (X + H) = line ` {0 .. 1}"
    by (auto simp: line_def closed_segment_def algebra_simps)
  have line_deriv: "x. (line has_derivative (λt. t *R H)) (at x)"
    by (auto intro!: derivative_eq_intros simp: line_def [abs_def])
  define g where "g = f o line"
  define Dg where "Dg n t = Df (line t) n H" for n :: nat and t :: real
  note n > 0
  moreover
  have Dg0: "Dg 0 = g" by (auto simp add: Dg_def Df_Nil g_def)
  moreover
  have DgSuc: "(Dg m has_vector_derivative Dg (Suc m) t) (at t within {0..1})"
    if "m < n" "0  t" "t  1" for m::nat and t::real
  proof -
    from that have [intro]: "line t  ?G" using assms
      by (auto simp: segment_eq)
    note [derivative_intros] = has_derivative_in_compose[OF _ has_derivative_subset[OF Df_Cons]]
    interpret Df: linear "(λd. Df (line t) (Suc m) d)"
      by (auto intro!: has_derivative_linear derivative_intros m < n)
    note [derivative_intros] =
      has_derivative_compose[OF _ line_deriv]
    show ?thesis
      using Df.scaleR m < n
      by (auto simp: Dg_def [abs_def] has_vector_derivative_def g_def segment_eq
         intro!: derivative_eq_intros subsetD[OF cs])
  qed
  ultimately
  have g_Taylor: "(i has_integral g 1 - (i<n. ((1 - 0) ^ i / fact i) *R Dg i 0)) {0 .. 1}"
    unfolding i_def Dg_def [abs_def] line_def
    by (rule Taylor_has_integral) auto
  then show c: ?case using n > 0 by (auto simp: g_def line_def Dg_def)
  case 2 show ?case using c
    by (simp add: integral_unique add.commute)
  case 3 show ?case using c by force
qed


subsection ‹Higher-order differentiable›

fun higher_differentiable_on ::
  "'a::real_normed_vector set  ('a  'b::real_normed_vector)  nat  bool" where
  "higher_differentiable_on S f 0  continuous_on S f"
| "higher_differentiable_on S f (Suc n) 
   (xS. f differentiable (at x)) 
   (v. higher_differentiable_on S (λx. frechet_derivative f (at x) v) n)"

lemma ball_differentiable_atD: "xS. f differentiable at x  f differentiable_on S"
  by (auto simp: differentiable_on_def differentiable_at_withinI)

lemma higher_differentiable_on_imp_continuous_on:
  "continuous_on S f" if "higher_differentiable_on S f n"
  using that
  by (cases n) (auto simp: differentiable_imp_continuous_on ball_differentiable_atD)

lemma higher_differentiable_on_imp_differentiable_on:
  "f differentiable_on S" if "higher_differentiable_on S f k" "k > 0"
  using that
  by (cases k) (auto simp: ball_differentiable_atD)

lemma higher_differentiable_on_congI:
  assumes "open S" "higher_differentiable_on S g n"
    and "x. x  S  f x = g x"
  shows "higher_differentiable_on S f n"
  using assms(2,3)
proof (induction n arbitrary: f g)
  case 0
  then show ?case by auto
next
  case (Suc n)
  have 1: "xS. g differentiable (at x)" and
       2: "higher_differentiable_on S (λx. frechet_derivative g (at x) v) n" for v
    using Suc by auto
  have 3: "xS. f differentiable (at x)" using 1 Suc(3) assms(1)
    by (metis differentiable_eqI) 
  have 4: "frechet_derivative f (at x) v = frechet_derivative g (at x) v" if "x  S" for x v
    using "3" Suc.prems(2) assms(1) frechet_derivative_transform_within_open_ext that by blast
  from 2 3 4 show ?case
    using Suc.IH[OF 2 4] by auto
qed

lemma higher_differentiable_on_cong:
  assumes "open S" "S = T"
    and "x. x  T  f x = g x"
  shows "higher_differentiable_on S f n  higher_differentiable_on T g n"
  using higher_differentiable_on_congI assms by auto


lemma higher_differentiable_on_SucD:
  "higher_differentiable_on S f n" if "higher_differentiable_on S f (Suc n)"
  using that
  by (induction n arbitrary: f) (auto simp: differentiable_imp_continuous_on ball_differentiable_atD)

lemma higher_differentiable_on_addD:
  "higher_differentiable_on S f n" if "higher_differentiable_on S f (n + m)"
  using that
  by (induction m arbitrary: f n)
    (auto simp del: higher_differentiable_on.simps dest!: higher_differentiable_on_SucD)

lemma higher_differentiable_on_le:
  "higher_differentiable_on S f n" if "higher_differentiable_on S f m" "n  m"
  using higher_differentiable_on_addD[of S f n "m - n"] that
  by auto

lemma higher_differentiable_on_open_subsetsI:
  "higher_differentiable_on S f n"
  if "x. x  S  T. x  T  open T  higher_differentiable_on T f n"
  using that
proof (induction n arbitrary: f)
  case 0
  show ?case
    by (force simp: continuous_on_def
        dest!: 0
        dest: at_within_open
        intro!: Lim_at_imp_Lim_at_within[where S=S])
next
  case (Suc n)
  have "f differentiable at x" if "x  S" for x
    using Suc.prems[OF x  S]
    by (auto simp: differentiable_on_def dest: at_within_open dest!: bspec)
  then have "f differentiable_on S"
    by (auto simp: differentiable_on_def intro!: differentiable_at_withinI[where s=S])
  with Suc show ?case
    by fastforce
qed

lemma higher_differentiable_on_const: "higher_differentiable_on S (λx. c) n"
  by (induction n arbitrary: c) (auto simp: continuous_intros frechet_derivative_const)

lemma higher_differentiable_on_id: "higher_differentiable_on S (λx. x) n"
  by (cases n) (auto simp: frechet_derivative_works higher_differentiable_on_const)

lemma higher_differentiable_on_add:
  "higher_differentiable_on S (λx. f x + g x) n"
  if "higher_differentiable_on S f n"
    "higher_differentiable_on S g n"
    "open S"
  using that
proof (induction n arbitrary: f g)
  case 0
  then show ?case by (auto intro!: continuous_intros)
next
  case (Suc n)
  from Suc.prems have
    f: "x. xS  f differentiable (at x)"
    and hf: "higher_differentiable_on S (λx. frechet_derivative f (at x) v) n"
    and g: "x. xS  g differentiable (at x)"
    and hg: "higher_differentiable_on S (λx. frechet_derivative g (at x) v) n"
    for v
    by auto
  show ?case
    using f g ‹open S
    by (auto simp: frechet_derivative_plus
        intro!: derivative_intros f g Suc.IH hf hg
        cong: higher_differentiable_on_cong)
qed

lemma (in bounded_bilinear) differentiable:
 "(λx. prod (f x) (g x)) differentiable at x within S" 
 if "f differentiable at x within S"
    "g differentiable at x within S"
  by (blast intro: differentiableI frechet_derivative_worksI that FDERIV)


context begin
private lemmas d = bounded_bilinear.differentiable
lemmas differentiable_inner = bounded_bilinear_inner[THEN d]
   and differentiable_scaleR = bounded_bilinear_scaleR[THEN d]
   and differentiable_mult = bounded_bilinear_mult[THEN d]
end

lemma (in bounded_bilinear) differentiable_on:
  "(λx. prod (f x) (g x)) differentiable_on S"
  if "f differentiable_on S" "g differentiable_on S"
  using that by (auto simp: differentiable_on_def differentiable)

context begin
private lemmas do = bounded_bilinear.differentiable_on
lemmas differentiable_on_inner = bounded_bilinear_inner[THEN do]
   and differentiable_on_scaleR = bounded_bilinear_scaleR[THEN do]
   and differentiable_on_mult = bounded_bilinear_mult[THEN do]
end

lemma (in bounded_bilinear) higher_differentiable_on:
  "higher_differentiable_on S (λx. prod (f x) (g x)) n"
  if
    "higher_differentiable_on S f n"
    "higher_differentiable_on S g n"
    "open S"
  using that
proof (induction n arbitrary: f g)
  case 0
  then show ?case by (auto intro!: continuous_intros continuous_on)
next
  case (Suc n)
  from Suc.prems have
    f: "x. xS  f differentiable (at x)"
    and hf: "higher_differentiable_on S (λx. frechet_derivative f (at x) v) n"
    and g: "x. xS  g differentiable (at x)"
    and hg: "higher_differentiable_on S (λx. frechet_derivative g (at x) v) n"
    for v
    by auto
  show ?case
    using f g ‹open S Suc
    by (auto simp: frechet_derivative
        intro!: derivative_intros f g differentiable higher_differentiable_on_add Suc.IH
        intro: higher_differentiable_on_SucD
        cong: higher_differentiable_on_cong)
qed

context begin
private lemmas hdo = bounded_bilinear.higher_differentiable_on
lemmas higher_differentiable_on_inner = bounded_bilinear_inner[THEN hdo]
   and higher_differentiable_on_scaleR = bounded_bilinear_scaleR[THEN hdo]
   and higher_differentiable_on_mult = bounded_bilinear_mult[THEN hdo]
end

lemma higher_differentiable_on_sum:
  "higher_differentiable_on S (λx. iF. f i x) n"
  if "i. i  F  finite F  higher_differentiable_on S (f i) n" "open S"
  using that
  by (induction F rule: infinite_finite_induct)
    (auto intro!: higher_differentiable_on_const higher_differentiable_on_add)

lemma higher_differentiable_on_subset:
  "higher_differentiable_on S f n"
  if "higher_differentiable_on T f n" "S  T"
  using that
  by (induction n arbitrary: f) (auto intro: continuous_on_subset differentiable_on_subset)

lemma higher_differentiable_on_compose:
  "higher_differentiable_on S (f o g) n"
  if "higher_differentiable_on T f n" "higher_differentiable_on S g n"  "g ` S  T" "open S" "open T"
  for g::"__::euclidean_space"―‹TODO: can we get around this restriction?›
  using that(1-3)
proof (induction n arbitrary: f g)
  case 0
  then show ?case using that(4-)
    by (auto simp: continuous_on_compose2)
next
  case (Suc n)
  from Suc.prems
  have
    f: "x. x  T  f differentiable (at x)"
    and g: "x. x  S  g differentiable (at x)"
    and hf: "higher_differentiable_on T (λx. frechet_derivative f (at x) v) n"
    and hg: "higher_differentiable_on S (λx. frechet_derivative g (at x) w) n"
    for v w
    by auto
  show ?case
    using g ` _  _ ‹open S f g ‹open T Suc
      Suc.IH[where f="λx. frechet_derivative f (at x) v"
        and g = "λx. g x" for v, unfolded o_def]
      higher_differentiable_on_SucD[OF Suc.prems(2)]
    by (auto
        simp: frechet_derivative_compose_eucl subset_iff
        simp del: o_apply
        intro!: differentiable_chain_within higher_differentiable_on_sum
          higher_differentiable_on_scaleR higher_differentiable_on_inner
          higher_differentiable_on_const
        intro: differentiable_at_withinI
        cong: higher_differentiable_on_cong)
qed


lemma higher_differentiable_on_uminus:
  "higher_differentiable_on S (λx. - f x) n"
  if "higher_differentiable_on S f n" "open S"
  using higher_differentiable_on_scaleR[of S "λx. -1" n f] that
  by (auto simp: higher_differentiable_on_const)

lemma higher_differentiable_on_minus:
  "higher_differentiable_on S (λx. f x - g x) n"
  if "higher_differentiable_on S f n"
    "higher_differentiable_on S g n"
    "open S"
  using higher_differentiable_on_add[OF _ higher_differentiable_on_uminus, OF that(1,2,3,3)]
  by simp

lemma higher_differentiable_on_inverse:
  "higher_differentiable_on S (λx. inverse (f x)) n"
  if "higher_differentiable_on S f n" "0  f ` S" "open S"
  for f::"__::real_normed_field"
  using that
proof (induction n arbitrary: f)
  case 0
  then show ?case by (auto simp: continuous_on_inverse)
next
  case (Suc n)
  from Suc.prems(1) have fn: "higher_differentiable_on S f n" by (rule higher_differentiable_on_SucD)
  from Suc show ?case
    by (auto simp: continuous_on_inverse image_iff power2_eq_square
        frechet_derivative_inverse divide_inverse
        intro!: differentiable_inverse higher_differentiable_on_uminus higher_differentiable_on_mult
          Suc.IH fn
        cong: higher_differentiable_on_cong)
qed

lemma higher_differentiable_on_divide:
  "higher_differentiable_on S (λx. f x / g x) n"
  if
    "higher_differentiable_on S f n"
    "higher_differentiable_on S g n"
    "x. x  S  g x  0"
    "open S"
  for f::"__::real_normed_field"
  using higher_differentiable_on_mult[OF _ higher_differentiable_on_inverse, OF that(1,2) _ that(4,4)]
    that(3)
  by (auto simp: divide_inverse image_iff)

lemma differentiable_on_open_Union:
  "f differentiable_on S"
  if "s. s  S  f differentiable_on s"
    "s. s  S  open s"
  using that
  unfolding differentiable_on_def
  by (metis Union_iff at_within_open open_Union)

lemma higher_differentiable_on_open_Union: "higher_differentiable_on (S) f n"
  if "s. s  S  higher_differentiable_on s f n"
    "s. s  S  open s"
  using that
proof (induction n arbitrary: f)
  case 0
  then show ?case by (auto simp: continuous_on_open_Union)
next
  case (Suc n)
  then show ?case
    by (auto simp: differentiable_on_open_Union)
qed

lemma differentiable_on_open_Un:
  "f differentiable_on S  T"
  if "f differentiable_on S"
    "f differentiable_on T"
    "open S" "open T"
  using that differentiable_on_open_Union[of "{S, T}" f]
  by auto

lemma higher_differentiable_on_open_Un: "higher_differentiable_on (S  T) f n"
  if "higher_differentiable_on S f n"
    "higher_differentiable_on T f n"
    "open S" "open T"
  using higher_differentiable_on_open_Union[of "{S, T}" f n] that
  by auto

lemma higher_differentiable_on_sqrt: "higher_differentiable_on S (λx. sqrt (f x)) n"
  if "higher_differentiable_on S f n" "0  f ` S" "open S"
  using that
proof (induction n arbitrary: f)
  case 0
  then show ?case by (auto simp: continuous_intros)
next
  case (Suc n)
  from Suc.prems(1) have fn: "higher_differentiable_on S f n" by (rule higher_differentiable_on_SucD)
  then have "continuous_on S f"
    by (rule higher_differentiable_on_imp_continuous_on)
  with ‹open S have op: "open (S  f -` {0<..})" (is "open ?op")
    by (rule open_continuous_vimage') simp
  from ‹open S ‹continuous_on S f have on: "open (S  f -` {..<0})" (is "open ?on")
    by (rule open_continuous_vimage') simp
  have op': "higher_differentiable_on ?op (λx. 1) n" and on': "higher_differentiable_on ?on (λx. -1) n"
     by (rule higher_differentiable_on_const)+
  then have i: "higher_differentiable_on (?op  ?on) (λx. if 0 < f x then 1::real else - 1) n"
    by (auto intro!: higher_differentiable_on_open_Un op on
          higher_differentiable_on_congI[OF _ op'] higher_differentiable_on_congI[OF _ on'])
  also have "?op  ?on = S" using Suc by auto
  finally have i: "higher_differentiable_on S (λx. if 0 < f x then 1::real else - 1) n" .
  from fn i Suc show ?case
    by (auto simp: sqrt_differentiable_on image_iff frechet_derivative_sqrt
        intro!: sqrt_differentiable higher_differentiable_on_mult higher_differentiable_on_inverse
          higher_differentiable_on_divide higher_differentiable_on_const
        cong: higher_differentiable_on_cong)
qed


lemma higher_differentiable_on_frechet_derivativeI:
  "higher_differentiable_on X (λx. frechet_derivative f (at x) h) i"
  if "higher_differentiable_on X f (Suc i)" "open X" "x  X"
  using that(1)
  by (induction i arbitrary: f h) auto

lemma higher_differentiable_on_norm:
  "higher_differentiable_on S (λx. norm (f x)) n"
  if "higher_differentiable_on S f n" "0  f ` S" "open S"
  for f::"__::real_inner"
  using that
proof (induction n arbitrary: f)
  case 0
  then show ?case by (auto simp: continuous_on_norm)
next
  case (Suc n)
  from Suc.prems(1) have fn: "higher_differentiable_on S f n" by (rule higher_differentiable_on_SucD)
  from Suc show ?case
    by (auto simp: continuous_on_norm frechet_derivative_norm image_iff sgn_div_norm
        cong: higher_differentiable_on_cong
        intro!: differentiable_norm_compose_at higher_differentiable_on_inner
          higher_differentiable_on_inverse
          higher_differentiable_on_mult Suc.IH fn)
qed

declare higher_differentiable_on.simps [simp del]

lemma higher_differentiable_on_Pair:
  "higher_differentiable_on S f k  higher_differentiable_on S g k 
   higher_differentiable_on S (λx. (f x, g x)) k"
  if "open S"
proof (induction k arbitrary: f g)
  case 0
  then show ?case
    unfolding higher_differentiable_on.simps
    by (auto intro!: continuous_intros)
next
  case (Suc k)
  then show ?case using that
    unfolding higher_differentiable_on.simps
    by (auto simp: frechet_derivative_pair[of f _ g] cong: higher_differentiable_on_cong)
qed

lemma higher_differentiable_on_compose':
  "higher_differentiable_on S (λx. f (g x)) n"
  if "higher_differentiable_on T f n" "higher_differentiable_on S g n"  "g ` S  T" "open S" "open T"
  for g::"__::euclidean_space"
  using higher_differentiable_on_compose[of T f n S g] comp_def that
  by (metis (no_types, lifting) higher_differentiable_on_cong)

lemma higher_differentiable_on_fst:
  "higher_differentiable_on (S × T) fst k"
proof (induction k)
  case (Suc k)
  then show ?case
    unfolding higher_differentiable_on.simps
    by (auto simp: differentiable_at_fst frechet_derivative_fst frechet_derivative_id higher_differentiable_on_const)
qed (auto simp: higher_differentiable_on.simps continuous_on_fst)

lemma higher_differentiable_on_snd:
  "higher_differentiable_on (S × T) snd k"
proof (induction k)
  case (Suc k)
  then show ?case
    unfolding higher_differentiable_on.simps
    by (auto intro!: continuous_intros
        simp: differentiable_at_snd frechet_derivative_snd frechet_derivative_id higher_differentiable_on_const)
qed (auto simp: higher_differentiable_on.simps continuous_on_snd)

lemma higher_differentiable_on_fst_comp:
  "higher_differentiable_on S (λx. fst (f x)) k"
  if "higher_differentiable_on S f k" "open S"
  using that
  by (induction k arbitrary: f)
    (auto intro!: continuous_intros differentiable_at_fst
      cong: higher_differentiable_on_cong
      simp: higher_differentiable_on.simps frechet_derivative_fst)

lemma higher_differentiable_on_snd_comp:
  "higher_differentiable_on S (λx. snd (f x)) k"
  if "higher_differentiable_on S f k" "open S"
  using that
  by (induction k arbitrary: f)
    (auto intro!: continuous_intros differentiable_at_snd
      cong: higher_differentiable_on_cong
      simp: higher_differentiable_on.simps frechet_derivative_snd)

lemma higher_differentiable_on_Pair':
  "higher_differentiable_on S f k  higher_differentiable_on T g k 
   higher_differentiable_on (S × T) (λx. (f (fst x), g (snd x))) k"
  if S: "open S" and T: "open T"
  for f::"_::euclidean_space_" and g::"_::euclidean_space_"
  by (auto intro!: higher_differentiable_on_Pair open_Times S T
      higher_differentiable_on_fst
      higher_differentiable_on_snd
      higher_differentiable_on_compose'[where f=f and T=S]
      higher_differentiable_on_compose'[where f=g and T=T])


lemma higher_differentiable_on_sin: "higher_differentiable_on S (λx. sin (f x::real)) n"
  and higher_differentiable_on_cos: "higher_differentiable_on S (λx. cos (f x::real)) n"
  if f: "higher_differentiable_on S f n" and S: "open S"
  unfolding atomize_conj
  using f
proof (induction n)
  case (Suc n)
  then have "higher_differentiable_on S f n"
    "higher_differentiable_on S (λx. sin (f x)) n"
    "higher_differentiable_on S (λx. cos (f x)) n"
    "x. x  S  f differentiable at x"
    using higher_differentiable_on_SucD
    by (auto simp: higher_differentiable_on.simps)
  with Suc show ?case
    by (auto simp: higher_differentiable_on.simps sin_differentiable_at cos_differentiable_at
        frechet_derivative_sin frechet_derivative_cos S
        intro!: higher_differentiable_on_mult higher_differentiable_on_uminus
        cong: higher_differentiable_on_cong[OF S])
qed (auto simp: higher_differentiable_on.simps intro!: continuous_intros)


subsection ‹Higher directional derivatives›

primrec nth_derivative :: "nat  ('a::real_normed_vector  'b::real_normed_vector)  'a  'a  'b" where
  "nth_derivative 0 f x h = f x"
| "nth_derivative (Suc i) f x h = nth_derivative i (λx. frechet_derivative f (at x) h) x h"

lemma frechet_derivative_nth_derivative_commute:
  "frechet_derivative (λx. nth_derivative i f x h) (at x) h =
    nth_derivative i (λx. frechet_derivative f (at x) h) x h"
  by (induction i arbitrary: f) auto

lemma nth_derivative_funpow:
  "nth_derivative i f x h = ((λf x. frechet_derivative f (at x) h) ^^ i) f x"
  by (induction i arbitrary: f) (auto simp del: funpow.simps simp: funpow_Suc_right)

lemma nth_derivative_exists:
  "f'. ((λx. nth_derivative i f x h) has_derivative f') (at x) 
    f' h = nth_derivative (Suc i) f x h"
  if "higher_differentiable_on X f (Suc i)" "open X" "x  X"
  using that(1)
proof (induction i arbitrary: f)
  case 0
  with that show ?case
    by (auto simp: higher_differentiable_on.simps that dest!: frechet_derivative_worksI)
next
  case (Suc i)
  from Suc.prems
  have "x. x  X  f differentiable at x" "higher_differentiable_on X (λx. frechet_derivative f (at x) h) (Suc i)"
    unfolding higher_differentiable_on.simps(2)[where n = "Suc i"]
    by auto
  from Suc.IH[OF this(2)] show ?case
    by auto
qed

lemma higher_derivatives_exists:
  assumes "higher_differentiable_on X f n" "open X"
  obtains Df where
    "a h. Df a 0 h = f a"
    "a h i. i < n  a  X  ((λa. Df a i H) has_derivative Df a (Suc i)) (at a)"
    "a i. i  n  a  X  Df a i H = nth_derivative i f a H"
proof -
  have "higher_differentiable_on X f (Suc i)" if "i < n" for i
    apply (rule higher_differentiable_on_le[OF assms(1)])
    using that by simp
  from nth_derivative_exists[OF this assms(2)]
  have "i{..<n}. x  X. f'. ((λx. nth_derivative i f x H) has_derivative f') (at x)  f' H = nth_derivative (Suc i) f x H"
    by blast
  from this[unfolded bchoice_iff]
  obtain f' where f': "i < n  x  X  ((λx. nth_derivative i f x H) has_derivative f' x i) (at x)"
    "i < n  x  X  f' x i H = nth_derivative (Suc i) f x H" for x i
    by force
  define Df where "Df a i h = (if i = 0 then f a else f' a (i - 1) h)" for a i h
  have "Df a 0 h = f a" for a h
    by (auto simp: Df_def)
  moreover have "i < n  a  X  ((λa. Df a i H) has_derivative Df a (Suc i)) (at a)"
    for i a
    apply (auto simp: Df_def[abs_def])
    using _ ‹open X
    apply (rule has_derivative_transform_within_open)
      apply (rule f')
       apply (auto simp: f')
    done
  moreover have "i  n  a  X  Df a i H = nth_derivative i f a H" for i a
    by (auto simp: Df_def f')
  ultimately show ?thesis ..
qed

lemma nth_derivative_differentiable:
  assumes "higher_differentiable_on S f (Suc n)" "x  S"
  shows "(λx. nth_derivative n f x v) differentiable at x"
  using assms
  by (induction n arbitrary: f) (auto simp: higher_differentiable_on.simps)

lemma higher_differentiable_on_imp_continuous_nth_derivative:
  assumes "higher_differentiable_on S f n"
  shows "continuous_on S (λx. nth_derivative n f x v)"
  using assms
  by (induction n arbitrary: f) (auto simp: higher_differentiable_on.simps)

lemma frechet_derivative_at_real_eq_scaleR:
  "frechet_derivative f (at x) v = v *R frechet_derivative f (at x) 1"
  if "f differentiable (at x)" "NO_MATCH 1 v"
  by (simp add: frechet_derivative_eq_vector_derivative that)

lemma higher_differentiable_on_real_Suc:
  "higher_differentiable_on S f (Suc n) 
     (xS. f differentiable (at x)) 
      (higher_differentiable_on S (λx. frechet_derivative f (at x) 1) n)"
  if "open S"
  for S::"real set"
  using ‹open S
  by (auto simp: higher_differentiable_on.simps frechet_derivative_at_real_eq_scaleR
      intro!: higher_differentiable_on_scaleR higher_differentiable_on_const
      cong: higher_differentiable_on_cong)

lemma higher_differentiable_on_real_SucI:
  fixes S::"real set"
  assumes
    "x. x  S  (λx. nth_derivative n f x 1) differentiable at x"
    "continuous_on S (λx. nth_derivative (Suc n) f x 1)"
    "higher_differentiable_on S f n"
    and o: "open S"
  shows "higher_differentiable_on S f (Suc n)"
  using assms
proof (induction n arbitrary: f)
  case 0
  then show ?case
      by (auto simp: higher_differentiable_on_real_Suc higher_differentiable_on.simps(1) o)
qed (fastforce simp: higher_differentiable_on_real_Suc)

lemma higher_differentiable_on_real_Suc':
  "open S  higher_differentiable_on S f (Suc n) 
    (v. continuous_on S (λx. nth_derivative (Suc n) f x 1)) 
    (xS. v. (λx. nth_derivative n f x 1) differentiable (at x))  higher_differentiable_on S f n"
  for S::"real set"
  apply (auto simp: nth_derivative_differentiable
      dest: higher_differentiable_on_SucD
      intro: higher_differentiable_on_real_SucI)
  by (auto simp: higher_differentiable_on_real_Suc higher_differentiable_on.simps(1)
      higher_differentiable_on_imp_continuous_nth_derivative)

lemma closed_segment_subsetD:
  "0  x  x  1  (X + x *R H)  S"
  if "closed_segment X (X + H)  S"
  using that
  by (rule subsetD) (auto simp: closed_segment_def algebra_simps intro!: exI[where x=x])


lemma higher_differentiable_Taylor:
  fixes f::"'a::real_normed_vector  'b::banach"
    and H::"'a"
    and Df::"'a  nat  'a  'a  'b"
  assumes "n > 0"
  assumes hd: "higher_differentiable_on S f n" "open S"
  assumes cs: "closed_segment X (X + H)  S"
  defines "i  λx. ((1 - x) ^ (n - 1) / fact (n - 1)) *R nth_derivative n f (X + x *R H) H"
  shows "(i has_integral f (X + H) - (i<n. (1 / fact i) *R nth_derivative i f X H)) {0..1}" (is ?th1)
  and "f (X + H) = (i<n. (1 / fact i) *R nth_derivative i f X H) + integral {0..1} i" (is ?th2)
  and "i integrable_on {0..1}" (is ?th3)
proof -
  from higher_derivatives_exists[OF hd]
  obtain Df where Df: "(a h. Df a 0 h = f a)"
    "(a h i. i < n  a  S  ((λa. Df a i H) has_derivative Df a (Suc i)) (at a))"
    "a i. i  n  a  S  Df a i H = nth_derivative i f a H"
    by blast
  from multivariate_Taylor_integral[OF n > 0, of Df H f X, OF Df(1,2)] cs
  have mt: "((λx. ((1 - x) ^ (n - 1) / fact (n - 1)) *R Df (X + x *R H) n H) has_integral
     f (X + H) - (i<n. (1 / fact i) *R Df X i H))
     {0..1}"
    by force
  from cs have "X  S" by auto
  show ?th1
    apply (rule has_integral_eq_rhs)
    unfolding i_def
    using negligible_empty _ mt
     apply (rule has_integral_spike)
    using closed_segment_subsetD[OF cs]
    by (auto simp: Df X  S)
  then show ?th2 ?th3
    unfolding has_integral_iff
    by auto
qed

lemma frechet_derivative_componentwise:
  "frechet_derivative f (at a) v = (iBasis. (v  i) * (frechet_derivative f (at a) i))"
  if "f differentiable at a"
  for f::"'a::euclidean_space  real"
proof -
  have "linear (frechet_derivative f (at a))"
    using that
    by (rule linear_frechet_derivative)
  from Linear_Algebra.linear_componentwise[OF this, of v 1]
  show ?thesis
    by simp
qed

lemma second_derivative_componentwise:
  "nth_derivative 2 f a v =
    (iBasis. (jBasis. frechet_derivative (λa. frechet_derivative f (at a) j) (at a) i * (v  j)) * (v  i))"
  if "higher_differentiable_on S f 2" and S: "open S" "a  S"
  for f::"'a::euclidean_space  real"
proof -
  have diff: "(λx. frechet_derivative f (at x) v) differentiable at a" for v
    using that
    by (auto simp: numeral_2_eq_2 higher_differentiable_on.simps differentiable_on_openD
        dest!: spec[where x=v])
  have d1: "x  S  f differentiable at x" for x
    using that
    by (auto simp: numeral_2_eq_2 higher_differentiable_on.simps dest!: differentiable_on_openD)
  have eq: "x. x  Basis 
         frechet_derivative
          (λx. iBasis. v  i * frechet_derivative f (at x) i) (at a) x =
         (jBasis. frechet_derivative (λa. frechet_derivative f (at a) j) (at a) x * (v  j))"
    apply (subst frechet_derivative_sum)
    subgoal by (auto intro!: differentiable_mult diff)
    apply (rule sum.cong)
     apply simp
    apply (subst frechet_derivative_times)
    subgoal by simp
    subgoal by (rule diff)
    by (simp add: frechet_derivative_const)
  show ?thesis
    apply (simp add: numeral_2_eq_2)
    apply (subst frechet_derivative_componentwise[OF diff])
    apply (rule sum.cong)
     apply simp
    apply simp
    apply (rule disjI2)
    apply (rule trans)
     apply (rule frechet_derivative_transform_within_open_ext [OF _ S frechet_derivative_componentwise])
    apply (simp add: diff)
       apply (rule d1, assumption)
    apply (simp add: eq)
    done
qed

lemma higher_differentiable_Taylor1:
  fixes f::"'a::real_normed_vector  'b::banach"
  assumes hd: "higher_differentiable_on S f 2" "open S"
  assumes cs: "closed_segment X (X + H)  S"
  defines "i  λx. ((1 - x)) *R nth_derivative 2 f (X + x *R H) H"
  shows "(i has_integral f (X + H) - (f X + nth_derivative 1 f X H)) {0..1}"
    and "f (X + H) = f X + nth_derivative 1 f X H + integral {0..1} i"
    and "i integrable_on {0..1}"
  using higher_differentiable_Taylor[OF _ hd cs]
  by (auto simp: numeral_2_eq_2 i_def)

lemma differentiable_on_open_blinfunE:
  assumes "f differentiable_on S" "open S"
  obtains f' where "x. x  S  (f has_derivative blinfun_apply (f' x)) (at x)"
proof -
  {
    fix x assume "x  S"
    with assms obtain f' where f': "(f has_derivative f') (at x)"
      by (auto dest!: differentiable_on_openD simp: differentiable_def)
    then have "bounded_linear f'"
      by (rule has_derivative_bounded_linear)
    then obtain bf' where "f' = blinfun_apply bf'"
      by (metis bounded_linear_Blinfun_apply)
    then have "bf'. (f has_derivative blinfun_apply bf') (at x)" using f'
      by blast
  } then obtain f' where "x. x  S  (f has_derivative blinfun_apply (f' x)) (at x)"
    by metis
  then show ?thesis ..
qed

lemma continuous_on_blinfunI1:
  "continuous_on X f"
  if "i. i  Basis  continuous_on X (λx. blinfun_apply (f x) i)"
  using that
  by (auto simp: continuous_on_def intro: tendsto_componentwise1)

lemma c1_euclidean_blinfunE:
  fixes f::"'a::euclidean_space'b::real_normed_vector"
  assumes "x. x  S  (f has_derivative f' x) (at x within S)"
  assumes "i. i  Basis  continuous_on S (λx. f' x i)"
  obtains bf' where
    "x. x  S  (f has_derivative blinfun_apply (bf' x)) (at x within S)"
    "continuous_on S bf'"
    "x. x  S  blinfun_apply (bf' x) = f' x"
proof -
  from assms have "bounded_linear (f' x)" if "x  S" for x
    by (auto intro!: has_derivative_bounded_linear that)
  then obtain bf' where bf': "x  S. f' x = blinfun_apply (bf' x)"
    apply atomize_elim
    apply (rule bchoice)
    apply auto
    by (metis bounded_linear_Blinfun_apply)
  with assms have "x. x  S  (f has_derivative blinfun_apply (bf' x)) (at x within S)"
    by simp
  moreover
  have f_tendsto: "((λn. f' n j)  f' x j) (at x within S)"
    if "x  S" "j  Basis"
    for x j
    using assms by (auto simp: continuous_on_def that)
  have "continuous_on S bf'"
    by (rule continuous_on_blinfunI1) (simp add: bf'[rule_format, symmetric] assms)
  moreover have "x. x  S  blinfun_apply (bf' x) = f' x" using bf' by auto
  ultimately show ?thesis ..
qed

lemma continuous_Sigma:
  assumes defined: "y  Pi T X"
  assumes f_cont: "continuous_on (Sigma T X) (λ(t, x). f t x)"
  assumes y_cont: "continuous_on T y"
  shows "continuous_on T (λx. f x (y x))"
  using
    defined
    continuous_on_compose2[OF
      continuous_on_subset[where t="(λx. (x, y x)) ` T", OF f_cont]
      continuous_on_Pair[OF continuous_on_id y_cont]]
  by auto

lemma continuous_on_Times_swap:
  "continuous_on (X × Y) (λ(x, y). f x y)"
  if "continuous_on (Y × X) (λ(y, x). f x y)"
  using continuous_on_compose2[OF that continuous_on_swap, where s="X × Y"]
  by (auto simp: split_beta' product_swap)

lemma leibniz_rule':
  "x. x  S 
      ((λx. integral (cbox a b) (f x)) has_derivative (λv. integral (cbox a b) (λt. fx x t v)))
          (at x within S)"
  "(λx. integral (cbox a b) (f x)) differentiable_on S"
  if "convex S"
    and c1: "t x. t  cbox a b  x  S  ((λx. f x t) has_derivative fx x t) (at x within S)"
    "i. i  Basis  continuous_on (S × cbox a b) (λ(x, t). fx x t i)"
    and i: "x. x  S  f x integrable_on cbox a b"
  for S::"'a::euclidean_space set"
    and f::"'a  'b::euclidean_space  'c::euclidean_space"
proof -
  have fx1: "continuous_on S (λx. fx x t i)" if "t  cbox a b" "i  Basis" for i t
    by (rule continuous_Sigma[OF _ c1(2), where y="λ_. t"]) (auto simp: continuous_intros that)
  {
    fix x assume "x  S"
    have fx2: "continuous_on (cbox a b) (λt. fx x t i)" if "i  Basis" for i
      by (rule continuous_Sigma[OF _ continuous_on_Times_swap[OF c1(2)]])
        (auto simp: continuous_intros that x  S)
    {
      fix t
      assume "t  cbox a b"
      have "f'. (x  S. ((λx. f x t) has_derivative blinfun_apply (f' x)) (at x within S) 
      blinfun_apply (f' x) = fx x t) 
      continuous_on S f'"
        by (rule c1_euclidean_blinfunE[OF c1(1)[OF t  _] fx1[OF t  _]]) (auto, metis)
    } then obtain fx' where
      fx':
      "t x. t  cbox a b  x  S  ((λx. f x t) has_derivative blinfun_apply (fx' t x)) (at x within S)"
      "t x. t  cbox a b  x  S  blinfun_apply (fx' t x) = fx x t"
      "t. t  cbox a b  continuous_on S (fx' t)"
      by metis
    have c: "continuous_on (S × cbox a b) (λ(x, t). fx' t x)"
      apply (rule continuous_on_blinfunI1)
      using c1(2)
      apply (rule continuous_on_eq) apply assumption
      by (auto simp: fx' split_beta')
    from leibniz_rule[of S a b f "λx t. fx' t x" x, OF fx'(1) i c x  S ‹convex S]
    have "((λx. integral (cbox a b) (f x)) has_derivative blinfun_apply (integral (cbox a b) (λt. fx' t x))) (at x within S)"
      by auto
    then have "((λx. integral (cbox a b) (f x)) has_derivative blinfun_apply (integral (cbox a b) (λt. fx' t x))) (at x within S)"
      by auto
    also
    have fx'xi: "(λt. fx' t x) integrable_on cbox a b"
      apply (rule integrable_continuous)
      apply (rule continuous_on_blinfunI1)
      by (simp add: fx' x  S fx2)
    have "blinfun_apply (integral (cbox a b) (λt. fx' t x)) = (λv. integral (cbox a b) (λxb. fx x xb v))"
      apply (rule ext)
      apply (subst blinfun_apply_integral)
       apply (rule fx'xi)
      by (simp add: x  S fx' cong: integral_cong)
    finally show "((λx. integral (cbox a b) (f x)) has_derivative (λc. integral (cbox a b) (λxb. fx x xb c))) (at x within S)"
      by simp
  } then show "(λx. integral (cbox a b) (f x)) differentiable_on S"
    by (auto simp: differentiable_on_def differentiable_def)
qed

lemmas leibniz_rule'_interval = leibniz_rule'[where 'b="_::ordered_euclidean_space",
    unfolded cbox_interval]

lemma leibniz_rule'_higher:
  "higher_differentiable_on S (λx. integral (cbox a b) (f x)) k"
  if "convex S" "open S"
    and c1: "higher_differentiable_on (S×cbox a b) (λ(x, t). f x t) k"
    ―‹this condition is actually too strong:
      it would suffice if higher partial derivatives (w.r.t. x) are continuous w.r.t. t.
      but it makes the statement short and no need to introduce new constants›
  for S::"'a::euclidean_space set"
    and f::"'a  'b::euclidean_space  'c::euclidean_space"
  using c1
proof (induction k arbitrary: f)
  case 0
  then show ?case
    by (auto simp: higher_differentiable_on.simps intro!: integral_continuous_on_param)
next
  case (Suc k)
  define D where "D x = frechet_derivative (λ(x, y). f x y) (at x)" for x
  note [continuous_intros] =
    Suc.prems[THEN higher_differentiable_on_imp_continuous_on, THEN continuous_on_compose2,
      of _ "λx. (f x, g x)" for f g, unfolded split_beta' fst_conv snd_conv]
  from Suc.prems have prems:
    "xt. xt  S × cbox a b  (λ(x, y). f x y) differentiable at xt"
    "higher_differentiable_on (S × cbox a b) (λx. D x (dx, dt)) k"
    for dx dt
    by (auto simp: higher_differentiable_on.simps D_def)
  from frechet_derivative_worksI[OF this(1), folded D_def]
  have D: "x  S  t  cbox a b  ((λ(x, y). f x y) has_derivative D (x, t)) (at (x, t))" for x t
    by auto
  have p1: "((λx. (x, t::'b)) has_derivative (λh. (h, 0))) (at x within S)" for x t
    by (auto intro!: derivative_eq_intros)
  have Dx: "x  S  t  cbox a b  ((λx. f x t) has_derivative (λdx. D (x, t) (dx, 0))) (at x within S)" for x t
    by (drule has_derivative_compose[OF p1 D], assumption) auto
  have cD: "continuous_on (S × cbox a b) (λ(x, t). D (x, t) v)" for v
    apply (rule higher_differentiable_on_imp_continuous_on[where n=k])
    using prems(2)[of "fst v" "snd v"]
    by (auto simp: split_beta')
  have fi: "x  S  f x integrable_on cbox a b" for x
    by (rule integrable_continuous) (auto intro!: continuous_intros)
  from leibniz_rule'[OF ‹convex S Dx cD fi]
  have ihd: "x  S  ((λx. integral (cbox a b) (f x)) has_derivative (λv. integral (cbox a b) (λt. D (x, t) (v, 0))))
     (at x within S)"
    and "(λx. integral (cbox a b) (f x)) differentiable_on S"
    for x
    by auto
  then have "x  S  (λx. integral (cbox a b) (f x)) differentiable at x" for x
    using ‹open S
    by (auto simp: differentiable_on_openD)
  moreover
  have "higher_differentiable_on S (λx. frechet_derivative (λx. integral (cbox a b) (f x)) (at x) v) k" for v
  proof -
    have *: "frechet_derivative (λx. integral (cbox a b) (f x)) (at x) =
      (λv. integral (cbox a b) (λt. D (x, t) (v, 0)))"
      if "x  S"
      for x
      apply (rule frechet_derivative_at')
      using ihd(1)[OF that] at_within_open[OF that ‹open S]
      by auto
    have **: "higher_differentiable_on S (λx. integral (cbox a b) (λt. D (x, t) (v, 0))) k"
      apply (rule Suc.IH)
      using prems by auto
    show ?thesis
      using ‹open S
      by (auto simp: * ** cong: higher_differentiable_on_cong)
  qed
  ultimately
  show ?case
    by (auto simp: higher_differentiable_on.simps)
qed

lemmas leibniz_rule'_higher_interval = leibniz_rule'_higher[where 'b="_::ordered_euclidean_space",
    unfolded cbox_interval]


subsection ‹Smoothness›

definition k_smooth_on :: "enat 'a::real_normed_vector set  ('a  'b::real_normed_vector)  bool"
  ("_-smooth'_on" [1000]) where
 smooth_on_def: "k-smooth_on S f = (nk. higher_differentiable_on S f n)"

abbreviation "smooth_on S f  -smooth_on S f"

lemma derivative_is_smooth':
  assumes "(k+1)-smooth_on S f"
  shows "k-smooth_on S (λx. frechet_derivative f (at x) v)"
  unfolding smooth_on_def
proof (intro allI impI)
  fix n assume "enat n  k" then have "Suc n  k + 1"
    unfolding plus_1_eSuc
    by (auto simp: eSuc_def split: enat.splits)
  then have "higher_differentiable_on S f (Suc n)"
    using assms(1) by (auto simp: smooth_on_def)
  then show "higher_differentiable_on S (λx. frechet_derivative f (at x) v) n"
    by (auto simp: higher_differentiable_on.simps(2))
qed

lemma derivative_is_smooth: "smooth_on S f  smooth_on S (λx. frechet_derivative f (at x) v)"
  using derivative_is_smooth'[of  S f] by simp

lemma smooth_on_imp_continuous_on: "continuous_on S f" if "k-smooth_on S f"
  apply (rule higher_differentiable_on_imp_continuous_on[where n=0])
  using that
  by (simp add: smooth_on_def enat_0)

lemma smooth_on_imp_differentiable_on[simp]: "f differentiable_on S" if "k-smooth_on S f" "k  0"
  using that
  by (auto simp: smooth_on_def Suc_ile_eq enat_0
      dest!: spec[where x=1]
      intro!: higher_differentiable_on_imp_differentiable_on[where k=1])

lemma smooth_on_cong:
  assumes "k-smooth_on S g" "open S"
    and "x. x  S  f x = g x"
  shows "k-smooth_on S f"
  using assms unfolding smooth_on_def
  by (auto cong: higher_differentiable_on_cong)

lemma smooth_on_open_Un:
  "k-smooth_on S f  k-smooth_on T f  open S  open T  k-smooth_on (S  T) f"
  by (auto simp: smooth_on_def higher_differentiable_on_open_Un)

lemma smooth_on_open_subsetsI:
  "k-smooth_on S f"
  if "x. x  S  T. x  T  open T  k-smooth_on T f"
  using that
  unfolding smooth_on_def
  by (force intro: higher_differentiable_on_open_subsetsI)

lemma smooth_on_const[intro]: "k-smooth_on S (λx. c)"
  by (auto simp: smooth_on_def higher_differentiable_on_const)

lemma smooth_on_id[intro]: "k-smooth_on S (λx. x)"
  by (auto simp: smooth_on_def higher_differentiable_on_id)

lemma smooth_on_add_fun: "k-smooth_on S f  k-smooth_on S g  open S  k-smooth_on S (f + g)"
  by (auto simp: smooth_on_def higher_differentiable_on_add plus_fun_def)

lemmas smooth_on_add = smooth_on_add_fun[unfolded plus_fun_def]

lemma smooth_on_sum:
  "n-smooth_on S (λx. iF. f i x)"
  if "i. i  F  finite F  n-smooth_on S (f i)" "open S"
  using that
  by (auto simp: smooth_on_def higher_differentiable_on_sum)

lemma (in bounded_bilinear) smooth_on:
  includes no_matrix_mult
  assumes "k-smooth_on S f" "k-smooth_on S g" "open S"
  shows "k-smooth_on S (λx. (f x) ** (g x))"
  using assms
  by (auto simp: smooth_on_def higher_differentiable_on)

lemma smooth_on_compose2:
  fixes g::"__::euclidean_space"
  assumes "k-smooth_on T f" "k-smooth_on S g" "open U" "open T" "g ` U  T" "U  S" 
  shows "k-smooth_on U (f o g)"
  using assms
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_compose intro: higher_differentiable_on_subset)

lemma smooth_on_compose:
  fixes g::"__::euclidean_space"
  assumes "k-smooth_on T f" "k-smooth_on S g" "open S" "open T" "g ` S  T"
  shows "k-smooth_on S (f o g)"
  using assms by (rule smooth_on_compose2) auto

lemma smooth_on_subset:
  "k-smooth_on S f"
  if "k-smooth_on T f" "S  T"
  using higher_differentiable_on_subset[of T f _ S] that
  by (auto simp: smooth_on_def)

context begin
private lemmas s = bounded_bilinear.smooth_on
lemmas smooth_on_inner = bounded_bilinear_inner[THEN s]
   and smooth_on_scaleR = bounded_bilinear_scaleR[THEN s]
   and smooth_on_mult = bounded_bilinear_mult[THEN s]
end

lemma smooth_on_divide:"k-smooth_on S f  k-smooth_on S g  open S (x. x  S  g x  0) 
  k-smooth_on S (λx. f x / g x)"
  for f::"__::real_normed_field"
  by (auto simp: smooth_on_def higher_differentiable_on_divide)

lemma smooth_on_scaleR_fun: "k-smooth_on S g  open S  k-smooth_on S (c *R g)"
  by (auto simp: scaleR_fun_def intro!: smooth_on_scaleR )

lemma smooth_on_uminus_fun: "k-smooth_on S g  open S  k-smooth_on S (- g)"
  using smooth_on_scaleR_fun[where c="-1", of k S g]
  by auto

lemmas smooth_on_uminus = smooth_on_uminus_fun[unfolded fun_Compl_def]

lemma smooth_on_minus_fun: "k-smooth_on S f  k-smooth_on S g  open S  k-smooth_on S (f - g)"
  unfolding diff_conv_add_uminus
  apply (rule smooth_on_add_fun)
    apply assumption
   apply (rule smooth_on_uminus_fun)
  by auto

lemmas smooth_on_minus = smooth_on_minus_fun[unfolded fun_diff_def]

lemma smooth_on_times_fun: "k-smooth_on S f  k-smooth_on S g  open S  k-smooth_on S (f * g)"
  for f g::"_ _::real_normed_algebra"
  by (auto simp: times_fun_def intro!: smooth_on_mult)

lemma smooth_on_le:
  "l-smooth_on S f"
  if "k-smooth_on S f" "l  k"
  using that
  by (auto simp: smooth_on_def)

lemma smooth_on_inverse: "k-smooth_on S (λx. inverse (f x))"
  if "k-smooth_on S f" "0  f ` S" "open S"
  for f::"_ _::real_normed_field"
  using that
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_inverse)

lemma smooth_on_norm: "k-smooth_on S (λx. norm (f x))"
  if "k-smooth_on S f" "0  f ` S" "open S"
  for f::"_ _::real_inner"
  using that
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_norm)

lemma smooth_on_sqrt: "k-smooth_on S (λx. sqrt (f x))"
  if "k-smooth_on S f" "0  f ` S" "open S"
  using that
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_sqrt)

lemma smooth_on_frechet_derivative:
  "-smooth_on UNIV (λx. frechet_derivative f (at x) v)"
  if "-smooth_on UNIV f"
    ―‹TODO: generalize›
  using that
  apply (auto simp: smooth_on_def)
  apply (rule higher_differentiable_on_frechet_derivativeI)
  by auto

lemmas smooth_on_frechet_derivivative_comp = smooth_on_compose2[OF smooth_on_frechet_derivative, unfolded o_def]

lemma smooth_onD: "higher_differentiable_on S f n" if "m-smooth_on S f" "enat n  m"
  using that
  by (auto simp: smooth_on_def)

lemma (in bounded_linear) higher_differentiable_on: "higher_differentiable_on S f n"
proof (induction n)
  case 0
  then show ?case
    by (auto simp: higher_differentiable_on.simps linear_continuous_on bounded_linear_axioms)
next
  case (Suc n)
  then show ?case
    apply (auto simp: higher_differentiable_on.simps frechet_derivative higher_differentiable_on_const)
    using bounded_linear_axioms apply (rule bounded_linear_imp_differentiable)
    done
qed

lemma (in bounded_linear) smooth_on: "k-smooth_on S f"
  by (auto simp: smooth_on_def higher_differentiable_on)

lemma smooth_on_snd:
  "k-smooth_on S (λx. snd (f x))"
  if "k-smooth_on S f" "open S"
  using higher_differentiable_on_snd_comp that
  by (auto simp: smooth_on_def)

lemma smooth_on_fst:
  "k-smooth_on S (λx. fst (f x))"
  if "k-smooth_on S f" "open S"
  using higher_differentiable_on_fst_comp that
  by (auto simp: smooth_on_def)

lemma smooth_on_sin: "n-smooth_on S (λx. sin (f x::real))" if "n-smooth_on S f" "open S"
  using that
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_sin)

lemma smooth_on_cos: "n-smooth_on S (λx. cos (f x::real))" if "n-smooth_on S f" "open S"
  using that
  by (auto simp: smooth_on_def intro!: higher_differentiable_on_cos)

lemma smooth_on_Taylor2E:
  fixes f::"'a::euclidean_space  real"
  assumes hd: "-smooth_on UNIV f"
  obtains g where "Y.
    f Y = f X + frechet_derivative f (at X) (Y - X) + (iBasis. (jBasis. ((Y - X)  j) * ((Y - X)  i) * g i j Y))"
    "i j. i  Basis  j  Basis  -smooth_on UNIV (g i j)"
    ―‹TODO: generalize›
proof -
  define S::"'a set" where "S = UNIV"
  have "open S" and "convex S" "X  S" by (auto simp: S_def)
  have hd: "-smooth_on S f"
    using hd by (auto simp: S_def)
  define i where "i H x = ((1 - x)) *R nth_derivative 2 f (X + x *R H) H" for x H
  define d2 where "d2 v v' = (λx. frechet_derivative (λx. frechet_derivative f (at x) v') (at x) v)" for v v'
  define g where "g H x i j = d2 i j (X + x *R H)" for i j x H
  define g' where  "g' i j H = integral {0 .. 1} (λx. (1 - x) * g H x i j)" for i j H
  have "higher_differentiable_on S f 2"
    using hd(1) by (auto simp: smooth_on_def dest!: spec[where x=2])
  note hd2 = this ‹open S

  have d2_cont: "continuous_on S (d2 i j)" for i j
    using hd2(1)
    by (auto simp: g_def numeral_2_eq_2 higher_differentiable_on.simps d2_def)
  note [continuous_intros] = continuous_on_compose2[OF d2_cont]

  have hdiff2: "-smooth_on S (d2 v v')" for v v'
    apply (auto simp: d2_def)
    apply (rule smooth_on_frechet_derivivative_comp)
    apply (rule smooth_on_frechet_derivivative_comp)
    by (auto simp: S_def assms)
  {
    fix Y
    assume "Y  S"
    define H where "H = Y - X"
    from Y  S have "X + H  S" by (simp add: H_def)
    with X  S have cs: "closed_segment X (X + H)  S"
      using ‹convex S
      by (rule closed_segment_subset)

    have i: "(i H has_integral f (X + H) - (f X + nth_derivative 1 f X H)) {0..1}"
      "f (X + H) = f X + nth_derivative 1 f X H + integral {0..1} (i H)"
      "i H integrable_on {0..1}"
      unfolding i_def
      using higher_differentiable_Taylor1[OF hd2 cs]
      by auto
    note i(2)
    also

    have integrable: "(λx. nBasis. (1 - x) * (g H x a n * (H  n) * (H  a))) integrable_on {0..1}"
      "(λx. (1 - x) * (g H x n a * (H  a) * (H  n))) integrable_on {0..1}" 
      for a n
       by (auto intro!: integrable_continuous_interval continuous_intros closed_segment_subsetD
          cs simp: g_def)

    have i_eq: "i H x = (1 - x) *R (iBasis. (jBasis. g H x i j * (H  j)) * (H  i))"
      if "0  x" "x  1"
      for x
      unfolding i_def
      apply (subst second_derivative_componentwise[OF hd2])
       apply (rule closed_segment_subsetD, rule cs, rule that, rule that)
      by (simp add: g_def d2_def)

    have "integral {0 .. 1} (i H) = integral {0..1} (λx. (1 - x) * (iBasis. (jBasis. g H x i j * (H  j)) * (H  i)))"
      apply (subst integral_spike[OF negligible_empty])
       apply (rule sym)
       apply (rule i_eq)
      by (auto simp: that)
    also
    have " = (iBasis. (jBasis. (H  j) * (H  i) * g' i j H))"
      apply (simp add: sum_distrib_left sum_distrib_right integral_sum integrable g'_def)
      apply (simp add: integral_mult_right[symmetric] del: integral_mult_right)
      by (simp only: ac_simps)
    finally have "f (X + H) = f X + nth_derivative 1 f X H + (iBasis. jBasis. H  j * (H  i) * g' i j H)" .
  } note * = this
  have "f Y = f X + frechet_derivative f (at X) (Y - X) + (iBasis. jBasis. (Y - X)  j * ((Y - X)  i) * g' i j (Y - X))"
    for Y
    using *[of Y]
    by (auto simp: S_def)
  moreover
  define T::"real set" where "T = {- 1<..<2}"
  have "open T"
    by (auto simp: T_def)
  have "{0 .. 1}  T"
    by (auto simp: T_def)
  have T_small: "a  S  b  T  X + b *R (a - X)  S" for a b
    by (auto simp: S_def)
  have "open (S × T)"
    by (auto intro: open_Times ‹open S ‹open T)
  have "smooth_on S (λY. g' i j (Y - X))" if i: "i  Basis" and j: "j  Basis" for i j
    unfolding smooth_on_def
    apply safe
    apply (simp add: g'_def)
    apply (rule leibniz_rule'_higher_interval)
      apply fact
     apply fact
    apply (rule higher_differentiable_on_subset[where T="S × T"])
     apply (auto intro!: higher_differentiable_on_mult simp: split_beta')
       apply (subst diff_conv_add_uminus)
       apply (rule higher_differentiable_on_add)
         apply (rule higher_differentiable_on_const)
        apply (subst scaleR_minus1_left[symmetric])
        apply (rule higher_differentiable_on_scaleR)
          apply (rule higher_differentiable_on_const)
         apply (rule higher_differentiable_on_snd_comp)
          apply (rule higher_differentiable_on_id)
         apply fact apply fact apply fact
      apply (auto simp: g_def)
      apply (rule smooth_onD)
       apply (rule smooth_on_compose2[OF hdiff2, unfolded o_def])
    using ‹open S ‹open T
    using T_small _  T
    by (auto intro!: open_Times smooth_on_add smooth_on_scaleR smooth_on_snd
        smooth_on_minus smooth_on_fst)
  ultimately show ?thesis unfolding S_def ..
qed


lemma smooth_on_Pair:
  "k-smooth_on S (λx. (f x, g x))"
  if "open S" "k-smooth_on S f" "k-smooth_on S g"
proof (auto simp: smooth_on_def)
  fix n assume n: "enat n  k"
  have 1: "higher_differentiable_on S f n"
    using that(2) n unfolding smooth_on_def by auto
  have 2: "higher_differentiable_on S g n"
    using that(3) n unfolding smooth_on_def by auto
  show "higher_differentiable_on S (λx. (f x, g x)) n"
    by (rule higher_differentiable_on_Pair [OF that(1) 1 2])
qed


lemma smooth_on_Pair':
  "k-smooth_on (S × T) (λx. (f (fst x), g (snd x)))"
  if "open S" "open T" "k-smooth_on S f" "k-smooth_on T g"
  for f::"_::euclidean_space_" and g::"_::euclidean_space_"
proof (auto simp: smooth_on_def)
  fix n assume n: "enat n  k"
  have 1: "higher_differentiable_on S f n"
    using that(3) n unfolding smooth_on_def by auto
  have 2: "higher_differentiable_on T g n"
    using that(4) n unfolding smooth_on_def by auto
  show "higher_differentiable_on (S × T) (λx. (f (fst x), g (snd x))) n"
    by (rule higher_differentiable_on_Pair'[OF that(1,2) 1 2])
qed


subsection ‹Diffeomorphism›

definition "diffeomorphism k S T p p' 
  k-smooth_on S p  k-smooth_on T p'  homeomorphism S T p p'"

lemma diffeomorphism_imp_homeomorphism:
  assumes "diffeomorphism k s t p p'"
  shows  "homeomorphism s t p p'"
  using assms
  by (auto simp: diffeomorphism_def)

lemma diffeomorphismD:
  assumes "diffeomorphism k S T f g"
  shows diffeomorphism_smoothD: "k-smooth_on S f" "k-smooth_on T g"
    and diffeomorphism_inverseD: "x. x  S  g (f x) = x" "y. y  T  f (g y) = y"
    and diffeomorphism_image_eq: "(f ` S = T)" "(g ` T = S)"
  using assms by (auto simp: diffeomorphism_def homeomorphism_def)

lemma diffeomorphism_compose:
  "diffeomorphism n S T f g  diffeomorphism n T U h k  open S  open T  open U 
    diffeomorphism n S U (h  f) (g  k)"
  for f::"__::euclidean_space"
  by (auto simp: diffeomorphism_def intro!: smooth_on_compose homeomorphism_compose)
    (auto simp: homeomorphism_def)

lemma diffeomorphism_add: "diffeomorphism k UNIV UNIV (λx. x + c) (λx. x - c)"
  by (auto simp: diffeomorphism_def homeomorphism_add intro!: smooth_on_minus smooth_on_add)

lemma diffeomorphism_scaleR: "diffeomorphism k UNIV UNIV (λx. c *R x) (λx. x /R c)"
  if "c  0"
  by (auto simp: that diffeomorphism_def homeomorphism_scaleR
      intro!: smooth_on_minus smooth_on_scaleR)

end

Theory Bump_Function

section ‹Bump Functions›
theory Bump_Function
  imports Smooth
    "HOL-Analysis.Weierstrass_Theorems"
begin

subsection ‹Construction›

context begin

qualified definition f :: "real  real" where
  "f t = (if t > 0 then exp(-inverse t) else 0)"

lemma f_nonpos[simp]: "x  0  f x = 0"
  by (auto simp: f_def)

lemma exp_inv_limit_0_right:
  "((λ(t::real). exp(-inverse t))  0) (at_right 0)"
  apply (rule filterlim_compose[where g = exp])
   apply (rule exp_at_bot)
  apply (rule filterlim_compose[where g = uminus])
   apply (rule filterlim_uminus_at_bot_at_top)
  by (rule filterlim_inverse_at_top_right)

lemma "F t in at_right 0. ((λx. inverse (x ^ Suc k)) has_real_derivative
  - (inverse (t ^ Suc k) * ((1 + real k) * t ^ k) * inverse (t ^ Suc k))) (at t)"
  unfolding eventually_at_filter
  by (auto simp del: power_Suc intro!: derivative_eq_intros eventuallyI)

lemma exp_inv_limit_0_right_gen':
  "((λ(t::real). inverse (t ^ k) / exp(inverse t))  0) (at_right 0)"
proof (induct k)
  case 0
  then show ?case
    using exp_inv_limit_0_right
    by (auto simp: exp_minus inverse_eq_divide)
next
  case (Suc k)
  have df: "F t in at_right 0. ((λx. inverse (x ^ Suc k)) has_real_derivative 
    - (inverse (t ^ k) * ((1 + real k)) * (inverse t ^ 2))) (at t)"
    unfolding eventually_at_filter
    apply (auto simp del: power_Suc intro!: derivative_eq_intros eventuallyI)
    by (auto simp: power2_eq_square)
  have dg: "F t in at_right 0. ((λx. exp (inverse x)) has_real_derivative
    - (exp (inverse t) * (inverse t ^ 2))) (at t)"
    unfolding eventually_at_filter
    by (auto simp del: power_Suc intro!: derivative_eq_intros eventuallyI simp: power2_eq_square)
  show ?case
    apply (rule lhopital_right_0_at_top [OF _ _ df dg])
      apply (rule filterlim_compose[where g = exp])
       apply (rule exp_at_top)
      apply (rule filterlim_inverse_at_top_right)
    subgoal by (auto simp: eventually_at_filter)
    subgoal
      apply (rule Lim_transform_eventually[where f = "λx. (1 + real k) * (inverse (x ^ k) / exp (inverse x))"])
      using Suc.hyps tendsto_mult_right_zero apply blast
      by (auto simp: eventually_at_filter)
    done
qed

lemma exp_inv_limit_0_right_gen:
  "((λ(t::real). exp(-inverse t) / t ^ k)  0) (at_right 0)"
  using exp_inv_limit_0_right_gen'[of k]
  by (metis (no_types, lifting) Groups.mult_ac(2) Lim_cong_within divide_inverse exp_minus)  

lemma f_limit_0_right: "(f  0) (at_right 0)"
proof -
  have "F t in at_right 0. (t::real) > 0"
    by (rule eventually_at_right_less)
  then have "F t in at_right 0. exp(-inverse t) = f t"
    by (eventually_elim) (auto simp: f_def)
  moreover have "((λ(t::real). exp(-inverse t))  0) (at_right 0)"
    by (rule exp_inv_limit_0_right)
  ultimately show ?thesis
    by (blast intro: Lim_transform_eventually)
qed

lemma f_limit_0: "(f  0) (at 0)"
  using _ f_limit_0_right
proof (rule filterlim_split_at_real)
  have "F t in at_left 0. 0 = f t"
    by (auto simp: f_def eventually_at_filter)
  then show "(f  0) (at_left 0)"
    by (blast intro: Lim_transform_eventually) 
qed

lemma f_tendsto: "(f  f x) (at x)"
proof -
  consider "x = 0" | "x < 0" | "x > 0" by arith
  then show ?thesis
  proof cases
    case 1
    then show ?thesis by (auto simp: f_limit_0 f_def)
  next
    case 2
    have "F t in at x. t < 0"
      apply (rule order_tendstoD)
      by (rule tendsto_intros) fact
    then have "F t in at x. 0 = f t"
      by (eventually_elim) (auto simp: f_def)
    then show ?thesis
      using x < 0 by (auto simp: f_def intro: Lim_transform_eventually)
  next
    case 3
    have "F t in at x. t > 0"
      apply (rule order_tendstoD)
      by (rule tendsto_intros) fact
    then have "F t in at x. exp(-inverse t) = f t"
      by (eventually_elim) (auto simp: f_def)
    moreover have "(λt. exp (- inverse t)) x f x"
      using x > 0 by (auto simp: f_def tendsto_intros )
    ultimately show ?thesis
      by (blast intro: Lim_transform_eventually)
  qed
qed

lemma f_continuous: "continuous_on S f"
  using f_tendsto continuous_on continuous_on_subset subset_UNIV by metis

lemma continuous_on_real_polynomial_function:
  "continuous_on S p" if "real_polynomial_function p"
  using that
  by induction (auto intro: continuous_intros linear_continuous_on)

lemma f_nth_derivative_is_poly:
  "higher_differentiable_on {0<..} f k 
   (p. real_polynomial_function p  (t>0. nth_derivative k f t 1 = p t / (t ^ (2 * k)) * exp(-inverse t)))"
proof (induction k)
  case 0
  then show ?case
    apply (auto simp: higher_differentiable_on.simps f_continuous)
    by (auto simp: f_def)
next
  case (Suc k)
  obtain p where fk: "higher_differentiable_on {0<..} f k"
    and p1: "real_polynomial_function p"
    and p2: "t>0. nth_derivative k f t 1 = p t / t ^ (2 * k) * exp (- inverse t)"
    using Suc by auto
  obtain p' where p'1: "real_polynomial_function p'"
    and p'2: "t. (p has_real_derivative (p' t)) (at t)"
    using has_real_derivative_polynomial_function[of p] p1 by auto

  define rp where "rp t = (t2 * p' t - 2 * real k * t * p t + p t)" for t
  have rp: "real_polynomial_function rp"
    unfolding rp_def
    by (auto intro!: real_polynomial_function.intros(2-) real_polynomial_function_diff
        p1 p'1 simp: power2_eq_square)
  moreover
  have fk': "(λx. nth_derivative k f x 1) differentiable at t" (is ?a)
    "frechet_derivative (λx. nth_derivative k f x 1) (at t) 1 =
        rp t * (exp (-inverse t) / t^(2*k+2))" (is ?b)
    if "0 < t" for t
  proof -
    from p'2 that have dp: "(p has_derivative ((*) (p' t))) (at t within {0<..})"
      by (auto simp: at_within_open[of _ "{0<..}"] has_field_derivative_def ac_simps)
    have "((λt. p t / t ^ (2 * k) * exp (- inverse t)) has_derivative
      (λv. v * rp t * (exp (-inverse t) / t^(2*k+2)))) (at t within {0<..})"
      using that
      apply (auto intro!: derivative_eq_intros dp ext)
      apply (simp add: divide_simps algebra_simps rp_def power2_eq_square)
      by (metis Suc_pred mult_is_0 neq0_conv power_Suc zero_neq_numeral)
    then have "((λx. nth_derivative k f x 1) has_derivative
      (λv. v * rp t * (exp (-inverse t) / t^(2*k+2)))) (at t within {0<..})"
      apply (rule has_derivative_transform_within[OF _ zero_less_one])
      using that p2 by (auto simp: )
    then have "((λx. nth_derivative k f x 1) has_derivative
      (λv. v * rp t * (exp (-inverse t) / t^(2*k+2)))) (at t)"
      using that
      by (auto simp: at_within_open[of _ "{0<..}"])
    from frechet_derivative_at'[OF this] this
    show ?a ?b
      by (auto simp: differentiable_def)
  qed
  have hdS: "higher_differentiable_on {0<..} f (Suc k)"
    apply (subst higher_differentiable_on_real_Suc')
     apply (auto simp: fk fk' frechet_derivative_nth_derivative_commute[symmetric])
    apply (subst continuous_on_cong[OF refl])
     apply (rule fk')
    by (auto intro!: continuous_intros p'1 p1 rp
        intro: continuous_on_real_polynomial_function)
  moreover
  have "nth_derivative (Suc k) f t 1 = rp t / t ^ (2 * (Suc k)) * exp (- inverse t)"
    if "t > 0" for t
  proof -
    have "nth_derivative (Suc k) f t 1 = frechet_derivative (λx. nth_derivative k f x 1) (at t) 1"
      by (simp add: frechet_derivative_nth_derivative_commute)

    also have " = rp t / t^(2*k+2) * (exp (-inverse t))"
      using fk'[OF t > 0] by simp
    finally  show ?thesis by simp
  qed
  ultimately show ?case by blast
qed

lemma f_has_derivative_at_neg:
  " x < 0  (f has_derivative (λx. 0)) (at x)"
  by (rule has_derivative_transform_within_open[where f="λx. 0" and s="{..<0}"])
    (auto simp: f_def)

lemma f_differentiable_at_neg:
  "x < 0  f differentiable at x"
  using f_has_derivative_at_neg
  by (auto simp: differentiable_def)

lemma frechet_derivative_f_at_neg:
  "x  {..<0}  frechet_derivative f (at x) = (λx. 0)"
  by (rule frechet_derivative_at') (rule f_has_derivative_at_neg, simp)

lemma f_nth_derivative_lt_0:
  "higher_differentiable_on {..<0} f k  (t<0. nth_derivative k f t 1 = 0)"
proof (induction k)
  case 0
  have rewr: "a  {..<0}  ¬0 < a" for a::real by simp
  show ?case
    by (auto simp: higher_differentiable_on.simps f_def rewr
        simp del: lessThan_iff
        cong: continuous_on_cong)
next
  case (Suc k)
  have "t < 0  (λx. nth_derivative k f x 1) differentiable at t" for t
    by (rule differentiable_eqI[where g=0 and X="{..<0}"])
      (auto simp: zero_fun_def frechet_derivative_const Suc.IH)
  then have "frechet_derivative (λx. nth_derivative k f x 1) (at t) 1 = 0" if "t < 0" for t
    using that Suc.IH
    by (subst frechet_derivative_transform_within_open[where X="{..<0}" and g =0])
      (auto simp: frechet_derivative_zero_fun)
  with Suc show ?case
    by (auto simp: higher_differentiable_on.simps f_differentiable_at_neg
        frechet_derivative_f_at_neg zero_fun_def
        simp flip: frechet_derivative_nth_derivative_commute
        simp del: lessThan_iff
        intro!: higher_differentiable_on_const
        cong: higher_differentiable_on_cong)
qed

lemma netlimit_at_left: "netlimit (at_left x) = x" for x::real
  by (rule Lim_ident_at) simp

lemma netlimit_at_right: "netlimit (at_right x) = x" for x::real
  by (rule Lim_ident_at) simp


lemma has_derivative_split_at:
  "(g has_derivative g') (at x)"
  if
    "(g has_derivative g') (at_left x)"
    "(g has_derivative g') (at_right x)"
  for x::real
  using that
  unfolding has_derivative_def netlimit_at netlimit_at_right netlimit_at_left
  by (auto intro: filterlim_split_at)

lemma has_derivative_at_left_at_right':
  "(g has_derivative g') (at x)"
  if
    "(g has_derivative g') (at x within {..x})"
    "(g has_derivative g') (at x within {x..})"
  for x::real
  apply (rule has_derivative_split_at)
  subgoal by (rule has_derivative_subset) (fact, auto)
  subgoal by (rule has_derivative_subset) (fact, auto)
  done

lemma real_polynomial_function_tendsto:
  "(p  p x) (at x within X)" if "real_polynomial_function p"
  using that
  by (induction p) (auto intro!: tendsto_eq_intros intro: bounded_linear.tendsto)

lemma f_nth_derivative_cases:
  "higher_differentiable_on UNIV f k 
   (t0. nth_derivative k f t 1 = 0) 
   (p. real_polynomial_function p 
      (t>0. nth_derivative k f t 1 = p t / (t ^ (2 * k)) * exp(-inverse t)))"
proof (induction k)
  case 0
  then show ?case
    apply (auto simp: higher_differentiable_on.simps f_continuous)
    by (auto simp: f_def)
next
  case (Suc k)
  from Suc.IH obtain pk where IH:
    "higher_differentiable_on UNIV f k"
    "t. t  0  nth_derivative k f t 1 = 0"
    "real_polynomial_function pk"
    "t. t > 0  nth_derivative k f t 1 = pk t / t ^ (2 * k) * exp (- inverse t)"
    by auto
  from f_nth_derivative_lt_0[of "Suc k"]
    local.f_nth_derivative_is_poly[of "Suc k"]
  obtain p where neg: "higher_differentiable_on {..<0} f (Suc k)"
    and neg0: "(t<0. nth_derivative (Suc k) f t 1 = 0)"
    and pos: "higher_differentiable_on {0<..} f (Suc k)"
    and p: "real_polynomial_function p"
    "t. t > 0  nth_derivative (Suc k) f t 1 = p t / t ^ (2 * Suc k) * exp (- inverse t)"
    by auto
  moreover
  have at_within_eq_at_right: "(at 0 within {0..}) = at_right (0::real)"
    apply (auto simp: filter_eq_iff eventually_at_filter )
     apply (simp add: eventually_mono)
    apply (simp add: eventually_mono)
    done
  have [simp]: "{0..} - {0} = {0::real<..}" by auto
  have [simp]: "(at (0::real) within {0..})  bot"
    by (auto simp: at_within_eq_bot_iff)
  have k_th_has_derivative_at_left:
    "((λx. nth_derivative k f x 1) has_derivative (λx. 0)) (at 0 within {..0})"
    apply (rule has_derivative_transform_within[OF _ zero_less_one])
      prefer 2
      apply force
     prefer 2
     apply (simp add: IH)
    by (rule derivative_intros)
  have k_th_has_derivative_at_right:
    "((λx. nth_derivative k f x 1) has_derivative (λx. 0)) (at 0 within {0..})"
    apply (rule has_derivative_transform_within[where
          f="λx'. if x' = 0 then 0 else pk x' / x' ^ (2 * k) * exp (- inverse x')", OF _ zero_less_one])
    subgoal
      unfolding has_derivative_def
      apply (auto simp: Lim_ident_at)
      apply (rule Lim_transform_eventually[where f="λx. (pk x * (exp (- inverse x) / x ^ (2 * k + 1)))"])
       apply (rule tendsto_eq_intros)
         apply (rule real_polynomial_function_tendsto[THEN tendsto_eq_rhs])
          apply fact
         apply (rule refl)
        apply (subst at_within_eq_at_right)
        apply (rule exp_inv_limit_0_right_gen)
      apply (auto simp add: eventually_at_filter divide_simps)
      done
    subgoal by force
    subgoal by (auto simp: IH(2) IH(4))
    done
  have k_th_has_derivative: "((λx. nth_derivative k f x 1) has_derivative (λx. 0)) (at 0)"
    apply (rule has_derivative_at_left_at_right')
     apply (rule k_th_has_derivative_at_left)
    apply (rule k_th_has_derivative_at_right)
    done
  have nth_Suc_zero: "nth_derivative (Suc k) f 0 1 = 0"
    apply (auto simp: frechet_derivative_nth_derivative_commute[symmetric])
    apply (subst frechet_derivative_at')
     apply (rule k_th_has_derivative)
    by simp
  moreover have "higher_differentiable_on UNIV f (Suc k)"
  proof -
    have "continuous_on UNIV (λx. nth_derivative (Suc k) f x 1)"
      unfolding continuous_on_eq_continuous_within
    proof
      fix x::real
      consider "x < 0" | "x > 0" | "x = 0" by arith
      then show "isCont (λx. nth_derivative (Suc k) f x 1) x"
      proof cases
        case 1
        then have at_eq: "at x = at x within {..<0}"
          using at_within_open[of x "{..<0}"] by auto
        show ?thesis
          unfolding at_eq
          apply (rule continuous_transform_within[OF _ zero_less_one])
          using neg0 1 by (auto simp: at_eq)
      next
        case 2
        then have at_eq: "at x = at x within {0<..}"
          using at_within_open[of x "{0<..}"] by auto
        show ?thesis
          unfolding at_eq
          apply (rule continuous_transform_within[OF _ zero_less_one])
          using p 2 by (auto intro!: continuous_intros
              intro: continuous_real_polymonial_function continuous_at_imp_continuous_within)
      next
        case 3
        have "((λx. nth_derivative (Suc k) f x 1)  0) (at_left 0)"
        proof -
          have "F x in at_left 0. 0 = nth_derivative (Suc k) f x 1"
            using neg0
            by (auto simp: eventually_at_filter)
          then show ?thesis
            by (blast intro: Lim_transform_eventually) 
        qed
        moreover have "((λx. nth_derivative (Suc k) f x 1)  0) (at_right 0)"
        proof -
          have "((λx. p x * (exp (- inverse x) / x ^ (2 * Suc k)))  0) (at_right 0)"
            apply (rule tendsto_eq_intros)
              apply (rule real_polynomial_function_tendsto)
              apply fact
             apply (rule exp_inv_limit_0_right_gen)
            by simp
          moreover
          have "F x in at_right 0. p x * (exp (- inverse x) / x ^ (2 * Suc k)) =
            nth_derivative (Suc k) f x 1"
            using p
            by (auto simp: eventually_at_filter)
          ultimately show ?thesis
            by (rule Lim_transform_eventually)
        qed
        ultimately show ?thesis
          by (auto simp: continuous_def nth_Suc_zero 3 filterlim_split_at
              simp del: nth_derivative.simps)
      qed
    qed
    moreover have "(λx. nth_derivative k f x 1) differentiable at x" for x
    proof -
      consider  "x = 0" | "x < 0" | "x > 0"by arith
      then show ?thesis
      proof cases
        case 1
        then show ?thesis
          using k_th_has_derivative by (auto simp: differentiable_def)
      next
        case 2
        with neg show ?thesis
          by (subst (asm) higher_differentiable_on_real_Suc') (auto simp: )
      next
        case 3
        with pos show ?thesis
          by (subst (asm) higher_differentiable_on_real_Suc') (auto simp: )
      qed
    qed
    moreover have "higher_differentiable_on UNIV f k" by fact
    ultimately
    show ?thesis
      by (subst higher_differentiable_on_real_Suc'[OF open_UNIV]) auto
  qed
  ultimately
  show ?case
    by (auto simp: less_eq_real_def)
qed

lemma f_smooth_on: "k-smooth_on S f"
  and f_higher_differentiable_on: "higher_differentiable_on S f n"
  using f_nth_derivative_cases
  by (auto simp: smooth_on_def higher_differentiable_on_subset[OF _ subset_UNIV])

lemma f_compose_smooth_on: "k-smooth_on S (λx. f (g x))"
  if "k-smooth_on S g" "open S"
  using smooth_on_compose[OF f_smooth_on that open_UNIV subset_UNIV]
  by (auto simp: o_def)

lemma f_nonneg: "f x  0"
  by (auto simp: f_def)

lemma f_pos_iff: "f x > 0  x > 0"
  by (auto simp: f_def)

lemma f_eq_zero_iff: "f x = 0  x  0"
  by (auto simp: f_def)


subsection ‹Cutoff function›

definition "h t = f (2 - t) / (f (2 - t) + f (t - 1))"

lemma denominator_pos: "f (2 - t) + f (t - 1) > 0"
  by (auto simp: f_def add_pos_pos)

lemma denominator_nonzero: "f (2 - t) + f (t - 1) = 0  False"
  using denominator_pos[of t] by auto

lemma h_range: "0  h t" "h t  1"
  by (auto simp: h_def f_nonneg denominator_pos)

lemma h_pos: "t < 2  0 < h t"
  and h_less_one: "1 < t  h t < 1"
  by (auto simp: h_def f_pos_iff denominator_pos)

lemma h_eq_0: "h t = 0" if "t  2"
  using that
  by (auto simp: h_def)

lemma h_eq_1: "h t = 1" if "t  1"
  using that
  by (auto simp: h_def f_eq_zero_iff)

lemma h_compose_smooth_on: "k-smooth_on S (λx. h (g x))"
  if "k-smooth_on S g" "open S"
  by (auto simp: h_def[abs_def] denominator_nonzero
      intro!: smooth_on_divide f_compose_smooth_on smooth_on_minus smooth_on_add
      that)


subsection ‹Bump function›

definition H::"_::real_inner  real" where "H x = h (norm x)"

lemma H_range: "0  H x" "H x  1"
  by (auto simp: H_def h_range)

lemma H_eq_one: "H x = 1" if "x  cball 0 1"
  using that
  by (auto simp: H_def h_eq_1)

lemma H_pos: "H x > 0" if "x  ball 0 2"
  using that
  by (auto simp: H_def h_pos)

lemma H_eq_zero: "H x = 0" if "x  ball 0 2"
  using that
  by (auto simp: H_def h_eq_0)

lemma H_neq_zeroD: "H x  0  x  ball 0 2"
  using H_eq_zero by blast

lemma H_smooth_on: "k-smooth_on UNIV H"
proof -
  have 1: "k-smooth_on (ball 0 1) H"
    by (rule smooth_on_cong[where g="λx. 1"]) (auto simp: H_eq_one)
  have 2: "k-smooth_on (UNIV - cball 0 (1/2)) H"
    by (auto simp: H_def[abs_def]
        intro!: h_compose_smooth_on smooth_on_norm)
  have O: "open (ball 0 1)" "open (UNIV - cball 0 (1 / 2))"
    by auto
  have *: "ball 0 1  (UNIV - cball 0 (1 / 2)) = UNIV" by (auto simp: mem_ball)
  from smooth_on_open_Un[OF 1 2 O, unfolded *]
  show ?thesis
    by (rule smooth_on_subset) auto
qed

lemma H_compose_smooth_on: "k-smooth_on S (λx. H (g x))" if "k-smooth_on S g" "open S"
  for g :: "_  _::euclidean_space"
  using smooth_on_compose[OF H_smooth_on that]
  by (auto simp: o_def)

end

end

Theory Chart

section ‹Charts›
theory Chart
  imports Analysis_More
begin

subsection ‹Definition›

text ‹A chart on M› is a homeomorphism from an open subset of M› to an open subset
  of some Euclidean space E›. Here d› and d'› are open subsets of M› and E›, respectively,
  f: d → d'› is the mapping, and f': d' → d› is the inverse mapping.›
typedef (overloaded) ('a::topological_space, 'e::euclidean_space) chart =
  "{(d::'a set, d'::'e set, f, f').
    open d  open d'  homeomorphism d d' f f'}"
  by (rule exI[where x="({}, {}, (λx. undefined), (λx. undefined))"]) simp

setup_lifting type_definition_chart

lift_definition apply_chart::"('a::topological_space, 'e::euclidean_space) chart  'a  'e"
  is "λ(d, d', f, f'). f" .

declare [[coercion apply_chart]]

lift_definition inv_chart::"('a::topological_space, 'e::euclidean_space) chart  'e  'a"
  is "λ(d, d', f, f'). f'" .

lift_definition domain::"('a::topological_space, 'e::euclidean_space) chart  'a set"
  is "λ(d, d', f, f'). d" .

lift_definition codomain::"('a::topological_space, 'e::euclidean_space) chart  'e set"
  is "λ(d, d', f, f'). d'" .


subsection ‹Properties›

lemma open_domain[intro, simp]: "open (domain c)"
  and open_codomain[intro, simp]: "open (codomain c)"
  and chart_homeomorphism: "homeomorphism (domain c) (codomain c) c (inv_chart c)"
  by (transfer, auto)+

lemma at_within_domain: "at x within domain c = at x" if "x  domain c"
  by (rule at_within_open[OF that open_domain])

lemma at_within_codomain: "at x within codomain c = at x" if "x  codomain c"
  by (rule at_within_open[OF that open_codomain])

lemma
  chart_in_codomain[intro, simp]: "x  domain c  c x  codomain c"
  and inv_chart_inverse[simp]: "x  domain c  inv_chart c (c x) = x"
  and inv_chart_in_domain[intro, simp]:"y  codomain c  inv_chart c y  domain c"
  and chart_inverse_inv_chart[simp]: "y  codomain c  c (inv_chart c y) = y"
  and image_domain_eq: "c ` (domain c) = codomain c"
  and inv_image_codomain_eq[simp]: "inv_chart c ` (codomain c) = domain c"
  and continuous_on_domain: "continuous_on (domain c) c"
  and continuous_on_codomain: "continuous_on (codomain c) (inv_chart c)"
  using chart_homeomorphism[of c]
  by (auto simp: homeomorphism_def)

lemma chart_eqI: "c = d"
  if "domain c = domain d"
    "codomain c = codomain d"
    "x. c x = d x"
    "x. inv_chart c x = inv_chart  d x"
  using that
  by transfer auto

lemmas continuous_on_chart[continuous_intros] =
  continuous_on_compose2[OF continuous_on_domain]
  continuous_on_compose2[OF continuous_on_codomain]

lemma continuous_apply_chart: "continuous (at x within X) c" if "x  domain c"
  apply (rule continuous_at_imp_continuous_within)
  using continuous_on_domain[of c] that at_within_domain[OF that]
  by (auto simp: continuous_on_eq_continuous_within)

lemma continuous_inv_chart: "continuous (at x within X) (inv_chart c)" if "x  codomain c"
  apply (rule continuous_at_imp_continuous_within)
  using continuous_on_codomain[of c] that at_within_codomain[OF that]
  by (auto simp: continuous_on_eq_continuous_within)

lemmas apply_chart_tendsto[tendsto_intros] = isCont_tendsto_compose[OF continuous_apply_chart, rotated]
lemmas inv_chart_tendsto[tendsto_intros] = isCont_tendsto_compose[OF continuous_inv_chart, rotated]

lemma continuous_within_compose2':
  "continuous (at (f x) within t) g  f ` s  t 
    continuous (at x within s) f 
    continuous (at x within s) (λx. g (f x))"
  by (simp add: continuous_within_compose2 continuous_within_subset)

lemmas continuous_chart[continuous_intros] =
  continuous_within_compose2'[OF continuous_apply_chart]
  continuous_within_compose2'[OF continuous_inv_chart]

lemma continuous_on_chart_inv:
  assumes "continuous_on s (apply_chart c o f)"
    "f ` s  domain c"
  shows "continuous_on s f"
proof -
  have "continuous_on s (inv_chart c o apply_chart c o f)"
    using assms by (auto intro!: continuous_on_chart(2))
  moreover have "x. x  s  (inv_chart c o apply_chart c o f) x = f x"
    using assms by auto
  ultimately show ?thesis by auto
qed

lemma continuous_on_chart_inv':
  assumes "continuous_on (apply_chart c ` s) (f o inv_chart c)"
    "s  domain c"
  shows "continuous_on s f"
proof -
  have "continuous_on s (apply_chart c)"
    using assms continuous_on_domain continuous_on_subset by blast
  then have "continuous_on s (f o inv_chart c o apply_chart c)"
    apply (rule continuous_on_compose) using assms by auto
  moreover have "(f o inv_chart c o apply_chart c) x = f x" if "x  s" for x
    using assms that by auto
  ultimately show ?thesis by auto
qed

lemma inj_on_apply_chart: "inj_on (apply_chart f) (domain f)"
  by (auto simp: intro!: inj_on_inverseI[where g="inv_chart f"])

lemma apply_chart_Int: "f ` (X  Y) = f ` X  f ` Y" if "X  domain f" "Y  domain f"
  using inj_on_apply_chart that
  by (rule inj_on_image_Int)

lemma chart_image_eq_vimage: "c ` X = inv_chart c -` X  codomain c"
  if "X  domain c"
  using that
  by force

lemma open_chart_image[simp, intro]: "open (c ` X)"
  if "open X" "X  domain c"
proof -
  have "c ` X = inv_chart c -` X  codomain c"
    using that(2)
    by (rule chart_image_eq_vimage)
  also have "open "
    using that
    by (metis continuous_on_codomain continuous_on_open_vimage open_codomain)
  finally show ?thesis .
qed

lemma open_inv_chart_image[simp, intro]: "open (inv_chart c ` X)"
  if "open X" "X  codomain c"
proof -
  have "inv_chart c ` X = c -` X  domain c"
    using that(2)
    apply (auto simp: )
    using image_iff by fastforce
  also have "open "
    using that
    by (metis continuous_on_domain continuous_on_open_vimage open_domain)
  finally show ?thesis .
qed

lemma homeomorphism_UNIV_imp_open_map:
  "homeomorphism UNIV UNIV p p'  open f'  open (p ` f')"
  by (auto dest: homeomorphism_imp_open_map[where U=f'])


subsection ‹Restriction›

lemma homeomorphism_restrict:
  "homeomorphism (a  s) (b  f' -` s) f f'" if "homeomorphism a b f f'"
  using that
  by (fastforce simp: homeomorphism_def intro: continuous_on_subset intro!: imageI)

lift_definition restrict_chart::"'a set  ('a::t2_space, 'e::euclidean_space) chart  ('a, 'e) chart"
  is "λS. λ(d, d', f, f'). if open S then (d  S, d'  f' -` S, f, f') else ({}, {}, f, f')"
  by (auto simp: split: if_splits intro!: open_continuous_vimage' homeomorphism_restrict
      intro: homeomorphism_cont2 homeomorphism_cont1 )

lemma restrict_chart_restrict_chart:
  "restrict_chart X (restrict_chart Y c) = restrict_chart (X  Y) c"
  if "open X" "open Y"
  using that
  by transfer auto

lemma domain_restrict_chart[simp]: "open S  domain (restrict_chart S c) = domain c  S"
  and domain_restrict_chart_if: "domain (restrict_chart S c) = (if open S then domain c  S else {})"
  and codomain_restrict_chart[simp]: "open S  codomain (restrict_chart S c) = codomain c  inv_chart c -` S"
  and codomain_restrict_chart_if: "codomain (restrict_chart S c) = (if open S then codomain c  inv_chart c -` S else {})"
  and apply_chart_restrict_chart[simp]: "apply_chart (restrict_chart S c) = apply_chart c"
  and inv_chart_restrict_chart[simp]: "inv_chart (restrict_chart S c) = inv_chart c"
  by (transfer, auto)+


subsection ‹Composition›

lift_definition compose_chart::"('e'e)  ('e'e) 
  ('a::topological_space, 'e::euclidean_space) chart  ('a, 'e) chart"
  is "λp p'. λ(d, d', f, f'). if homeomorphism UNIV UNIV p p' then (d, p ` d', p o f, f' o p')
    else ({}, {}, f, f')"
  by (auto split: if_splits)
    (auto intro: homeomorphism_UNIV_imp_open_map homeomorphism_compose homeomorphism_of_subsets)

lemma compose_chart_apply_chart[simp]: "apply_chart (compose_chart p p' c) = p o apply_chart c"
  and compose_chart_inv_chart[simp]: "inv_chart (compose_chart p p' c) = inv_chart c o p'"
  and domain_compose_chart[simp]: "domain (compose_chart p p' c) = domain c"
  and codomain_compose_chart[simp]: "codomain (compose_chart p p' c) = p ` codomain c"
  if "homeomorphism UNIV UNIV p p'"
  using that by (transfer, auto)+

end

Theory Topological_Manifold

section ‹Topological Manifolds›
theory Topological_Manifold
  imports Chart
begin

text ‹Definition of topological manifolds. Existence of locally finite cover.›

subsection ‹Defintition›

text ‹We define topological manifolds as a second-countable Hausdorff space, where
  every point in the carrier set has a neighborhood that is homeomorphic to an open
  subset of the Euclidean space. Here topological manifolds are specified by a set
  of charts, and the carrier set is simply defined to be the union of the domain of
  the charts.›
locale manifold =
  fixes charts::"('a::{second_countable_topology, t2_space}, 'e::euclidean_space) chart set"
begin

definition "carrier = ((domain ` charts))"

lemma open_carrier[intro, simp]: "open carrier"
  by (auto simp: carrier_def)

lemma carrierE:
  assumes "x  carrier"
  obtains c where "c  charts" "x  domain c"
  using assms by (auto simp: carrier_def)

lemma domain_subset_carrier[simp]: "domain c  carrier" if "c  charts"
  using that
  by (auto simp: carrier_def)

lemma in_domain_in_carrier[intro, simp]: "c  charts  x  domain c  x  carrier"
  by (auto simp: carrier_def)


subsection ‹Existence of locally finite cover›

text ‹Every point has a precompact neighborhood.›
lemma precompact_neighborhoodE:
  assumes "x  carrier"
  obtains C where "x  C" "open C" "compact (closure C)" "closure C  carrier"
proof -
  from carrierE[OF assms] obtain c where c: "c  charts" "x  domain c" by auto
  then have "c x  codomain c" by auto
  with open_contains_cball[of "codomain c"]
  obtain e where e: "0 < e" "cball (apply_chart c x) e  codomain c"
    by auto
  then have e': "ball (apply_chart c x) e  codomain c"
    by (auto simp: mem_ball)
  define C where "C = inv_chart c ` ball (c x) e"
  have "x  C"
    using c e > 0
    unfolding C_def
    by (auto intro!: image_eqI[where x="apply_chart c x"])
  moreover have "open C"
    using e'
    by (auto simp: C_def)
  moreover
  have compact: "compact (inv_chart c ` cball (apply_chart c x) e)"
    using e
    by (intro compact_continuous_image continuous_on_chart) auto
  have closure_subset: "closure C  inv_chart c ` cball (apply_chart c x) e"
    apply (rule closure_minimal)
    subgoal by (auto simp: C_def mem_ball)
    subgoal by (rule compact_imp_closed) (rule compact)
    done
  have "compact (closure C)"
    apply (rule compact_if_closed_subset_of_compact[where T="inv_chart c ` cball (c x) e"])
    subgoal by simp
    subgoal by (rule compact)
    subgoal by (rule closure_subset)
    done
  moreover have "closure C  carrier"
    using closure_subset c e
    by force
  ultimately show ?thesis ..
qed

text ‹There exists a covering of the carrier by precompact sets.›
lemma precompact_open_coverE:
  obtains U::"nat'a set"
  where "(i. U i) = carrier" "i. open (U i)" "i. compact (closure (U i))"
    "i. closure (U i)  carrier"
proof cases
  assume "carrier = {}"
  then have "(i. {}) = carrier" "open {}" "compact (closure {})" "closure {}  carrier"
    by auto
  then show ?thesis ..
next
  assume "carrier  {}"
  have "b. x  b  open b  compact (closure b)  closure b  carrier" if "x  carrier" for x
    using that
    by (rule precompact_neighborhoodE) auto
  then obtain balls where balls:
    "x. x  carrier  x  balls x"
    "x. x  carrier  open (balls x)"
    "x. x  carrier  compact (closure (balls x))"
    "x. x  carrier  closure (balls x)  carrier"
    by metis
  let ?balls = "balls ` carrier"
  have *: "x::'a set. x  ?balls  open x" by (auto simp: balls)
  from Lindelof[of "?balls", OF this]
  obtain