Library HoTT.Homotopy.Join.TriJoin

Induction and recursion principles for the triple join

We show that the triple join satisfies symmetrical induction and recursion principles and prove that the recursion principle gives an equivalence of 0-groupoids. We use this in JoinAssoc.v to prove that the join is associative. Our approach parallels what is done in the two-variable case in Join/Core.v, especially starting with TriJoinRecData here and JoinRecData there. That case is much simpler, so should be read first.

Section TriJoinStructure.
  Context {A B C : Type}.

  Definition TriJoin := Join A (Join B C).

  Definition join1 : A TriJoin := joinl.
  Definition join2 : B TriJoin := fun b ⇒ (joinr (joinl b)).
  Definition join3 : C TriJoin := fun c ⇒ (joinr (joinr c)).
  Definition join12 : a b, join1 a = join2 b := fun a bjglue a (joinl b).
  Definition join13 : a c, join1 a = join3 c := fun a cjglue a (joinr c).
  Definition join23 : b c, join2 b = join3 c := fun b cap joinr (jglue b c).
  Definition join123 : a b c, join12 a b @ join23 b c = join13 a c := fun a b ctriangle_v a (jglue b c).
End TriJoinStructure.

Arguments TriJoin A B C : clear implicits.

ap_trijoin and ap_trijoin_transport

Functions send triangles to triangles.
Definition ap_triangle {X Y} (f : X Y)
  {a b c : X} {ab : a = b} {bc : b = c} {ac : a = c} (abc : ab @ bc = ac)
  : ap f ab @ ap f bc = ap f ac
  := (ap_pp f ab bc)^ @ ap (ap f) abc.

This general result abstracts away the situation where J is TriJoin A B C, a is joinl a' for some a', jr is joinr : Join B C J, jg is fun w jglue a' w, and p is jglue b c. By working in this generality, we can do induction on p. This also allows us to inline the proof of triangle_v.
Definition ap_trijoin_general {J W P : Type} (f : J P)
  (a : J) (jr : W J) (jg : w, a = jr w)
  {b c : W} (p : b = c)
  : ap f (jg b) @ ap f (ap jr p) = ap f (jg c).
Proof.
  apply ap_triangle.
  induction p.
  apply concat_p1.
Defined.

Functions send the canonical triangles in triple joins to triangles.
Definition ap_trijoin {A B C P : Type} (f : TriJoin A B C P)
  (a : A) (b : B) (c : C)
  : ap f (join12 a b) @ ap f (join23 b c) = ap f (join13 a c).
Proof.
  nrapply ap_trijoin_general.
Defined.

Definition ap_trijoin_general_transport {J W P : Type} (f : J P)
  (a : J) (jr : W J) (jg : w, a = jr w)
  {b c : W} (p : b = c)
  : ap_trijoin_general f a jr jg p
    = (1 @@ ap_compose _ f _)^ @ (transport_paths_Fr _ _)^ @ apD (fun xap f (jg x)) p.
Proof.
  induction p.
  unfold ap_trijoin_general; simpl.
  induction (jg b).
  reflexivity.
Defined.

Definition ap_trijoin_transport {A B C P : Type} (f : TriJoin A B C P)
  (a : A) (b : B) (c : C)
  : ap_trijoin f a b c
    = (1 @@ ap_compose _ f _)^ @ (transport_paths_Fr _ _)^ @ apD (fun xap f (jglue a x)) (jglue b c).
Proof.
  nrapply ap_trijoin_general_transport.
Defined.

Definition ap_trijoin_general_V {J W P : Type} (f : J P)
  (a : J) (jr : W J) (jg : w, a = jr w)
  {b c : W} (p : b = c)
  : ap_trijoin_general f a jr jg p^
     = (1 @@ (ap (ap f) (ap_V jr p) @ ap_V f _)) @ moveR_pV _ _ _ (ap_trijoin_general f a jr jg p)^.
Proof.
  induction p.
  unfold ap_trijoin_general; cbn.
  by induction (jg b).
Defined.

Definition ap_trijoin_V {A B C P : Type} (f : TriJoin A B C P)
  (a : A) (b : B) (c : C)
  : ap_triangle f (triangle_v a (jglue b c)^)
     = (1 @@ (ap (ap f) (ap_V joinr _) @ ap_V f _)) @ moveR_pV _ _ _ (ap_trijoin f a b c)^.
Proof.
  nrapply ap_trijoin_general_V.
Defined.

The induction principle for the triple join

A lemma that handles the path algebra in the final step.
Local Definition trijoin_ind_helper {A BC : Type} (P : Join A BC Type)
  (a : A) (b c : BC) (bc : b = c)
  (j1' : P (joinl a)) (j2' : P (joinr b)) (j3' : P (joinr c))
  (j12' : jglue a b # j1' = j2') (j13' : jglue a c # j1' = j3') (j23' : (ap joinr bc) # j2' = j3')
  (j123' : transport_pp _ (jglue a b) (ap joinr bc) j1' @ ap (transport _ (ap joinr bc)) j12' @ j23'
           = transport2 _ (triangle_v a bc) _ @ j13')
  : ((apD (fun x : BCtransport P (jglue a x) j1') bc)^
       @ ap (transport (fun x : BCP (joinr x)) bc) j12')
      @ ((transport_compose P joinr bc j2') @ j23') = j13'.
Proof.
  induction bc; simpl.
  rewrite transport_pp_1 in j123'.
  cbn in ×.
  unfold transport; unfold transport in j123'.
  rewrite ap_idmap; rewrite ap_idmap in j123'.
  rewrite concat_pp_p in j123'.
  apply cancelL in j123'.
  rewrite 2 concat_1p.
  exact j123'.
Qed.

An induction principle for the triple join. Note that the hypotheses are phrased completely in terms of the "constructors" of TriJoin A B C.
Definition trijoin_ind (A B C : Type) (P : TriJoin A B C Type)
  (join1' : a, P (join1 a))
  (join2' : b, P (join2 b))
  (join3' : c, P (join3 c))
  (join12' : a b, join12 a b # join1' a = join2' b)
  (join13' : a c, join13 a c # join1' a = join3' c)
  (join23' : b c, join23 b c # join2' b = join3' c)
  (join123' : a b c, transport_pp _ (join12 a b) (join23 b c) (join1' a)
                         @ ap (transport _ (join23 b c)) (join12' a b) @ join23' b c
                       = transport2 _ (join123 a b c) _ @ join13' a c)
  : x, P x.
Proof.
  snrapply Join_ind.
  - exact join1'.
  - snrapply Join_ind.
    + exact join2'.
    + exact join3'.
    + intros b c.
      lhs rapply (transport_compose P).
      apply join23'.
  - intro a.
    snrapply Join_ind.
    + simpl. exact (join12' a).
    + simpl. exact (join13' a).
    + intros b c; cbn beta zeta.
      lhs nrapply (transport_paths_FlFr_D (jglue b c)).
      lhs nrapply (1 @@ _).
      1: nrapply Join_ind_beta_jglue.
      apply trijoin_ind_helper, join123'.
Defined.

The recursion principle for the triple join, and many results about it

We'll bundle up the arguments into a record.
Record TriJoinRecData {A B C P : Type} := {
    j1 : A P;
    j2 : B P;
    j3 : C P;
    j12 : a b, j1 a = j2 b;
    j13 : a c, j1 a = j3 c;
    j23 : b c, j2 b = j3 c;
    j123 : a b c, j12 a b @ j23 b c = j13 a c;
  }.

Arguments TriJoinRecData : clear implicits.
Arguments Build_TriJoinRecData {A B C P}%type_scope (j1 j2 j3 j12 j13 j23 j123)%function_scope.

Definition trijoin_rec {A B C P : Type} (f : TriJoinRecData A B C P)
  : TriJoin A B C $-> P.
Proof.
  snrapply Join_rec.
  - exact (j1 f).
  - snrapply Join_rec.
    + exact (j2 f).
    + exact (j3 f).
    + exact (j23 f).
  - intro a.
    snrapply Join_ind; cbn beta.
    + exact (j12 f a).
    + exact (j13 f a).
    + intros b c.
      lhs nrapply transport_paths_Fr.
      exact (1 @@ Join_rec_beta_jglue _ _ _ _ _ @ j123 f a b c).
Defined.

Beta rules for the recursion principle.
Definition trijoin_rec_beta_join12 {A B C P : Type} (f : TriJoinRecData A B C P) (a : A) (b : B)
  : ap (trijoin_rec f) (join12 a b) = j12 f a b
  := Join_rec_beta_jglue _ _ _ _ _.

Definition trijoin_rec_beta_join13 {A B C P : Type} (f : TriJoinRecData A B C P) (a : A) (c : C)
  : ap (trijoin_rec f) (join13 a c) = j13 f a c
  := Join_rec_beta_jglue _ _ _ _ _.

Definition trijoin_rec_beta_join23 {A B C P : Type} (f : TriJoinRecData A B C P) (b : B) (c : C)
  : ap (trijoin_rec f) (join23 b c) = j23 f b c.
Proof.
  unfold trijoin_rec, join23.
  lhs_V nrapply (ap_compose joinr); simpl.
  apply Join_rec_beta_jglue.
Defined.

Local Lemma trijoin_rec_beta_join123_helper {A : Type} {x y z : A}
  {u0 u1 : x = y} {p0 p1 r1 : y = z} {q0 s1 t1 : x = z}
  (p : p0 = p1) (q : q0 = u0 @ p0) (r : p0 = r1)
  (s : u1 @ r1 = s1) (t : s1 = t1) (u : u0 = u1)
  : ((1 @@ p)^ @ q^) @ (((q @ (u @@ 1)) @ ((1 @@ r) @ s)) @ t)
    = ((u @@ (p^ @ r)) @ s) @ t.
Proof.
  induction u, t, s, r, p.
  revert q0 q; by apply paths_ind_r.
Defined.

Definition trijoin_rec_beta_join123 {A B C P : Type} (f : TriJoinRecData A B C P)
  (a : A) (b : B) (c : C)
  : ap_trijoin (trijoin_rec f) a b c
    = (trijoin_rec_beta_join12 f a b @@ trijoin_rec_beta_join23 f b c)
        @ j123 f a b c @ (trijoin_rec_beta_join13 f a c)^.
Proof.
  (* Expand the LHS: *)
  lhs nrapply ap_trijoin_transport.
  rewrite (apD_homotopic (Join_rec_beta_jglue _ _ _ _) (jglue b c)).
  rewrite Join_ind_beta_jglue.
  (* Change ap (transport __) _ on LHS. *)
  rewrite (concat_p_pp _ (transport_paths_Fr (jglue b c) (j12 f a b)) _).
  rewrite (concat_Ap (transport_paths_Fr (jglue b c))).
  (* Everything that remains is pure path algebra. *)
  (* trijoin_rec_beta_join23 expands to something of the form p^ @ r, so that's what is in the lemma.  One can unfold it to see this, but the Qed is a bit faster without this: *)
  (* unfold trijoin_rec_beta_join23. *)
  (* Note that one of the aps on the LHS computes to u @@ 1, so that's what is in the lemma: *)
  (* change (ap (fun q => q @ ?x) ?u) with (u @@ @idpath _ x). *)
  nrapply trijoin_rec_beta_join123_helper.
Qed.

We're next going to define a map in the other direction. We do it via showing that TriJoinRecData is a 0-coherent 1-functor to Type. We'll later show that it is a 1-functor to 0-groupoids.
Definition trijoinrecdata_fun {A B C P Q : Type} (g : P Q) (f : TriJoinRecData A B C P)
  : TriJoinRecData A B C Q.
Proof.
  snrapply Build_TriJoinRecData.
  - exact (g o j1 f).
  - exact (g o j2 f).
  - exact (g o j3 f).
  - exact (fun a bap g (j12 f a b)).
  - exact (fun a cap g (j13 f a c)).
  - exact (fun b cap g (j23 f b c)).
  - intros a b c; cbn beta.
    exact (ap_triangle g (j123 f a b c)).
  (* The last four goals above can also be handled by the induction tactics below, but it's useful to be concrete. *)
Defined.

The triple join itself has canonical TriJoinRecData.
Combining these gives a function going in the opposite direction to trijoin_rec.
Definition trijoin_rec_inv {A B C P : Type} (f : TriJoin A B C P)
  : TriJoinRecData A B C P
  := trijoinrecdata_fun f (trijoinrecdata_trijoin A B C).

Under Funext, trijoin_rec and trijoin_rec_inv should be inverse equivalences. We'll avoid Funext and show that they are equivalences of 0-groupoids, where we choose the path structures carefully.

The graph structure on TriJoinRecData A B C P

The type of fillers for a triangular prism with five 2d faces abc, abc', k12, k13, k23.
Definition prism {P : Type}
  {a b c : P} {ab : a = b} {ac : a = c} {bc : b = c} (abc : ab @ bc = ac)
  {a' b' c' : P} {ab' : a' = b'} {ac' : a' = c'} {bc' : b' = c'} (abc' : ab' @ bc' = ac')
  {k1 : a = a'} {k2 : b = b'} {k3 : c = c'}
  (k12 : ab @ k2 = k1 @ ab') (k13 : ac @ k3 = k1 @ ac') (k23 : bc @ k3 = k2 @ bc')
  := concat_p_pp _ _ _ @ whiskerR abc k3 @ k13
    = whiskerL ab k23
    @ concat_p_pp _ _ _ @ whiskerR k12 bc'
    @ concat_pp_p _ _ _ @ whiskerL k1 abc'.

The "identity" filler is slightly non-trivial, because the fillers for the squares, e.g. ab @ 1 = 1 @ ab, must be non-trivial.
Definition prism_id {P : Type}
  {a b c : P} {ab : a = b} {ac : a = c} {bc : b = c} (abc : ab @ bc = ac)
  : prism abc abc (equiv_p1_1q idpath) (equiv_p1_1q idpath) (equiv_p1_1q idpath).
Proof.
  induction ab, bc, abc; simpl.
  reflexivity.
Defined.

The paths between elements of TriJoinRecData A B C P. Under Funext, this type will be equivalent to the identity type. But without Funext, this definition will be more useful.
Record TriJoinRecPath {A B C P : Type} {f g : TriJoinRecData A B C P} := {
    h1 : a, j1 f a = j1 g a;
    h2 : b, j2 f b = j2 g b;
    h3 : c, j3 f c = j3 g c;
    h12 : a b, j12 f a b @ h2 b = h1 a @ j12 g a b;
    h13 : a c, j13 f a c @ h3 c = h1 a @ j13 g a c;
    h23 : b c, j23 f b c @ h3 c = h2 b @ j23 g b c;
    h123 : a b c, prism (j123 f a b c) (j123 g a b c) (h12 a b) (h13 a c) (h23 b c);
  }.

Arguments TriJoinRecPath {A B C P} f g.

We also define data for trijoin_rec that unbundles the first three components. This lets us talk about paths between two such when the first three components are definitionally equal. This is a common special case, and this set-up greatly simplifies a lot of path algebra in later proofs.
Record TriJoinRecData' {A B C P : Type} {j1' : A P} {j2' : B P} {j3' : C P} := {
    j12' : a b, j1' a = j2' b;
    j13' : a c, j1' a = j3' c;
    j23' : b c, j2' b = j3' c;
    j123' : a b c, j12' a b @ j23' b c = j13' a c;
  }.

Arguments TriJoinRecData' {A B C P} j1' j2' j3'.
Arguments Build_TriJoinRecData' {A B C P}%type_scope
  (j1' j2' j3' j12' j13' j23' j123')%function_scope.

Definition prism' {P : Type} {a b c : P}
  {ab : a = b} {ac : a = c} {bc : b = c} (abc : ab @ bc = ac)
  {ab' : a = b} {ac' : a = c} {bc' : b = c} (abc' : ab' @ bc' = ac')
  (k12 : ab = ab') (k13 : ac = ac') (k23 : bc = bc')
  := abc @ k13 = (k12 @@ k23) @ abc'.

Record TriJoinRecPath' {A B C P : Type} {j1' : A P} {j2' : B P} {j3' : C P}
  {f g : TriJoinRecData' j1' j2' j3'} := {
    h12' : a b, j12' f a b = j12' g a b;
    h13' : a c, j13' f a c = j13' g a c;
    h23' : b c, j23' f b c = j23' g b c;
    h123' : a b c, prism' (j123' f a b c) (j123' g a b c) (h12' a b) (h13' a c) (h23' b c);
  }.

Arguments TriJoinRecPath' {A B C P} {j1' j2' j3'} f g.

We can bundle and unbundle these types of data. For unbundling, we just handle TriJoinRecData for now.

Definition bundle_trijoinrecdata {A B C P : Type} {j1' : A P} {j2' : B P} {j3' : C P}
  (f : TriJoinRecData' j1' j2' j3')
  : TriJoinRecData A B C P
  := Build_TriJoinRecData j1' j2' j3' (j12' f) (j13' f) (j23' f) (j123' f).

Definition unbundle_trijoinrecdata {A B C P : Type} (f : TriJoinRecData A B C P)
  : TriJoinRecData' (j1 f) (j2 f) (j3 f)
  := Build_TriJoinRecData' (j1 f) (j2 f) (j3 f) (j12 f) (j13 f) (j23 f) (j123 f).

The proof by induction that is easily available to us here is what saves work in more complicated contexts.
Definition bundle_prism {P : Type} {a b c : P}
  {ab : a = b} {ac : a = c} {bc : b = c} (abc : ab @ bc = ac)
  {ab' : a = b} {ac' : a = c} {bc' : b = c} (abc' : ab' @ bc' = ac')
  (k12 : ab = ab') (k13 : ac = ac') (k23 : bc = bc')
  (k123 : prism' abc abc' k12 k13 k23)
  : prism abc abc' (equiv_p1_1q k12) (equiv_p1_1q k13) (equiv_p1_1q k23).
Proof.
  induction ab.
  induction bc.
  induction abc.
  induction k12.
  induction k23.
  induction k13.
  unfold prism' in k123.
  induction (moveR_Vp _ _ _ k123); clear k123.
  simpl.
  reflexivity.
Defined.

Definition bundle_trijoinrecpath {A B C P : Type} {j1' : A P} {j2' : B P} {j3' : C P}
  {f g : TriJoinRecData' j1' j2' j3'} (h : TriJoinRecPath' f g)
  : TriJoinRecPath (bundle_trijoinrecdata f) (bundle_trijoinrecdata g).
Proof.
  snrapply Build_TriJoinRecPath.
  1, 2, 3: reflexivity.
  1, 2, 3: intros; apply equiv_p1_1q.
  - apply (h12' h).
  - apply (h13' h).
  - apply (h23' h).
  - cbn beta zeta.
    intros a b c.
    apply bundle_prism, (h123' h).
Defined.

A tactic that helps us apply the previous result.
Ltac bundle_trijoinrecpath :=
  hnf;
  match goal with |- TriJoinRecPath ?F ?G
    refine (bundle_trijoinrecpath (f:=unbundle_trijoinrecdata F)
                                  (g:=unbundle_trijoinrecdata G) _) end;
  snrapply Build_TriJoinRecPath'.

Using these paths, we can restate the beta rule for trijoin_rec. The statement using TriJoinRecPath' typechecks only because trijoin_rec computes definitionally on the path constructors.
Definition trijoin_rec_beta' {A B C P : Type} (f : TriJoinRecData A B C P)
  : TriJoinRecPath' (unbundle_trijoinrecdata (trijoin_rec_inv (trijoin_rec f)))
                    (unbundle_trijoinrecdata f).
Proof.
  snrapply Build_TriJoinRecPath'; cbn.
  - apply trijoin_rec_beta_join12.
  - apply trijoin_rec_beta_join13.
  - apply trijoin_rec_beta_join23.
  - intros a b c.
    unfold prism'.
    apply moveR_pM.
    nrapply trijoin_rec_beta_join123.
Defined.

We can upgrade this to an unprimed path. This says that trijoin_rec_inv is split surjective.

trijoin_rec_inv is an injective map between 0-groupoids

We begin with a general purpose lemma.
Definition triangle_ind {P : Type} (a : P)
  (Q : (b c : P) (ab : a = b) (ac : a = c) (bc : b = c) (abc : ab @ bc = ac), Type)
  (s : Q a a idpath idpath idpath idpath)
  : b c ab ac bc abc, Q b c ab ac bc abc.
Proof.
  intros.
  induction ab.
  induction bc.
  induction abc.
  apply s.
Defined.

This lemma handles the path algebra in the last goal of the next result.
Local Definition isinj_trijoin_rec_inv_helper {J P : Type} {f g : J P}
  {a b c : J} {ab : a = b} {ac : a = c} {bc : b = c} {abc : ab @ bc = ac}
  {H1 : f a = g a} {H2 : f b = g b} {H3 : f c = g c}
  {H12 : ap f ab @ H2 = H1 @ ap g ab}
  {H13 : ap f ac @ H3 = H1 @ ap g ac}
  {H23 : ap f bc @ H3 = H2 @ ap g bc}
  (H123 : prism (ap_triangle f abc) (ap_triangle g abc) H12 H13 H23)
  : (transport_pp (fun xf x = g x) ab bc H1 @ ap (transport (fun xf x = g x) bc)
       (transport_paths_FlFr' ab H1 H2 H12)) @ transport_paths_FlFr' bc H2 H3 H23
    = transport2 (fun xf x = g x) abc H1 @ transport_paths_FlFr' ac H1 H3 H13.
Proof.
  revert b c ab ac bc abc H2 H3 H12 H13 H23 H123.
  nrapply triangle_ind; cbn.
  unfold ap_triangle, transport_paths_FlFr', transport; cbn -[concat_pp_p].
  generalize dependent (f a); intro fa; clear f.
  generalize dependent (g a); intro ga; clear g a.
  intros H1 H2 H3 H12 H13 H23.
  rewrite ap_idmap.
  revert H12; equiv_intro (equiv_1p_q1 (p:=H2) (q:=H1)) H12'; induction H12'.
  revert H13; equiv_intro (equiv_1p_q1 (p:=H3) (q:=H2)) H13'; induction H13'.
  induction H3.
  intro H123.
  unfold prism in H123.
  rewrite whiskerL_1p_1 in H123.
  cbn in ×.
  rewrite ! concat_p1 in H123.
  induction H123.
  reflexivity.
Qed.

trijoin_rec_inv is essentially injective, as a map between 0-groupoids.
Definition isinj_trijoin_rec_inv {A B C P : Type} {f g : TriJoin A B C P}
  (h : TriJoinRecPath (trijoin_rec_inv f) (trijoin_rec_inv g))
  : f == g.
Proof.
  snrapply trijoin_ind.
  1: apply (h1 h).
  1: apply (h2 h).
  1: apply (h3 h).
  1, 2, 3: intros; nrapply transport_paths_FlFr'.
  1: apply (h12 h).
  1: apply (h13 h).
  1: apply (h23 h).
  intros a b c; cbn beta.
  apply isinj_trijoin_rec_inv_helper.
  exact (h123 h a b c).
Defined.

Lemmas and tactics about triangles and prisms

We now introduce several lemmas and tactics that will dispense with some routine goals. The idea is that a generic triangle can be assumed to be trivial on the first vertex, and a generic prism can be assumed to be the identity on the domain. In order to apply the triangle_ind and prism_ind lemmas that make this precise, we need to generalize various terms in the goal.
This destructs a seven component term f, tries to generalize each piece evaluated appropriately, and clears all pieces. If called with a, b and c all valid terms, we expect all seven components to be generalized. But one can also call it with one of a, b and c a dummy value (e.g. _X_) that causes four of the generalize tactics to fail. In this case, four components will be simply cleared, and three will be generalized and cleared, so this applies when the goal only depends on three of the seven components.
Ltac generalize_some f a b c :=
  let f1 := fresh in let f2 := fresh in let f3 := fresh in
  let f12 := fresh in let f13 := fresh in let f23 := fresh in
  let f123 := fresh in
  destruct f as [f1 f2 f3 f12 f13 f23 f123]; cbn;
  try generalize (f123 a b c); clear f123;
  try generalize (f23 b c); clear f23;
  try generalize (f13 a c); clear f13;
  try generalize (f12 a b); clear f12;
  try generalize (f3 c); clear f3;
  try generalize (f2 b); clear f2;
  try generalize (f1 a); clear f1.
  (* No easy way to skip the "last" one, since we don't know which will be the last to be generalized. *)

Use this with f : TriJoinRecData A B C P, a : A, b : B, c : C.
Ltac triangle_ind f a b c :=
  generalize_some f a b c;
  intro f; (* generalize_some goes one step too far, so intro the last variable. *)
  apply triangle_ind.

Use this with f : TriJoinRecData A B C P. Two of the arguments a, b and c should be elements of A, B and C, respectively, and the third should be a dummy value (e.g. _X_) that causes the generalize tactic to fail. It applies to goals that only depend on the components of f involving just two of A, B and C.
Ltac triangle_ind_two f a b c :=
  generalize_some f a b c;
  intro f; (* generalize_some goes one step too far, so intro the last variable. *)
  apply paths_ind.

The prism analog of the function triangle_ind from earlier in the file. To prove something about all prisms, it's enough to prove it for the "identity" prism. Note that we don't specialize to a prism concentrated on a single vertex, since sometimes we have to deal with a composite of two prisms.
Definition prism_ind {P : Type} (a b c : P) (ab : a = b) (ac : a = c) (bc : b = c) (abc : ab @ bc = ac)
  (Q : (a' b' c' : P) (ab' : a' = b') (ac' : a' = c') (bc' : b' = c') (abc' : ab' @ bc' = ac')
         (k1 : a = a') (k2 : b = b') (k3 : c = c')
         (k12 : ab @ k2 = k1 @ ab') (k13 : ac @ k3 = k1 @ ac') (k23 : bc @ k3 = k2 @ bc')
         (k123 : prism abc abc' k12 k13 k23), Type)
  (s : Q a b c ab ac bc abc idpath idpath idpath (equiv_p1_1q idpath) (equiv_p1_1q idpath) (equiv_p1_1q idpath) (prism_id abc))
  : a' b' c' ab' ac' bc' abc' k1 k2 k3 k12 k13 k23 k123, Q a' b' c' ab' ac' bc' abc' k1 k2 k3 k12 k13 k23 k123.
Proof.
  intros.
  induction k1, k2, k3.
  revert k123.
  revert k12; equiv_intro (equiv_p1_1q (p:=ab) (q:=ab')) k12'; induction k12'.
  revert k13; equiv_intro (equiv_p1_1q (p:=ac) (q:=ac')) k13'; induction k13'.
  revert k23; equiv_intro (equiv_p1_1q (p:=bc) (q:=bc')) k23'; induction k23'.
  induction ab, bc, abc; simpl in ×.
  unfold prism; simpl.
  equiv_intro (equiv_concat_r (concat_1p (whiskerL 1 abc') @ whiskerL_1p_1 abc')^ idpath) k123'.
  induction k123'.
  simpl.
  exact s.
Defined.

Use this with f g : TriJoinRecData A B C P, h : TriJoinRecPath f g (so g is the *co*domain of h), a : A, b : B, c : C.
Ltac prism_ind g h a b c :=
  generalize_some h a b c;
  generalize_some g a b c;
  apply prism_ind.

Use this with f g : TriJoinRecData A B C P, h : TriJoinRecPath f g (so g is the *co*domain of h). Two of the arguments a, b and c should be elements of A, B and C, respectively, and the third should be a dummy value (e.g. _X_) that causes the generalize tactic to fail. It applies to goals that only depend on the components of g and h involving just two of A, B and C. So it is dealing with one square face of the prism.
Ltac prism_ind_two g h a b c :=
  generalize_some h a b c;
  generalize_some g a b c;
  apply square_ind. (* From Join/Core.v *)

Use the WildCat library to organize things

We begin by showing that TriJoinRecData A B C P is a 0-groupoid, one piece at a time.
Global Instance isgraph_trijoinrecdata (A B C P : Type) : IsGraph (TriJoinRecData A B C P)
  := {| Hom := TriJoinRecPath |}.

Global Instance is01cat_trijoinrecdata (A B C P : Type) : Is01Cat (TriJoinRecData A B C P).
Proof.
  apply Build_Is01Cat.
  - intro f.
    bundle_trijoinrecpath.
    1, 2, 3: reflexivity.
    intros a b c; cbn beta.
    (* Can finish with:  by triangle_ind f a b c. *)
    unfold prism'.
    cbn.
    apply concat_p1_1p.
  - intros f1 f2 f3 k2 k1.
    snrapply Build_TriJoinRecPath; intros; cbn beta.
    + exact (h1 k1 a @ h1 k2 a).
    + exact (h2 k1 b @ h2 k2 b).
    + exact (h3 k1 c @ h3 k2 c).
    + (* Some simple path algebra works as well. *)
      prism_ind_two f3 k2 a b _X_.
      prism_ind_two f2 k1 a b _X_.
      by triangle_ind_two f1 a b _X_.
    + prism_ind_two f3 k2 a _X_ c.
      prism_ind_two f2 k1 a _X_ c.
      by triangle_ind_two f1 a _X_ c.
    + prism_ind_two f3 k2 _X_ b c.
      prism_ind_two f2 k1 _X_ b c.
      by triangle_ind_two f1 _X_ b c.
    + cbn beta. prism_ind f3 k2 a b c.
      prism_ind f2 k1 a b c.
      by triangle_ind f1 a b c.
Defined.

Global Instance is0gpd_trijoinrecdata (A B C P : Type) : Is0Gpd (TriJoinRecData A B C P).
Proof.
  apply Build_Is0Gpd.
  intros f g h.
  snrapply Build_TriJoinRecPath; intros; cbn beta.
  + exact (h1 h a)^.
  + exact (h2 h b)^.
  + exact (h3 h c)^.
  + (* Some simple path algebra works as well. *)
    prism_ind_two g h a b _X_.
    by triangle_ind_two f a b _X_.
  + prism_ind_two g h a _X_ c.
    by triangle_ind_two f a _X_ c.
  + prism_ind_two g h _X_ b c.
    by triangle_ind_two f _X_ b c.
  + prism_ind g h a b c.
    by triangle_ind f a b c.
Defined.

Definition trijoinrecdata_0gpd (A B C P : Type) : ZeroGpd
  := Build_ZeroGpd (TriJoinRecData A B C P) _ _ _.

trijoinrecdata_0gpd A B C is a 1-functor from Type to ZeroGpd

It's a 1-functor that lands in ZeroGpd, and the morphisms of ZeroGpd are 0-functors, so it's easy to get confused about the levels.
First we need to show that the induced map is a morphism in ZeroGpd, i.e. that it is a 0-functor.
Global Instance is0functor_trijoinrecdata_fun {A B C P Q : Type} (g : P Q)
  : Is0Functor (@trijoinrecdata_fun A B C P Q g).
Proof.
  apply Build_Is0Functor.
  intros f1 f2 h.
  snrapply Build_TriJoinRecPath; intros; cbn.
  1, 2, 3: apply (ap g).
  1: apply (h1 h).
  1: apply (h2 h).
  1: apply (h3 h).
  1, 2, 3: refine ((ap_pp g _ _)^ @ _ @ ap_pp g _ _); apply (ap (ap g)).
  1: apply (h12 h). (* Or: prism_ind_12 f2 h a b. triangle_ind_12 f1 a b. reflexivity. *)
  1: apply (h13 h).
  1: apply (h23 h).
  prism_ind f2 h a b c.
  triangle_ind f1 a b c; cbn.
  reflexivity.
Defined.

trijoinrecdata_0gpd A B C is a 0-functor from Type to ZeroGpd (one level up).
Global Instance is0functor_trijoinrecdata_0gpd (A B C : Type) : Is0Functor (trijoinrecdata_0gpd A B C).
Proof.
  apply Build_Is0Functor.
  intros P Q g.
  snrapply Build_Morphism_0Gpd.
  - exact (trijoinrecdata_fun g).
  - apply is0functor_trijoinrecdata_fun.
Defined.

trijoinrecdata_0gpd A B C is a 1-functor from Type to ZeroGpd.
Global Instance is1functor_trijoinrecdata_0gpd (A B C : Type) : Is1Functor (trijoinrecdata_0gpd A B C).
Proof.
  apply Build_Is1Functor.
  (* If g1 g2 : P Q are homotopic, then the induced maps are homotopic: *)
  - intros P Q g1 g2 h f; cbn in ×.
    snrapply Build_TriJoinRecPath; intros; cbn.
    1, 2, 3: apply h.
    1, 2, 3: apply concat_Ap.
    triangle_ind f a b c; cbn.
    by induction (h f).
  (* The identity map P P is sent to a map homotopic to the identity. *)
  - intros P f; cbn.
    bundle_trijoinrecpath; intros; cbn.
    1, 2, 3: apply ap_idmap.
    by triangle_ind f a b c.
  (* It respects composition. *)
  - intros P Q R g1 g2 f; cbn.
    bundle_trijoinrecpath; intros; cbn.
    1, 2, 3: apply ap_compose.
    by triangle_ind f a b c.
Defined.

Definition trijoinrecdata_0gpd_fun (A B C : Type) : Fun11 Type ZeroGpd
  := Build_Fun11 _ _ (trijoinrecdata_0gpd A B C).

By the Yoneda lemma, it follows from TriJoinRecData being a 1-functor that given TriJoinRecData in J, we get a map (J P) $-> (TriJoinRecData A B C P) of 0-groupoids which is natural in P. Below we will specialize to the case where J is TriJoin A B C with the canonical TriJoinRecData.
Definition trijoin_nattrans_recdata {A B C J : Type} (f : TriJoinRecData A B C J)
  : NatTrans (opyon_0gpd J) (trijoinrecdata_0gpd_fun A B C).
Proof.
  snrapply Build_NatTrans.
  - rapply opyoneda_0gpd; exact f.
  - exact _.
Defined.

Thus we get a map (TriJoin A B C P) $-> (TriJoinRecData A B C P) of 0-groupoids, natural in P. The underlying map is trijoin_rec_inv A B C P.
This natural transformation is in fact a natural equivalence of 0-groupoids.
Definition trijoin_rec_inv_natequiv (A B C : Type)
  : NatEquiv (opyon_0gpd (TriJoin A B C)) (trijoinrecdata_0gpd_fun A B C).
Proof.
  snrapply Build_NatEquiv'.
  1: apply trijoin_rec_inv_nattrans.
  intro P.
  apply isequiv_0gpd_issurjinj.
  apply Build_IsSurjInj.
  - intros f; cbn in f.
     (trijoin_rec f).
    apply trijoin_rec_beta.
  - exact (@isinj_trijoin_rec_inv A B C P).
Defined.

It will be handy to name the inverse natural equivalence.
trijoin_rec_natequiv A B C P is an equivalence of 0-groupoids whose underlying function is definitionally trijoin_rec.
Local Definition trijoin_rec_natequiv_check (A B C P : Type)
  : equiv_fun_0gpd (trijoin_rec_natequiv A B C P) = @trijoin_rec A B C P
  := idpath.

It follows that trijoin_rec A B C P is a 0-functor.
Global Instance is0functor_trijoin_rec (A B C P : Type) : Is0Functor (@trijoin_rec A B C P).
Proof.
  change (Is0Functor (equiv_fun_0gpd (trijoin_rec_natequiv A B C P))).
  exact _.
Defined.

And that trijoin_rec A B C is natural. The $== in the statement is just ==, but we use WildCat notation so that we can invert and compose these with WildCat notation.
Definition trijoin_rec_nat (A B C : Type) {P Q : Type} (g : P Q)
  (f : TriJoinRecData A B C P)
  : trijoin_rec (trijoinrecdata_fun g f) $== g o trijoin_rec f.
Proof.
  exact (isnat (trijoin_rec_natequiv A B C) g f).
Defined.

It is also useful to record this.
This comes up a lot as well, and if you inline the proof, you get an ugly goal.
Definition moveR_trijoin_rec {A B C P : Type} {f : TriJoinRecData A B C P} {g : TriJoin A B C P}
  (p : f $== trijoin_rec_inv g)
  : trijoin_rec f == g.
Proof.
  exact (moveR_equiv_V_0gpd (trijoin_rec_inv_natequiv A B C P) _ _ p).
Defined.

Functoriality of the triple join

Precomposition of TriJoinRecData

First observe that we can precompose k : TriJoinRecData with a triple of maps.
Definition trijoinrecdata_tricomp {A B C A' B' C' P} (k : TriJoinRecData A B C P)
  (f : A' A) (g : B' B) (h : C' C)
  : TriJoinRecData A' B' C' P
  := {| j1 := j1 k o f; j2 := j2 k o g; j3 := j3 k o h;
       j12 := fun a bj12 k (f a) (g b);
       j13 := fun a cj13 k (f a) (h c);
       j23 := fun b cj23 k (g b) (h c);
       j123 := fun a b cj123 k (f a) (g b) (h c); |}.

Precomposition with a triple respects paths.
Definition trijoinrecdata_tricomp_0fun {A B C A' B' C' P}
  {k l : TriJoinRecData A B C P} (p : k $== l)
  (f : A' A) (g : B' B) (h : C' C)
  : trijoinrecdata_tricomp k f g h $== trijoinrecdata_tricomp l f g h.
Proof.
  (* This line is not needed, but clarifies the proof. *)
  unfold trijoinrecdata_tricomp; destruct p.
  snrapply Build_TriJoinRecPath; intros; cbn; apply_hyp.
  (* E.g., the first goal is j1 k (f a) = j1 l (f a), and this is solved by h1 p (f a). We just precompose all fields of p with fg and h. *)
Defined.

Homotopies between the triple are also respected.
Definition trijoinrecdata_tricomp2 {A B C A' B' C' P} (k : TriJoinRecData A B C P)
  {f f' : A' A} {g g' : B' B} {h h' : C' C}
  (p : f == f') (q : g == g') (r : h == h')
  : trijoinrecdata_tricomp k f g h $== trijoinrecdata_tricomp k f' g' h'.
Proof.
  snrapply Build_TriJoinRecPath; intros; cbn.
  - apply ap, p.
  - apply ap, q.
  - apply ap, r.
  - induction (p a), (q b); by apply equiv_p1_1q.
  - induction (p a), (r c); by apply equiv_p1_1q.
  - induction (q b), (r c); by apply equiv_p1_1q.
  - induction (p a), (q b), (r c); apply prism_id.
Defined.

Functoriality of TriJoin via functor_trijoin

To define functor_trijoin, we simply precompose the canonical TriJoinRecData with f, g and h. For example, this has j1 := join1 o f and j12 := fun a b join12 (f a) (g b).
We use functor_trijoin to express a partial functoriality of trijoin_rec in A, B and C.
Definition trijoin_rec_functor_trijoin {A B C A' B' C' P} (k : TriJoinRecData A' B' C' P)
  (f : A A') (g : B B') (h : C C')
  : trijoin_rec k o functor_trijoin f g h == trijoin_rec (trijoinrecdata_tricomp k f g h).
Proof.
  (* On the LHS, we use naturality of the trijoin_rec inside functor_trijoin: *)
  refine ((trijoin_rec_nat _ _ _ _ _)^$ $@ _).
  refine (fmap trijoin_rec _).
  (* Just to clarify to the reader what is going on: *)
  change (?L $-> ?R) with (trijoinrecdata_tricomp (trijoin_rec_inv (trijoin_rec k)) f g h $-> R).
  exact (trijoinrecdata_tricomp_0fun (trijoin_rec_beta k) f g h).
Defined.

Now we have all of the tools to efficiently prove functoriality.

Definition functor_trijoin_compose {A B C A' B' C' A'' B'' C''}
  (f : A A') (g : B B') (h : C C')
  (f' : A' A'') (g' : B' B'') (h' : C' C'')
  : functor_trijoin (f' o f) (g' o g) (h' o h) == functor_trijoin f' g' h' o functor_trijoin f g h.
Proof.
  symmetry.
  nrapply trijoin_rec_functor_trijoin.
Defined.

Definition functor_trijoin_idmap {A B C}
  : functor_trijoin idmap idmap idmap == (idmap : TriJoin A B C TriJoin A B C).
Proof.
  apply moveR_trijoin_rec.
  change (trijoinrecdata_trijoin A B C $== trijoinrecdata_fun idmap (trijoinrecdata_trijoin A B C)).
  symmetry.
  exact (fmap_id (trijoinrecdata_0gpd A B C) _ (trijoinrecdata_trijoin A B C)).
Defined.

Definition functor2_trijoin {A B C A' B' C'}
  {f f' : A A'} {g g' : B B'} {h h' : C C'}
  (p : f == f') (q : g == g') (r : h == h')
  : functor_trijoin f g h == functor_trijoin f' g' h'.
Proof.
  unfold functor_trijoin.
  rapply (fmap trijoin_rec).
  apply (trijoinrecdata_tricomp2 _ p q r).
Defined.

Global Instance isequiv_functor_trijoin {A B C A' B' C'}
  (f : A A') `{!IsEquiv f}
  (g : B B') `{!IsEquiv g}
  (h : C C') `{!IsEquiv h}
  : IsEquiv (functor_trijoin f g h).
Proof.
  (* This proof is almost identical to the proof of isequiv_functor_join. *)
  snrapply isequiv_adjointify.
  - apply (functor_trijoin f^-1 g^-1 h^-1).
  - etransitivity.
    1: symmetry; apply functor_trijoin_compose.
    etransitivity.
    1: exact (functor2_trijoin (eisretr f) (eisretr g) (eisretr h)).
    apply functor_trijoin_idmap.
  - etransitivity.
    1: symmetry; apply functor_trijoin_compose.
    etransitivity.
    1: exact (functor2_trijoin (eissect f) (eissect g) (eissect h)).
    apply functor_trijoin_idmap.
Defined.

Definition equiv_functor_trijoin {A B C A' B' C'}
  (f : A <~> A') (g : B <~> B') (h : C <~> C')
  : TriJoin A B C <~> TriJoin A' B' C'
  := Build_Equiv _ _ (functor_trijoin f g h) _.

The relationship between functor_trijoin and functor_join.

While functor_trijoin is convenient to work with, we want to know that functor_trijoin f g h is homotopic to functor_join f (functor_join g h). This is worked out using the next three results.
A lemma that handles the path algebra in the next result. BC here is Join B C there, bc here is jglue b c there, bc' here is jg g b c there, and beta_jg here is Join_rec_beta_jglue _ _ _ b c there.
Local Lemma ap_triangle_functor_join {A BC A' P} (f : A A') (g : BC P)
  (a : A) {b c : BC} (bc : b = c) (bc' : g b = g c) (beta_jg : ap g bc = bc')
  : ap_triangle (functor_join f g) (triangle_v a bc) @ functor_join_beta_jglue f g a c
    = (functor_join_beta_jglue f g a b
        @@ ((ap_compose joinr (functor_join f g) bc)^
             @ (ap_compose g joinr bc @ ap (ap joinr) beta_jg)))
       @ triangle_v (f a) bc'.
Proof.
  induction bc, beta_jg; simpl.
  transitivity (concat_p1 _ @ functor_join_beta_jglue f g a b).
  - refine (_ @@ 1).
    unfold ap_triangle.
    apply moveR_Vp; symmetry.
    exact (ap_pp_concat_p1 (functor_join f g) (jglue a b)).
  - apply moveR_Mp; symmetry.
    exact (concat_p_pp _ _ _ @ whiskerR_p1 _).
Defined.

We'll generalize the situation a bit to keep things less verbose. join_rec g here will be functor_join g h in the next result. Maybe this extra generality will also be useful sometime?
Definition functor_join_join_rec {A B C A' P} (f : A A') (g : JoinRecData B C P)
  : functor_join f (join_rec g)
    == trijoin_rec {| j1 := joinl o f; j2 := joinr o jl g; j3 := joinr o jr g;
                      j12 := fun a bjglue (f a) (jl g b);
                      j13 := fun a cjglue (f a) (jr g c);
                      j23 := fun b cap joinr (jg g b c);
                      j123 := fun a b ctriangle_v (f a) (jg g b c); |}.
Proof.
  (* Recall that trijoin_rec is defined to be the inverse of trijoin_rec_inv_natequiv .... *)
  refine (moveL_equiv_V_0gpd (trijoin_rec_inv_natequiv A B C _) _ _ _).
  (* The next two lines aren't needed, but clarify the goal. *)
  unfold trijoin_rec_inv_natequiv, equiv_fun_0gpd; simpl.
  unfold trijoinrecdata_fun, trijoinrecdata_trijoin; simpl.
  bundle_trijoinrecpath; intros; cbn.
  - exact (functor_join_beta_jglue f _ a (joinl b)).
  - exact (functor_join_beta_jglue f _ a (joinr c)).
  - unfold join23.
    refine ((ap_compose joinr _ _)^ @ _).
    simpl.
    refine (ap_compose _ joinr (jglue b c) @ _).
    refine (ap (ap joinr) _).
    apply join_rec_beta_jg.
  - unfold prism'.
    change (join123 a b c) with (triangle_v a (jglue b c)).
    exact (ap_triangle_functor_join f (join_rec g) a (jglue b c) (jg g b c) (Join_rec_beta_jglue _ _ _ b c)).
Defined.

Definition functor_trijoin_as_functor_join {A B C A' B' C'}
  (f : A A') (g : B B') (h : C C')
  : functor_join f (functor_join g h) == functor_trijoin f g h
  := functor_join_join_rec f (functor_join_recdata g h).