diff --git a/lab3/README.md b/lab3/README.md
index d0646ee60e261eea40ca494faafd1f1b0b0baf69..a5ad652d1e0e7ab4c35a2555c50739f80020da12 100644
--- a/lab3/README.md
+++ b/lab3/README.md
@@ -13,6 +13,7 @@ Three hours
 # Source Codes
 
 - MPI I/O. Serial hello world in C and Fortran ([hello_mpi.c](hello_mpi.c) and [hello_mpi.f90](hello_mpi.f90))
+- MPI Derived types and I/O. Serial STL file reader in C and Fortran ([mpi_derived_types.c](mpi_derived_types.c) and [mpi_derived_types.f90](mpi_derived_types.f90)
 - MPI Latency: C and Fortran ([mpi_latency.c](mpi_latency.c) and [mpi_latency.f90](mpi_latency.f90))
 - MPI Bandwidth : C and Fortran ([mpi_bandwidth.c](mpi_bandwidth.c) and [mpi_bandwidth.f90](mpi_bandwidth.f90))
 - MPI Bandwidth Non-Blocking: C and Fortran ([mpi_bandwidth-nonblock.c](mpi_bandwidth-nonblock.c) 
@@ -29,7 +30,19 @@ MPI I/O is used so that results can be written to the same file in parallel. Tak
 
 The simplest solution is likely to be for you to create a character buffer, and then use the MPI_File_write_at function.
 
-# Exercises 2 - Bandwidth and latency between nodes
+# Exercise 2 - MPI I/O and dervied types
+
+Take the serial stl reader and modify it such that the stl file is read (and written) in parallel using collective MPI I/O. Use derived types such that the file can be read/written with a maximum of 3 I/O operations per read and write.
+
+The simplest solution is likely to create a derived type for each triangle, and then use the MPI_File_XXXX_at_all function. A correct solution will have the same MD5 hash for both stl models (input and output), unless the order of the triangles has been changed.
+
+```
+md5sum out.stl data/sphere.stl
+822aba6dc20cc0421f92ad50df95464c  out.stl
+822aba6dc20cc0421f92ad50df95464c  data/sphere.stl
+```
+
+# Exercises 3 - Bandwidth and latency between nodes
 
 Use `mpi_wtime` to compute latency and bandwidth with the bandwidth and latency codes above
 
diff --git a/lab3/data/sphere.stl b/lab3/data/sphere.stl
new file mode 100644
index 0000000000000000000000000000000000000000..86a17cb221228e88ce977506ae1faeee4077687b
Binary files /dev/null and b/lab3/data/sphere.stl differ
diff --git a/lab3/mpi_derived_types.c b/lab3/mpi_derived_types.c
new file mode 100644
index 0000000000000000000000000000000000000000..45e2153266a7fb15738610abf66bbc18e74dfabd
--- /dev/null
+++ b/lab3/mpi_derived_types.c
@@ -0,0 +1,112 @@
+/*
+  STL file format 
+
+  UINT8[80] – Header
+  UINT32 – Number of triangles
+
+  foreach triangle
+  REAL32[3] – Normal vector
+  REAL32[3] – Vertex 1
+  REAL32[3] – Vertex 2
+  REAL32[3] – Vertex 3
+  UINT16 – Attribute byte count
+  end
+
+  (see https://en.wikipedia.org/wiki/STL_(file_format)
+  
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <mpi.h>
+
+#define STL_HDR_SIZE 80
+
+typedef struct {
+  float n[3];			/* Normal vector */
+  float v1[3];			/* Vertex 1 */
+  float v2[3];			/* Vertex 2 */
+  float v3[3];			/* Vertex 3 */
+  uint16_t attrib;		/* Attribute byte count */
+} __attribute__((packed)) stl_triangle_t;
+
+typedef struct {
+  char hdr[STL_HDR_SIZE];	/* Header */
+  uint32_t n_tri;		/* Number of triangles */
+  stl_triangle_t *tri;		/* Triangles */
+} stl_model_t;
+
+
+void stl_read(const char *fname, stl_model_t *model) {
+  FILE *fp;
+  int pe_size, pe_rank;
+
+  MPI_Comm_size(MPI_COMM_WORLD, &pe_size);
+  MPI_Comm_rank(MPI_COMM_WORLD, &pe_rank);  
+
+  fp = fopen(fname, "r");
+
+  if (pe_rank == 0) printf("Reading STL file: %s\n", fname);
+
+  /* Read STL header */
+  fread(model->hdr, sizeof(char), STL_HDR_SIZE, fp);
+
+  /* Make sure it's a binary STL file */
+  if (strncmp(model->hdr, "solid", 5) == 0) {
+    fprintf(stderr, "ASCII STL files not supported!\n");
+    exit(-1);
+  }
+
+  /* Read how many triangles the file contains */
+  fread(&model->n_tri, sizeof(uint32_t), 1, fp);
+  if (pe_rank == 0) printf("Found: %d triangles\n", model->n_tri);
+
+  /* Allocate memory for triangles, and read them */
+  model->tri = malloc(model->n_tri * sizeof(stl_triangle_t));
+  fread(model->tri, sizeof(stl_triangle_t), model->n_tri, fp);
+
+  fclose(fp);
+  if (pe_rank == 0) printf("Done\n");
+
+}
+
+void stl_write(const char *fname, stl_model_t *model) {
+  FILE *fp;
+  int pe_size, pe_rank;
+
+  MPI_Comm_size(MPI_COMM_WORLD, &pe_size);
+  MPI_Comm_rank(MPI_COMM_WORLD, &pe_rank);
+  
+  fp = fopen(fname, "w");
+  if (pe_rank == 0) printf("Writing STL file: %s\n", fname);
+
+  /* Write STL header */
+  fwrite(model->hdr, sizeof(char), STL_HDR_SIZE, fp);
+
+  /* Write number of triangles */
+  fwrite(&model->n_tri, sizeof(uint32_t), 1, fp);
+
+  /* Write all triangles */
+  fwrite(model->tri, sizeof(stl_triangle_t), model->n_tri, fp);
+
+  fclose(fp);
+  if (pe_rank == 0) printf("Done\n");
+}
+
+int main(int argc, char **argv) {
+  stl_model_t model;
+  
+  MPI_Init(&argc, &argv);
+  
+  stl_read("./data/sphere.stl", &model);
+  stl_write("out.stl", &model);
+  free(model.tri);
+
+  MPI_Finalize();
+  
+  return 0;
+}
+
+
diff --git a/lab3/mpi_derived_types.f90 b/lab3/mpi_derived_types.f90
new file mode 100644
index 0000000000000000000000000000000000000000..03b248048c3a2170302669ba47beb5f3cb78cac9
--- /dev/null
+++ b/lab3/mpi_derived_types.f90
@@ -0,0 +1,117 @@
+!
+!  STL file format 
+!
+!  UINT8[80] – Header
+!  UINT32 – Number of triangles
+!
+!  foreach triangle
+!  REAL32[3] – Normal vector
+!  REAL32[3] – Vertex 1
+!  REAL32[3] – Vertex 2
+!  REAL32[3] – Vertex 3
+!  UINT16 – Attribute byte count
+!  end
+!
+!  (see https://en.wikipedia.org/wiki/STL_(file_format)
+!
+module types
+  integer, parameter :: dp = kind(0.0d0)
+  integer, parameter :: sp = kind(0.0)
+end module types
+
+module stl
+  use mpi
+  use types
+  implicit none
+
+  type :: stl_triangle_t
+     real(kind=sp) :: n(3)
+     real(kind=sp) :: v1(3)
+     real(kind=sp) :: v2(3)
+     real(kind=sp) :: v3(3)
+     integer(kind=2) :: attrib
+  end type stl_triangle_t
+
+  type :: stl_model_t
+     character(len=80) :: hdr
+     integer :: n_tri
+     type(stl_triangle_t), allocatable :: tri(:)
+  end type stl_model_t
+  
+contains
+
+  subroutine stl_read(fname, model)
+    character(len=*), intent(in) :: fname
+    type(stl_model_t), intent(inout) :: model
+    integer :: pe_size, pe_rank, ierr
+
+    call MPI_Comm_size(MPI_COMM_WORLD, pe_size, ierr)
+    call MPI_Comm_rank(MPI_COMM_WORLD, pe_rank, ierr)
+
+    open(42, file=fname, access='stream', form='unformatted')
+    
+    if (pe_rank .eq. 0) write(*,*) 'Reading STL file: ', fname
+    ! Read STL header
+    read(42, pos=1) model%hdr
+    
+    ! Make sure it's a binary STL file 
+    if (model%hdr(1:6) .eq. 'solid') then
+       write(*,*) 'ASCII STL files not supported!'
+       stop 
+    end if
+    
+    ! Read how many triangles the file contains 
+    read(42, pos=81) model%n_tri
+    if (pe_rank .eq. 0) write(*,*) 'Found: ', model%n_tri,' triangles'
+
+    ! Allocate memory for triangles, and read them
+    allocate(model%tri(model%n_tri))    
+    read(42, pos=85) model%tri
+
+    close(42)    
+    if (pe_rank .eq. 0) write(*,*) 'Done'
+
+  end subroutine stl_read
+
+  subroutine stl_write(fname, model)
+    character(len=*), intent(in) :: fname
+    type(stl_model_t), intent(in) :: model
+    integer :: pe_size, pe_rank, ierr
+
+    call MPI_Comm_size(MPI_COMM_WORLD, pe_size, ierr)
+    call MPI_Comm_rank(MPI_COMM_WORLD, pe_rank, ierr)
+    
+    open(17, file=fname, access='stream', form='unformatted')
+    
+    if (pe_rank .eq. 0) write(*,*) 'Writing STL file: ', fname
+
+    ! Write STL header
+    write(17, pos=1) model%hdr
+
+    ! Write number of triangles
+    write(17, pos=81) model%n_tri
+    
+    ! Write all triangles
+    write(17, pos=85) model%tri
+
+    close(17)
+    if (pe_rank .eq. 0) write(*,*) 'Done'
+
+  end subroutine stl_write
+end module stl
+
+program mpi_derived_types
+  use stl
+  use mpi
+  implicit none
+  type(stl_model_t) :: model
+  integer :: ierr
+  call MPI_Init(ierr)
+
+  call stl_read("./data/sphere.stl", model)
+  call stl_write("out.stl", model)
+  deallocate(model%tri)
+
+  call MPI_Finalize(ierr)
+
+end program mpi_derived_types