diff --git a/_CoqProject b/_CoqProject
index da33050768088bcc5ec4ccf4f6d280191dbb7c95..08da4d9e34c861b4fd7d171d24b080475324932d 100644
--- a/_CoqProject
+++ b/_CoqProject
@@ -26,6 +26,8 @@ theories/lecture_notes/modular_incr.v
 theories/lecture_notes/recursion_through_the_store.v
 theories/lecture_notes/stack.v
 theories/lecture_notes/ccounter.v
+theories/lecture_notes/sfra.v
+theories/lecture_notes/parallel_incr.v
 
 theories/spanning_tree/graph.v
 theories/spanning_tree/mon.v
diff --git a/theories/lecture_notes/parallel_incr.v b/theories/lecture_notes/parallel_incr.v
new file mode 100644
index 0000000000000000000000000000000000000000..a7597ca6c1cbd7ce6487f4306466bea8798fc2e2
--- /dev/null
+++ b/theories/lecture_notes/parallel_incr.v
@@ -0,0 +1,286 @@
+(* In this file we explain how to do the "parallel increment" example using ghost state (Example
+   8.31) from the lecture notes in Iris in Coq.
+
+   That is, we want to specify that a non-atomic increment of a location by two threads
+   results in the location being incremented by either 1 or 2.
+
+   We assume that the reader has experimented with coq_intro_example_1.v, and
+   thus we do not explain the features we have already explained there.
+ *)
+
+From iris.algebra Require Import frac.
+From iris.proofmode Require Import proofmode.
+From iris.base_logic.lib Require Export invariants.
+From iris.program_logic Require Export weakestpre.
+From iris.heap_lang Require Import proofmode.
+From iris.heap_lang Require Import notation lang.
+From iris.heap_lang.lib Require Import par.
+From iris.prelude Require Import options.
+
+(* In the file sfra.v, the "S-F" transition system resource algebra from example 8.16 is implemented,
+   along with some lemmas that will be useful here.
+   It is mainly a technicality and not necessary to look at to understand the proof.
+   An example on how to construct resource algebras can be found in coq_intro_example_2.v.
+ *)
+From iris_examples.lecture_notes Require Import sfra.
+
+(* Definition of the program *)
+Definition incr (ℓ : loc) : expr := #ℓ <- !#ℓ + #1.
+
+Section proof.
+  Context `{!heapGS Σ}.
+  Context `{!spawnG Σ}.
+  Notation iProp := (iProp Σ).
+  Context (N : namespace).
+  (* We have to add the resource algebra classes to the context *)
+  Context `{!SFG Σ}.
+  Context `{!FracG Σ}.
+
+(* Proof idea
+
+   To get a specification which precisely reflects the possible outcomes of the parallel increment,
+   we use ghost state in an invariant to represent the possible states during an execution.
+   The ghost state consists of two parts from two different resource algebras which we use in parallel:
+   - A transition system (resource algebra SF) with the tokens S and F, where S can be updated to F, F is duplicable,
+     and everything else except F⋅F is incompatible
+   - 1/2 fractions, which can be combined into 1
+
+   The transition system is used to keep track of whether we are in the starting state (S),
+   which cannot exist at the end of execution, or one of the two final states (F), which are possible
+   at the end of execution. However, to be able to state that the location will be incremented by
+   exactly 1 or 2 after execution of the program (instead of "at least by one"), we also
+   have to keep track of how many times the value has been logically incremented.
+   To this end, we use two 1/2 fractions, which, owned by the threads, initially represent the ability to increment,
+   and when given to the invariant, they represent the number of contributions of "+1" to the value at location ℓ.
+
+   The invariant thus consists of three disjuncts:
+   - the starting state, where ℓ points to n, represented by the ownership of the S token
+   - the first final state, where ℓ points to n+1, represented by the ownership of an F token
+     and a 1/2 token, which stands for one contribution of 1 to the value at ℓ
+   - the second final state, where ℓ points to n+2, represented by the ownership of an F token
+     and two 1/2 tokens, that is, a 1 token, which stands for two contributions of 1 to the value at ℓ
+*)
+
+Definition incr_inv (ℓ : loc) (γ1 γ2 : gname) (n : Z) : iProp :=
+    ℓ ↦ #n ∗ own γ1 S
+  ∨ ℓ ↦ #(n+1) ∗ own γ1 F ∗ own γ2 (1/2)%Qp
+  ∨ ℓ ↦ #(n+2) ∗ own γ1 F ∗ own γ2 1%Qp.
+
+(* Overview of the proof
+   The proof starts with allocating ghost state: S from the SF resource algebra, and 1 from the fractional resource
+   algebra. We are then able to allocate the invariant in the first state, using the fact that ℓ points to n
+   at the beginning and that we own the S token.
+   From this point on, we can proceed separately in each thread. Here we will make use of the two 1/2 fractions
+   we own, passing one to each thread. This means that each thread has the possibility to contribute +1 to the
+   value stored at location ℓ.
+   As both threads are the same, we can extract a specification for the code of a single thread and
+   then use it to prove our actual specification. As the location ℓ to be modified is governed
+   by an invariant, it can be used by both threads simultaneously without having to think of
+   interleavings in the proof. This nicely demonstrates the modularity of the logic, allowing us to verify parallel
+   components separately.
+
+   Each thread is then verified by receiving the invariant and a 1/2 token as resources.
+   To read the current value from the location and to write the updated value back, we need to open the invariant.
+   As the invariant can only be opened for an atomic step, it has to be opened and closed twice, once for
+   reading and once for writing. Each time, we have to consider all possible states the invariant can be in
+   and then have to show that one of the cases holds when closing it. Depending on where we are in the program
+   (reading the value or storing the updated value) only some of the cases will be possible. The impossible
+   cases will be ruled out using the definition of the resource algebras, namely by obtaining invalid
+   elements, resulting in a precondition being False.
+*)
+
+(* The main part of the proof is showing a specification that holds for each
+   of the threads. We start by proving this in a separate lemma. *)
+Lemma incr_spec (ℓ : loc) (n : Z) (γ1 γ2 : gname):
+  {{{ inv N (incr_inv ℓ γ1 γ2 n) ∗ own γ2 (1 / 2)%Qp }}}
+    incr ℓ
+  {{{ v, RET #v; own γ1 F }}}.
+Proof.
+  iIntros (Φ) "[#Inv Own1/2] Post".
+  unfold incr.
+  (* To prove that the increment results in an F token, we have to prove two triples,
+     one for reading the value currently stored at ℓ, and then one for storing the
+     incremented value. To prove each triple, we have to open the invariant to get access to ℓ, and
+     then close it again after the one atomic step. *)
+  wp_bind (!#ℓ)%E.
+  (* In the first triple, we want to show that the value currently stored at ℓ is n or n+1,
+     and that if it is n+1, the invariant is in a final state. This information we want to
+     use as an assumption in the second triple. Thus we first use wp_wand to update the
+     current postcondition accordingly. We have to use the 1/2 resource to prove the first
+     triple, as we will need it to rule out the case where ℓ contains n+2. However, we will
+     not consume this resource, so we restate the ownership in the postcondition. *)
+  iApply ((wp_wand _ _ _ (fun v => (⌜v = #n⌝ ∨ ⌜v = #(n+1)⌝ ∗ own γ1 F) ∗ own γ2 (1 / 2)%Qp)%I) with "[Own1/2]").
+  { (* The first triple about the result of !ℓ *)
+    (* We now open the invariant and destruct it into its three cases, one for each of the states.
+       In each case, we close the invariant again with the same resources we got from opening it. *)
+    iInv N as ">[I | [I | I]]" "Iclose".
+    - (* Case 1: ℓ ↦ #n ∗ own γ1 S *)
+      iDestruct "I" as "(Hℓ & OwnS)".
+      wp_load.
+      (* To close the invariant in the same state, we provide the two resources again
+         and use them to show the disjunct corresponding to that state. *)
+      iMod ("Iclose" with "[Hℓ OwnS]") as "_".
+      { iLeft. iFrame. }
+      iFrame. iLeft. done.
+    - (* Case 2: ℓ ↦ #(n + 1) ∗ own γ1 F ∗ own γ2 (1 / 2)%Qp *)
+      (* This is analogous to the first case. *)
+      iDestruct "I" as "(Hℓ & OwnF & Own1/2_I)".
+      (* We have to duplicate F, to have one to close the invariant and one for the postcondition. *)
+      iDestruct (own_SFRA_FF with "OwnF") as "[OwnF_1 OwnF_2]".
+      wp_load.
+      iMod ("Iclose" with "[Hℓ OwnF_1 Own1/2_I]") as "_".
+      { iRight. iLeft. iFrame. }
+      iFrame. iRight. iFrame. done.
+    - (* Case 3: ℓ ↦ #(n + 2) ∗ own γ1 F ∗ own γ2 1%Qp
+         This state is impossible, as it means we have already incremented twice, represented by
+         the ownership of 1. In this thread we have not come to writing to ℓ yet (represented by
+         still owning 1/2, which we would lose when closing the invariant in a new state ),
+         ℓ could thus have at most been updated once (by the other thread). We get the contradiction
+         by combining the 1 from the invariant with the 1/2 of this thread into the invalid element (1 + 1/2). *)
+      iDestruct "I" as "(Hℓ & OwnF & Own1)".
+      iCombine "Own1 Own1/2" as "Own3/2".
+      iDestruct (own_valid with "Own3/2") as "%Hinvalid".
+      contradiction.
+  }
+  { (* The second triple about storing the incremented v *)
+    iIntros (v) "[Hv Own1/2]".
+    (* We consider the two cases for the value of v *)
+    iDestruct "Hv" as "[-> | [-> OwnF]]";
+      wp_binop;
+      (* To store the updated value, we again have to open the invariant.
+         As before, we have to consider the different possible states the invariant can be in.
+         Note that the state can have changed since we closed the invariant after reading,
+         due to the other thread updating the value.
+         Together with the two possible values for v, we get 6 subgoals. *)
+      iInv N as ">[I | [I | I]]" "Iclose";
+      (* Again the third case of the invariant (ℓ ↦ #(n + 2) ∗ own γ1 F ∗ own γ2 1%Qp) is impossible,
+         as we could not have updated ℓ twice yet. Thus we solve the two corresponding
+         subgoals with the contradiction obtained by combining 1 and 1/2, as before. *)
+      try (
+          iDestruct "I" as "(Hℓ & OwnF_I & Own1)";
+          iCombine "Own1 Own1/2" as "Own3/2";
+          iDestruct (own_valid with "Own3/2") as "%Hinvalid";
+          contradiction
+        );
+      (* In the remaining cases, we store the updated value, and close the invariant
+         in the corresponding state, depending on whether we stored n+1 or n+2.
+         To update the invariant's state, we have to transfer the 1/2 that was given
+         to this thread into the invariant. *)
+      iDestruct "I" as "[Hℓ Own]";
+      wp_store;
+      try (replace (n+1+1)%Z with (n+2)%Z by lia).
+    - (* Storing (n + 1), invariant was in state S *)
+      (* We have to show the second disjunct of the invariant to close it, which
+         in addition to the 1/2 requires an F token.
+         As the invariant was in the S state before, we have to update S to F. *)
+      iMod (own_update _ _ F with "Own") as "OwnF"; first (apply SFRA_update).
+      (* We then duplicate F, because we need to pass one F to the conclusion too. *)
+      iDestruct (own_SFRA_FF with "OwnF") as "[OwnF_1 OwnF_2]".
+      iMod ("Iclose" with "[Hℓ OwnF_1 Own1/2]") as "_".
+      { iRight. iLeft. iFrame. }
+      iApply ("Post" with "OwnF_2").
+    - (* Storing (n + 1), invariant was in state F 1/2 *)
+      (* Again, we have to show the second disjunct of the invariant to close it.
+         However, the invariant was already in this state, so we don't have to use
+         our 1/2 to make a state transition. This corresponds to the situation where the other
+         thread has incremented the value from n to n+1 after we read n, but before we stored n+1.
+         We still have to duplicate the F token though, to be able to remember that the
+         invariant is in a final state. *)
+      iDestruct "Own" as "[OwnF Own1/2_I]".
+      iDestruct (own_SFRA_FF with "OwnF") as "[OwnF_1 OwnF_2]".
+      iMod ("Iclose" with "[Hℓ OwnF_1 Own1/2_I]") as "_".
+      { iRight. iLeft. iFrame. }
+      iApply ("Post" with "OwnF_2").
+    - (* Storing (n + 2), invariant was in state S *)
+      (* This case is impossible: the invariant cannot be in the starting state S
+         if we have read n+1, which means that the value had already been updated once.
+         We get a contradiction by combining the S token with the F token we got for reading v=n+1. *)
+      iDestruct (own_SF_false with "[$Own $OwnF]") as "HFalse".
+      done.
+    - (* Storing (n + 2), invariant was in state F 1/2 *)
+      (* This case represents the situation where both threads have run sequentially, i.e.,
+         the other thread has updated to n+1 before we read the value. Having stored n+2,
+         we have to show the third disjunct of the invariant, which requires us to transfer
+         an additional 1/2 to the invariant to obtain the required 1. *)
+      iDestruct "Own" as "(OwnF_I & Own1/2_I)".
+      iCombine "Own1/2 Own1/2_I" as "Own1".
+      iMod ("Iclose" with "[Hℓ OwnF_I Own1]") as "_".
+      { iRight. iRight. iFrame. }
+      iApply ("Post" with "OwnF").
+  }
+Qed.
+
+(* The actual specification for the whole program.
+   Here we make use of the specification for a single thread we proved above. *)
+Lemma parallel_incr_spec (ℓ : loc) (n : Z):
+  {{{ ℓ ↦ #n }}} (incr ℓ) ||| (incr ℓ) ;; !#ℓ {{{v, RET #v; ⌜(v = n+1)%Z⌝ ∨ ⌜(v = n+2)%Z⌝ }}}.
+Proof using Type* N.
+  iIntros (Φ) "Hℓ Post".
+  (* We start by allocating the start token S and the fraction 1. *)
+  iMod (own_alloc S) as (γ1) "OwnS"; first done.
+  iMod (own_alloc 1%Qp) as (γ2) "Own1"; first done.
+  (* We allocate the invariant in the first state, where we have to provide an S token. *)
+  iMod (inv_alloc N _ (incr_inv ℓ γ1 γ2 n) with "[Hℓ OwnS]") as "#Inv".
+  { iLeft. iFrame. }
+  (* We continue by focusing on the main part of the program, the parallel increment. *)
+  wp_bind (_ ||| _)%E.
+  (* For proving a conclusion for each thread, we want to pass a 1/2 fraction to each.
+     Therefore we have to split the 1 we have into two halves.
+     As frac is a fractional type, iDestruct can split the resource into two equal parts. *)
+  - iDestruct "Own1" as "[Own1/2_1 Own1/2_2]".
+    (* We now apply the par rule, passing one of the 1/2 to each thread.
+       The arguments are the conclusions of the threads, which (according to our incr_spec)
+       in both cases we choose to be the ownership of the F token. This will allow us to
+       conclude that when the program terminates, the invariant must be in one of the F states
+       (as S is incompatible with F). *)
+    wp_smart_apply (wp_par (λ _ , own γ1 F)%I (λ _ , own γ1 F)%I with "[Own1/2_1] [Own1/2_2]").
+    (* We can now for each thread apply our incr_spec specification. *)
+    + iApply (incr_spec ℓ n γ1 γ2 with "[Inv Own1/2_1]"); first by iFrame.
+      iNext. iIntros (_) "H". done.
+    + iApply (incr_spec ℓ n γ1 γ2 with "[Inv Own1/2_2]"); first by iFrame.
+      iNext. iIntros (_) "H". done.
+    + (* As the final part, we have to show the second triple for reading the location,
+         making use of the conclusions we get from both threads. *)
+      iIntros (v1 v2) "OwnF".
+      (* We get an F token from both threads, each stating that after the execution of one thread,
+         the invariant must be in one of the final statse. *)
+      iDestruct "OwnF" as "[OwnF _]".
+      iNext.
+      wp_pures.
+      (* To read the location, we have to open the invariant again.
+         We get three cases corresponding to the disjuncts in the invariant.
+         As we have the F token, only the final states are possible cases, as we will see next.
+         As we are only reading, as before we close the invariant again with the same resources
+         we got from opening it. *)
+      iInv N as ">[I | [I | I]]" "Iclose";
+      iDestruct "I" as "[Hℓ Own]".
+      * (* Case 1: ℓ ↦ #n, S
+           This state is not possible as both threads either store n+1 or n+2 in ℓ.
+           This is reflected by the incompatibility of owning the S token and an F token.
+           To obtain a contradiction, we combine the S and F tokens as before. *)
+        iDestruct (own_SF_false with "[$Own $OwnF]") as "HFalse".
+        done.
+      * (* Case 2: ℓ ↦ #(n + 1), F 1/2
+           This state corresponds to the situation where only one thread
+           has actually incremented the value. *)
+        wp_load.
+        (* As we only read the value stored at ℓ, we can close the invariant again
+           with the same resources we got from opening. *)
+        iMod ("Iclose" with "[Hℓ Own]") as "_".
+        { iRight. iLeft. iFrame. }
+        iModIntro. iApply "Post". iLeft. done.
+      * (* Case 3: ℓ ↦ #(n + 2), F 1
+           This state corresponds to the situation where both threads have
+           incremented the value subsequently. *)
+        wp_load.
+        iMod ("Iclose" with "[Hℓ Own]") as "_".
+        { iRight. iRight. iFrame. }
+        iModIntro. iApply "Post". iRight. done.
+Qed.
+
+End proof.
+
+(*  LocalWords:  disjuncts disjunct postcondition duplicable interleavings
+(*  LocalWords:  modularity
+ *)
+ *)
diff --git a/theories/lecture_notes/sfra.v b/theories/lecture_notes/sfra.v
new file mode 100644
index 0000000000000000000000000000000000000000..c7bbcf0f030a2cc045d96ddd415192b72de25ad1
--- /dev/null
+++ b/theories/lecture_notes/sfra.v
@@ -0,0 +1,111 @@
+(* This file implements the "S-F" transition system resource algebra from example 8.16.
+   Note that usually this resource algebra would be defined using existing abstractions,
+   for example via "csumR (exclR unitO) unitR". For demonstration purposes, here it is
+   defined from scratch.
+ *)
+
+From iris.heap_lang Require Import proofmode.
+From iris.algebra Require Import frac.
+From iris.prelude Require Import options.
+
+Section RADefinitions.
+
+Inductive SF :=
+    | S (* The starting state *)
+    | F (* The final state *)
+    | B. (* The invalid element "bottom" *)
+
+Canonical Structure SFRAC := leibnizO SF.
+
+
+Global Instance SFRAop : Op SF :=
+  λ x y, match x, y with
+           | F, F => F
+           | _, _ => B end.
+
+Global Instance SFRAValid : Valid SF :=
+  λ x, match x with
+       | S | F => True
+       | _ => False end.
+
+Global Instance SFRACore : PCore SF :=
+  λ _, None.
+
+(* Produce cameras from the RA *)
+Definition SFRA_mixin : RAMixin SF.
+Proof.
+    split; try apply _; try done.
+    (* The operation is associative. *)
+    - unfold op, SFRAop. intros [] [] []; reflexivity.
+    (* The operation is commutative. *)
+    - unfold op, SFRAop. intros [] []; reflexivity.
+    - (* Validity axiom: validity is down-closed with respect to the extension order. *)
+      intros [] []; intros H; try (inversion H); auto.
+Qed.
+
+Canonical Structure SFRA := discreteR SF SFRA_mixin.
+
+Global Instance SFRA_cmra_discrete : CmraDiscrete SFRA.
+Proof. apply discrete_cmra_discrete. Qed.
+
+Global Instance SFRA_S_exclusive : Exclusive S.
+Proof. intros x. done. Qed.
+
+(* States the duplicability of F. Alternatively can use "replace".
+   NOTE: Usually would make F a core element of the resource algebra, which would make it persistent
+   and thereby duplicable *)
+Lemma SFRA_FF: F = F ⋅ F.
+Proof.
+  done.
+Qed.
+
+Lemma SFRA_update : (S : SF) ~~> F.
+Proof.
+  apply cmra_update_exclusive.
+  done.
+  (* *** Manual proof *** *)
+  (* unfold "~~>". *)
+  (* intros n mx. *)
+  (* destruct mx as [x |]. *)
+  (* - destruct x; contradiction. *)
+  (* - done. *)
+Qed.
+
+Class SFG Σ := SF_G :> inG Σ (SFRA).
+Class FracG Σ := Frac_G :> inG Σ (fracR).
+
+End RADefinitions.
+
+
+(* This section defines some basic lemmas that make using the resource algebras more practical. *)
+Section RALemmas.
+  Context (N : namespace).
+  Context `{!SFG Σ}.
+  Context `{!FracG Σ}.
+
+Lemma own_frac_valid γ (x : Qp): own γ x ⊢ ⌜(x ≤ 1)%Qp⌝.
+Proof.
+  rewrite own_valid.
+  iIntros "!%".
+  apply frac_valid.
+Qed.
+
+Lemma own_SFRA_FF (γ: gname): own γ F ⊣⊢ own γ F ∗ own γ F.
+Proof.
+  apply (own_op _ F F).
+  (* Alternative proof using lemmas from above *)
+  (* rewrite -own_op. *)
+  (* rewrite -SFRA_FF. *)
+  (* reflexivity. *)
+Qed.
+
+(* This lemma states the incompatibility of S and F *)
+Lemma own_SF_false (γ: gname): own γ S ∗ own γ F ⊢ False.
+Proof.
+  rewrite -own_op.
+  rewrite own_valid.
+  iIntros (Contra).
+  contradiction.
+Qed.
+
+End RALemmas.