commit 76c46ba160aa80b209aa7e20e302b170c87ef454
parent 2379861ee82b96220b667e149c44ed11172f7dc4
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date:   Mon,  9 Jul 2018 11:31:33 -0400
Merge pull request #6 from anders-dc/ridging
Implement compressive failure parameterization
Diffstat:
32 files changed, 885 insertions(+), 520 deletions(-)
diff --git a/src/atmosphere.jl b/src/atmosphere.jl
@@ -169,10 +169,10 @@ function applyAtmosphereDragToGrain!(grain::GrainCylindrical,
     drag_force = ρ_a * π * 
     (2.0*grain.ocean_drag_coeff_vert*grain.areal_radius*.1*grain.thickness + 
      grain.atmosphere_drag_coeff_horiz*grain.areal_radius^2.0) *
-        ([u, v] - grain.lin_vel)*norm([u, v] - grain.lin_vel)
+        ([u, v] - grain.lin_vel[1:2])*norm([u, v] - grain.lin_vel[1:2])
 
-    grain.force += drag_force
-    grain.atmosphere_stress = drag_force/grain.horizontal_surface_area
+    grain.force += vecTo3d(drag_force)
+    grain.atmosphere_stress = vecTo3d(drag_force/grain.horizontal_surface_area)
     nothing
 end
 
@@ -186,12 +186,12 @@ function applyAtmosphereVorticityToGrain!(grain::GrainCylindrical,
                                             atmosphere_curl::Float64)
     ρ_a = 1.2754   # atmosphere density
 
-    grain.torque +=
+    grain.torque[3] +=
         π * grain.areal_radius^4. * ρ_a * 
         (grain.areal_radius / 5. * grain.atmosphere_drag_coeff_horiz + 
         .1 * grain.thickness * grain.atmosphere_drag_coeff_vert) * 
-        abs(.5 * atmosphere_curl - grain.ang_vel) * 
-        (.5 * atmosphere_curl - grain.ang_vel)
+        norm(.5 * atmosphere_curl - grain.ang_vel[3]) * 
+        (.5 * atmosphere_curl - grain.ang_vel[3])
     nothing
 end
 
diff --git a/src/contact_search.jl b/src/contact_search.jl
@@ -109,7 +109,7 @@ Perform an O(n*log(n)) cell-based contact search between all grains in the
 """
 function findContactsInGrid!(simulation::Simulation, grid::Any)
 
-    distance_modifier = [0., 0.]
+    distance_modifier = [0., 0., 0.]
     i_corrected = 0
     j_corrected = 0
 
@@ -173,7 +173,7 @@ function checkForContacts(simulation::Simulation,
                           r_candidate::Float64;
                           return_when_overlap_found::Bool=false)
 
-    distance_modifier = zeros(2)
+    distance_modifier = zeros(3)
     overlaps_found::Integer = 0
 
     # Inter-grain position vector and grain overlap
@@ -225,7 +225,7 @@ function periodicBoundaryCorrection!(grid::Any, i::Integer, j::Integer)
 
     # vector for correcting inter-particle distance in case of
     # boundary periodicity
-    distance_modifier = zeros(2)
+    distance_modifier = zeros(3)
 
     # i and j are not corrected for periodic boundaries
     i_corrected = i
@@ -275,7 +275,7 @@ written to `simulation.contact_parallel_displacement`.
     boundaries.
 """
 function checkAndAddContact!(sim::Simulation, i::Int, j::Int,
-                             distance_modifier::Vector{Float64} = [0., 0.])
+                             distance_modifier::Vector{Float64} = [0., 0., 0.])
     if i < j
 
         @inbounds if !sim.grains[i].enabled || !sim.grains[j].enabled
@@ -327,8 +327,10 @@ function checkAndAddContact!(sim::Simulation, i::Int, j::Int,
                             position_ij
                         @inbounds fill!(sim.grains[i].
                               contact_parallel_displacement[ic] , 0.)
-                        @inbounds sim.grains[i].contact_rotation[ic] = 0.
+                        @inbounds fill!(sim.grains[i].contact_rotation[ic] , 0.)
                         @inbounds sim.grains[i].contact_age[ic] = 0.
+                        @inbounds sim.grains[i].contact_area[ic] = 0.
+                        @inbounds sim.grains[i].compressive_failure[ic] = false
                         break
                     end
                 end
diff --git a/src/datatypes.jl b/src/datatypes.jl
@@ -24,15 +24,16 @@ mutable struct GrainCylindrical
     lin_disp::Vector{Float64}
 
     # Angular kinematic degrees of freedom for vertical rotation around center
-    ang_pos::Float64
-    ang_vel::Float64
-    ang_acc::Float64
-    torque::Float64
+    ang_pos::Vector{Float64}
+    ang_vel::Vector{Float64}
+    ang_acc::Vector{Float64}
+    torque::Vector{Float64}
 
     # Kinematic constraint flags
     fixed::Bool
     allow_x_acc::Bool
     allow_y_acc::Bool
+    allow_z_acc::Bool
     rotating::Bool
     enabled::Bool
 
@@ -65,8 +66,10 @@ mutable struct GrainCylindrical
     contacts::Vector{Int}
     position_vector::Vector{Vector{Float64}}
     contact_parallel_displacement::Vector{Vector{Float64}}
-    contact_rotation::Vector{Float64}
+    contact_rotation::Vector{Vector{Float64}}
     contact_age::Vector{Float64}
+    contact_area::Vector{Float64}
+    compressive_failure::Vector{Bool}
 
     granular_stress::Vector{Float64}
     ocean_stress::Vector{Float64}
@@ -128,6 +131,7 @@ mutable struct GrainArrays
     fixed::Vector{Int}
     allow_x_acc::Vector{Int}
     allow_y_acc::Vector{Int}
+    allow_z_acc::Vector{Int}
     rotating::Vector{Int}
     enabled::Vector{Int}
 
diff --git a/src/grain.jl b/src/grain.jl
@@ -24,7 +24,7 @@ export addGrainCylindrical!
                                     atmosphere_drag_coeff_vert,
                                     atmosphere_drag_coeff_horiz,
                                     pressure, fixed,
-                                    allow_x_acc, allow_y_acc,
+                                    allow_x_acc, allow_y_acc, allow_z_acc,
                                     rotating, enabled, verbose,
                                     ocean_grid_pos, atmosphere_grid_pos,
                                     n_contact, granular_stress, ocean_stress,
@@ -39,21 +39,34 @@ are optional, and come with default values.  The only required arguments are
 # Arguments
 * `simulation::Simulation`: the simulation object where the grain should be
     added to.
-* `lin_pos::Vector{Float64}`: linear position of grain center [m].
+* `lin_pos::Vector{Float64}`: linear position of grain center [m]. If a
+    two-component vector is used, the values will be mapped to *x* and *y*, and
+    the *z* component will be set to zero.
 * `contact_radius::Float64`: grain radius for granular interaction [m].
 * `thickness::Float64`: grain thickness [m].
 * `areal_radius = false`: grain radius for determining sea-ice concentration
     [m].
-* `lin_vel::Vector{Float64} = [0., 0.]`: linear velocity [m/s].
-* `lin_acc::Vector{Float64} = [0., 0.]`: linear acceleration [m/s^2].
-* `force::Vector{Float64} = [0., 0.]`: linear force balance [N].
-* `ang_pos::Float64 = 0.`: angular position around its center vertical axis
-    [rad].
-* `ang_vel::Float64 = 0.`: angular velocity around its center vertical axis
-    [rad/s].
-* `ang_acc::Float64 = 0.`: angular acceleration around its center vertical axis
-    [rad/s^2].
-* `torque::Float64 = 0.`: torque around its center vertical axis [N*m]
+* `lin_vel::Vector{Float64} = [0., 0., 0.]`: linear velocity [m/s]. If a
+    two-component vector is used, the values will be mapped to *x* and *y*, and
+    the *z* component will be set to zero.
+* `lin_acc::Vector{Float64} = [0., 0., 0.]`: linear acceleration [m/s^2]. If a
+    two-component vector is used, the values will be mapped to *x* and *y*, and
+    the *z* component will be set to zero.
+* `force::Vector{Float64} = [0., 0., 0.]`: linear force balance [N]. If a
+    two-component vector is used, the values will be mapped to *x* and *y*, and
+    the *z* component will be set to zero.
+* `ang_pos::Float64 = [0., 0., 0.]`: angular position around its center vertical
+    axis [rad]. If a scalar is used, the value will be mapped to *z*, and the
+    *x* and *y* components will be set to zero.
+* `ang_vel::Float64 = [0., 0., 0.]`: angular velocity around its center vertical
+    axis [rad/s]. If a scalar is used, the value will be mapped to *z*, and the
+    *x* and *y* components will be set to zero.
+* `ang_acc::Float64 = [0., 0., 0.]`: angular acceleration around its center
+    vertical axis [rad/s^2]. If a scalar is used, the value will be mapped to
+    *z*, and the *x* and *y* components will be set to zero.
+* `torque::Float64 = [0., 0., 0.]`: torque around its center vertical axis
+    [N*m]. If a scalar is used, the value will be mapped to *z*, and the *x* and
+    *y* components will be set to zero.
 * `density::Float64 = 934.`: grain mean density [kg/m^3].
 * `contact_stiffness_normal::Float64 = 1e7`: contact-normal stiffness [N/m];
     overridden if `youngs_modulus` is set to a positive value.
@@ -75,8 +88,8 @@ are optional, and come with default values.  The only required arguments are
 * `shear_strength::Float64 = 0.`: shear strength of bonded contacts [Pa].
 * `strength_heal_rate::Float64 = 0.`: rate at which contact bond
     strength is obtained [Pa/s].
-* `fracture_toughness::Float64 = 0.`: maximum compressive
-    strength on granular contact (not currently enforced) [m^{1/2}*Pa]. A value
+* `fracture_toughness::Float64 = 0.`: fracture toughness which influences the 
+    maximum compressive strength on granular contact [m^{1/2}*Pa]. A value
     of 1.285e3 m^{1/2}*Pa is used for sea ice by Hopkins 2004.
 * `ocean_drag_coeff_vert::Float64 = 0.85`: vertical drag coefficient for ocean
     against grain sides [-].
@@ -90,6 +103,7 @@ are optional, and come with default values.  The only required arguments are
 * `fixed::Bool = false`: grain is fixed to a constant velocity (e.g. zero).
 * `allow_x_acc::Bool = false`: override `fixed` along `x`.
 * `allow_y_acc::Bool = false`: override `fixed` along `y`.
+* `allow_z_acc::Bool = false`: override `fixed` along `z`.
 * `rotating::Bool = true`: grain is allowed to rotate.
 * `enabled::Bool = true`: grain interacts with other grains.
 * `verbose::Bool = true`: display diagnostic information during the function
@@ -99,11 +113,11 @@ are optional, and come with default values.  The only required arguments are
 * `atmosphere_grid_pos::Array{Int, 1} = [0, 0]`: position of grain in the
     atmosphere grid.
 * `n_contacts::Int = 0`: number of contacts with other grains.
-* `granular_stress::Vector{Float64} = [0., 0.]`: resultant stress on grain
+* `granular_stress::Vector{Float64} = [0., 0., 0.]`: resultant stress on grain
     from granular interactions [Pa].
-* `ocean_stress::Vector{Float64} = [0., 0.]`: resultant stress on grain from
+* `ocean_stress::Vector{Float64} = [0., 0., 0.]`: resultant stress on grain from
     ocean drag [Pa].
-* `atmosphere_stress::Vector{Float64} = [0., 0.]`: resultant stress on grain
+* `atmosphere_stress::Vector{Float64} = [0., 0., 0.]`: resultant stress on grain
     from atmosphere drag [Pa].
 * `thermal_energy::Float64 = 0.0`: thermal energy of grain [J].
 * `color::Int=0`: type number, usually used for associating a color to the grain
@@ -111,12 +125,13 @@ are optional, and come with default values.  The only required arguments are
 
 # Examples
 The most basic example adds a new grain to the simulation `sim`, with a 
-center at `[1., 2.]`, a radius of `1.` meter, and a thickness of `0.5` 
+center at `[1., 2., 0.]`, a radius of `1.` meter, and a thickness of `0.5` 
 meter:
 
 ```julia
 Granular.addGrainCylindrical!(sim, [1., 2.], 1., .5)
 ```
+Note that the *z* component is set to zero if a two-component vector is passed.
 
 The following example will create a grain with tensile and shear strength, and a
 velocity of 0.5 m/s towards -x:
@@ -142,13 +157,13 @@ function addGrainCylindrical!(simulation::Simulation,
                                 contact_radius::Float64,
                                 thickness::Float64;
                                 areal_radius = false,
-                                lin_vel::Vector{Float64} = [0., 0.],
-                                lin_acc::Vector{Float64} = [0., 0.],
-                                force::Vector{Float64} = [0., 0.],
-                                ang_pos::Float64 = 0.,
-                                ang_vel::Float64 = 0.,
-                                ang_acc::Float64 = 0.,
-                                torque::Float64 = 0.,
+                                lin_vel::Vector{Float64} = [0., 0., 0.],
+                                lin_acc::Vector{Float64} = [0., 0., 0.],
+                                force::Vector{Float64} = [0., 0., 0.],
+                                ang_pos::Vector{Float64} = [0., 0., 0.],
+                                ang_vel::Vector{Float64} = [0., 0., 0.],
+                                ang_acc::Vector{Float64} = [0., 0., 0.],
+                                torque::Vector{Float64} = [0., 0., 0.],
                                 density::Float64 = 934.,
                                 contact_stiffness_normal::Float64 = 1e7,
                                 contact_stiffness_tangential::Float64 = 0.,
@@ -170,30 +185,52 @@ function addGrainCylindrical!(simulation::Simulation,
                                 fixed::Bool = false,
                                 allow_x_acc::Bool = false,
                                 allow_y_acc::Bool = false,
+                                allow_z_acc::Bool = false,
                                 rotating::Bool = true,
                                 enabled::Bool = true,
                                 verbose::Bool = true,
                                 ocean_grid_pos::Array{Int, 1} = [0, 0],
                                 atmosphere_grid_pos::Array{Int, 1} = [0, 0],
                                 n_contacts::Int = 0,
-                                granular_stress::Vector{Float64} = [0., 0.],
-                                ocean_stress::Vector{Float64} = [0., 0.],
-                                atmosphere_stress::Vector{Float64} = [0., 0.],
+                                granular_stress::Vector{Float64} = [0., 0., 0.],
+                                ocean_stress::Vector{Float64} = [0., 0., 0.],
+                                atmosphere_stress::Vector{Float64} = [0., 0., 0.],
                                 thermal_energy::Float64 = 0.,
                                 color::Int = 0)
 
     # Check input values
-    if length(lin_pos) != 2
-        error("Linear position must be a two-element array " *
-              "(lin_pos = $lin_pos)")
+    if length(lin_pos) != 3
+        lin_pos = vecTo3d(lin_pos)
     end
-    if length(lin_vel) != 2
-        error("Linear velocity must be a two-element array " *
-              "(lin_vel = $lin_vel)")
+    if length(lin_vel) != 3
+        lin_vel = vecTo3d(lin_vel)
     end
-    if length(lin_acc) != 2
-        error("Linear acceleration must be a two-element array " *
-              "(lin_acc = $lin_acc)")
+    if length(lin_acc) != 3
+        lin_acc = vecTo3d(lin_acc)
+    end
+    if length(force) != 3
+        force = vecTo3d(force)
+    end
+    if length(ang_pos) != 3
+        ang_pos = vecTo3d(ang_pos)
+    end
+    if length(ang_vel) != 3
+        ang_vel = vecTo3d(ang_vel)
+    end
+    if length(ang_acc) != 3
+        ang_acc = vecTo3d(ang_acc)
+    end
+    if length(torque) != 3
+        torque = vecTo3d(torque)
+    end
+    if length(granular_stress) != 3
+        granular_stress = vecTo3d(granular_stress)
+    end
+    if length(ocean_stress) != 3
+        ocean_stress = vecTo3d(ocean_stress)
+    end
+    if length(atmosphere_stress) != 3
+        atmosphere_stress = vecTo3d(atmosphere_stress)
     end
     if contact_radius <= 0.0
         error("Radius must be greater than 0.0 " *
@@ -212,81 +249,87 @@ function addGrainCylindrical!(simulation::Simulation,
     position_vector = Vector{Vector{Float64}}(undef, simulation.Nc_max)
     contact_parallel_displacement =
         Vector{Vector{Float64}}(undef, simulation.Nc_max)
-    contact_rotation::Vector{Float64} = zeros(Float64, simulation.Nc_max)
+    contact_rotation = Vector{Vector{Float64}}(undef, simulation.Nc_max)
     contact_age::Vector{Float64} = zeros(Float64, simulation.Nc_max)
+    contact_area::Vector{Float64} = zeros(Float64, simulation.Nc_max)
+    compressive_failure::Vector{Bool} = zeros(Bool, simulation.Nc_max)
     for i=1:simulation.Nc_max
-        position_vector[i] = zeros(2)
-        contact_parallel_displacement[i] = zeros(2)
+        position_vector[i] = zeros(3)
+        contact_rotation[i] = zeros(3)
+        contact_parallel_displacement[i] = zeros(3)
     end
 
     # Create grain object with placeholder values for surface area, volume, 
     # mass, and moment of inertia.
     grain = GrainCylindrical(density,
 
-                                 thickness,
-                                 contact_radius,
-                                 areal_radius,
-                                 1.0,  # circumreference
-                                 1.0,  # horizontal_surface_area
-                                 1.0,  # side_surface_area
-                                 1.0,  # volume
-                                 1.0,  # mass
-                                 1.0,  # moment_of_inertia
-                                 lin_pos,
-                                 lin_vel,
-                                 lin_acc,
-                                 force,
-                                 [0., 0.], # external_body_force
-                                 [0., 0.], # lin_disp
-
-                                 ang_pos,
-                                 ang_vel,
-                                 ang_acc,
-                                 torque,
-
-                                 fixed,
-                                 allow_x_acc,
-                                 allow_y_acc,
-                                 rotating,
-                                 enabled,
-
-                                 contact_stiffness_normal,
-                                 contact_stiffness_tangential,
-                                 contact_viscosity_normal,
-                                 contact_viscosity_tangential,
-                                 contact_static_friction,
-                                 contact_dynamic_friction,
-
-                                 youngs_modulus,
-                                 poissons_ratio,
-                                 tensile_strength,
-                                 shear_strength,
-                                 strength_heal_rate,
-                                 fracture_toughness,
-
-                                 ocean_drag_coeff_vert,
-                                 ocean_drag_coeff_horiz,
-                                 atmosphere_drag_coeff_vert,
-                                 atmosphere_drag_coeff_horiz,
-
-                                 pressure,
-                                 n_contacts,
-                                 ocean_grid_pos,
-                                 atmosphere_grid_pos,
-                                 contacts,
-                                 position_vector,
-                                 contact_parallel_displacement,
-                                 contact_rotation,
-                                 contact_age,
-
-                                 granular_stress,
-                                 ocean_stress,
-                                 atmosphere_stress,
-
-                                 thermal_energy,
-
-                                 color
-                                )
+                             thickness,
+                             contact_radius,
+                             areal_radius,
+                             1.0,  # circumreference
+                             1.0,  # horizontal_surface_area
+                             1.0,  # side_surface_area
+                             1.0,  # volume
+                             1.0,  # mass
+                             1.0,  # moment_of_inertia
+                             lin_pos,
+                             lin_vel,
+                             lin_acc,
+                             force,
+                             [0., 0., 0.], # external_body_force
+                             [0., 0., 0.], # lin_disp
+
+                             ang_pos,
+                             ang_vel,
+                             ang_acc,
+                             torque,
+
+                             fixed,
+                             allow_x_acc,
+                             allow_y_acc,
+                             allow_z_acc,
+                             rotating,
+                             enabled,
+
+                             contact_stiffness_normal,
+                             contact_stiffness_tangential,
+                             contact_viscosity_normal,
+                             contact_viscosity_tangential,
+                             contact_static_friction,
+                             contact_dynamic_friction,
+
+                             youngs_modulus,
+                             poissons_ratio,
+                             tensile_strength,
+                             shear_strength,
+                             strength_heal_rate,
+                             fracture_toughness,
+
+                             ocean_drag_coeff_vert,
+                             ocean_drag_coeff_horiz,
+                             atmosphere_drag_coeff_vert,
+                             atmosphere_drag_coeff_horiz,
+
+                             pressure,
+                             n_contacts,
+                             ocean_grid_pos,
+                             atmosphere_grid_pos,
+                             contacts,
+                             position_vector,
+                             contact_parallel_displacement,
+                             contact_rotation,
+                             contact_age,
+                             contact_area,
+                             compressive_failure,
+
+                             granular_stress,
+                             ocean_stress,
+                             atmosphere_stress,
+
+                             thermal_energy,
+
+                             color
+                            )
 
     # Overwrite previous placeholder values
     grain.circumreference = grainCircumreference(grain)
@@ -400,6 +443,8 @@ function convertGrainDataToArrays(simulation::Simulation)
                         Array{Int}(undef, length(simulation.grains)),
                         ## allow_y_acc
                         Array{Int}(undef, length(simulation.grains)),
+                        ## allow_z_acc
+                        Array{Int}(undef, length(simulation.grains)),
                         ## rotating
                         Array{Int}(undef, length(simulation.grains)),
                         ## enabled
@@ -479,22 +524,23 @@ function convertGrainDataToArrays(simulation::Simulation)
         ifarr.mass[i] = simulation.grains[i].mass
         ifarr.moment_of_inertia[i] = simulation.grains[i].moment_of_inertia
 
-        ifarr.lin_pos[1:2, i] = simulation.grains[i].lin_pos
-        ifarr.lin_vel[1:2, i] = simulation.grains[i].lin_vel
-        ifarr.lin_acc[1:2, i] = simulation.grains[i].lin_acc
-        ifarr.force[1:2, i] = simulation.grains[i].force
-        ifarr.external_body_force[1:2, i] =
+        ifarr.lin_pos[1:3, i] = simulation.grains[i].lin_pos
+        ifarr.lin_vel[1:3, i] = simulation.grains[i].lin_vel
+        ifarr.lin_acc[1:3, i] = simulation.grains[i].lin_acc
+        ifarr.force[1:3, i] = simulation.grains[i].force
+        ifarr.external_body_force[1:3, i] =
             simulation.grains[i].external_body_force
-        ifarr.lin_disp[1:2, i] = simulation.grains[i].lin_disp
+        ifarr.lin_disp[1:3, i] = simulation.grains[i].lin_disp
 
-        ifarr.ang_pos[3, i] = simulation.grains[i].ang_pos
-        ifarr.ang_vel[3, i] = simulation.grains[i].ang_vel
-        ifarr.ang_acc[3, i] = simulation.grains[i].ang_acc
-        ifarr.torque[3, i] = simulation.grains[i].torque
+        ifarr.ang_pos[1:3, i] = simulation.grains[i].ang_pos
+        ifarr.ang_vel[1:3, i] = simulation.grains[i].ang_vel
+        ifarr.ang_acc[1:3, i] = simulation.grains[i].ang_acc
+        ifarr.torque[1:3, i] = simulation.grains[i].torque
 
         ifarr.fixed[i] = Int(simulation.grains[i].fixed)
         ifarr.allow_x_acc[i] = Int(simulation.grains[i].allow_x_acc)
         ifarr.allow_y_acc[i] = Int(simulation.grains[i].allow_y_acc)
+        ifarr.allow_z_acc[i] = Int(simulation.grains[i].allow_z_acc)
         ifarr.rotating[i] = Int(simulation.grains[i].rotating)
         ifarr.enabled[i] = Int(simulation.grains[i].enabled)
 
@@ -531,10 +577,9 @@ function convertGrainDataToArrays(simulation::Simulation)
         ifarr.pressure[i] = simulation.grains[i].pressure
         ifarr.n_contacts[i] = simulation.grains[i].n_contacts
 
-        ifarr.granular_stress[1:2, i] = simulation.grains[i].granular_stress
-        ifarr.ocean_stress[1:2, i] = simulation.grains[i].ocean_stress
-        ifarr.atmosphere_stress[1:2, i] =
-            simulation.grains[i].atmosphere_stress
+        ifarr.granular_stress[1:3, i] = simulation.grains[i].granular_stress
+        ifarr.ocean_stress[1:3, i] = simulation.grains[i].ocean_stress
+        ifarr.atmosphere_stress[1:3, i] = simulation.grains[i].atmosphere_stress
 
         ifarr.thermal_energy[i] = simulation.grains[i].thermal_energy
 
@@ -576,6 +621,7 @@ function deleteGrainArrays!(ifarr::GrainArrays)
     ifarr.fixed = i1
     ifarr.allow_x_acc = i1
     ifarr.allow_y_acc = i1
+    ifarr.allow_z_acc = i1
     ifarr.rotating = i1
     ifarr.enabled = i1
 
@@ -643,22 +689,23 @@ function printGrainInfo(f::GrainCylindrical)
     println("  fixed:       $(f.fixed)")
     println("  allow_x_acc: $(f.allow_x_acc)")
     println("  allow_y_acc: $(f.allow_y_acc)")
+    println("  allow_z_acc: $(f.allow_z_acc)")
     println("  rotating:    $(f.rotating)")
     println("  enabled:     $(f.enabled)\n")
 
-    println("  k_n:     $(f.contact_stiffness_normal) N/m")
-    println("  k_t:     $(f.contact_stiffness_tangential) N/m")
+    println("  k_n: $(f.contact_stiffness_normal) N/m")
+    println("  k_t: $(f.contact_stiffness_tangential) N/m")
     println("  γ_n: $(f.contact_viscosity_normal) N/(m/s)")
     println("  γ_t: $(f.contact_viscosity_tangential) N/(m/s)")
-    println("  μ_s:    $(f.contact_static_friction)")
-    println("  μ_d:    $(f.contact_dynamic_friction)\n")
+    println("  μ_s: $(f.contact_static_friction)")
+    println("  μ_d: $(f.contact_dynamic_friction)\n")
 
-    println("  E:                  $(f.youngs_modulus) Pa")
-    println("  ν:                  $(f.poissons_ratio)")
-    println("  tensile_strength:   $(f.tensile_strength) Pa")
-    println("  shear_strength:     $(f.shear_strength) Pa")
-    println("  strength_heal_rate: $(f.strength_heal_rate) Pa/s")
-    println("  fracture_toughness: $(f.fracture_toughness) m^0.5 Pa\n")
+    println("  E:                    $(f.youngs_modulus) Pa")
+    println("  ν:                    $(f.poissons_ratio)")
+    println("  tensile_strength:     $(f.tensile_strength) Pa")
+    println("  shear_strength:       $(f.shear_strength) Pa")
+    println("  strength_heal_rate:   $(f.strength_heal_rate) Pa/s")
+    println("  fracture_toughness:   $(f.fracture_toughness) m^0.5 Pa\n")
 
     println("  c_o_v:  $(f.ocean_drag_coeff_vert)")
     println("  c_o_h:  $(f.ocean_drag_coeff_horiz)")
@@ -754,7 +801,7 @@ Add to the value of the external body force on a grain.
 * `force::Vector{Float64}`: a vector of force [N]
 """
 function addBodyForce!(grain::GrainCylindrical, force::Vector{Float64})
-    grain.external_body_force += force
+    grain.external_body_force += vecTo3d(force)
 end
 
 export setBodyForce!
@@ -768,7 +815,7 @@ Set the value of the external body force on a grain.
 * `force::Vector{Float64}`: a vector of force [N]
 """
 function setBodyForce!(grain::GrainCylindrical, force::Vector{Float64})
-    grain.external_body_force = force
+    grain.external_body_force = vecTo3d(force)
 end
 
 export compareGrains
@@ -781,11 +828,9 @@ function compareGrains(if1::GrainCylindrical, if2::GrainCylindrical)
 
     @test if1.density ≈ if2.density
     @test if1.thickness ≈ if2.thickness
-    @test if1.contact_radius ≈
-        if2.contact_radius
+    @test if1.contact_radius ≈ if2.contact_radius
     @test if1.areal_radius ≈ if2.areal_radius
-    @test if1.circumreference ≈
-        if2.circumreference
+    @test if1.circumreference ≈ if2.circumreference
     @test if1.horizontal_surface_area ≈ if2.horizontal_surface_area
     @test if1.side_surface_area ≈ if2.side_surface_area
     @test if1.volume ≈ if2.volume
@@ -809,11 +854,9 @@ function compareGrains(if1::GrainCylindrical, if2::GrainCylindrical)
     @test if1.enabled == if2.enabled
 
     @test if1.contact_stiffness_normal ≈ if2.contact_stiffness_normal
-    @test if1.contact_stiffness_tangential ≈ 
-        if2.contact_stiffness_tangential
+    @test if1.contact_stiffness_tangential ≈ if2.contact_stiffness_tangential
     @test if1.contact_viscosity_normal ≈ if2.contact_viscosity_normal
-    @test if1.contact_viscosity_tangential ≈ 
-        if2.contact_viscosity_tangential
+    @test if1.contact_viscosity_tangential ≈ if2.contact_viscosity_tangential
     @test if1.contact_static_friction ≈ if2.contact_static_friction
     @test if1.contact_dynamic_friction ≈ if2.contact_dynamic_friction
 
@@ -822,25 +865,23 @@ function compareGrains(if1::GrainCylindrical, if2::GrainCylindrical)
     @test if1.tensile_strength ≈ if2.tensile_strength
     @test if1.shear_strength ≈ if2.shear_strength
     @test if1.strength_heal_rate ≈ if2.strength_heal_rate
-    @test if1.fracture_toughness ≈
-        if2.fracture_toughness
+    @test if1.fracture_toughness ≈ if2.fracture_toughness
 
     @test if1.ocean_drag_coeff_vert ≈ if2.ocean_drag_coeff_vert
     @test if1.ocean_drag_coeff_horiz ≈ if2.ocean_drag_coeff_horiz
-    @test if1.atmosphere_drag_coeff_vert ≈ 
-        if2.atmosphere_drag_coeff_vert
-    @test if1.atmosphere_drag_coeff_horiz ≈ 
-        if2.atmosphere_drag_coeff_horiz
+    @test if1.atmosphere_drag_coeff_vert ≈ if2.atmosphere_drag_coeff_vert
+    @test if1.atmosphere_drag_coeff_horiz ≈ if2.atmosphere_drag_coeff_horiz
 
     @test if1.pressure ≈ if2.pressure
     @test if1.n_contacts == if2.n_contacts
     @test if1.ocean_grid_pos == if2.ocean_grid_pos
     @test if1.contacts == if2.contacts
     @test if1.position_vector == if2.position_vector
-    @test if1.contact_parallel_displacement == 
-        if2.contact_parallel_displacement
+    @test if1.contact_parallel_displacement == if2.contact_parallel_displacement
     @test if1.contact_rotation == if2.contact_rotation
     @test if1.contact_age ≈ if2.contact_age
+    @test if1.contact_area ≈ if2.contact_area
+    @test if1.compressive_failure ≈ if2.compressive_failure
 
     @test if1.granular_stress ≈ if2.granular_stress
     @test if1.ocean_stress ≈ if2.ocean_stress
@@ -905,12 +946,12 @@ rotational) to zero in order to get rid of all kinetic energy.
 """
 function zeroKinematics!(sim::Simulation)
     for grain in sim.grains
-        grain.lin_vel .= zeros(2)
-        grain.lin_acc .= zeros(2)
-        grain.force .= zeros(2)
-        grain.lin_disp .= zeros(2)
-        grain.ang_vel = 0.
-        grain.ang_acc = 0.
-        grain.torque = 0.
+        grain.lin_vel .= zeros(3)
+        grain.lin_acc .= zeros(3)
+        grain.force .= zeros(3)
+        grain.lin_disp .= zeros(3)
+        grain.ang_vel .= zeros(3)
+        grain.ang_acc .= zeros(3)
+        grain.torque .= zeros(3)
     end
 end
diff --git a/src/grid.jl b/src/grid.jl
@@ -161,14 +161,15 @@ function sortGrainsInGrid!(simulation::Simulation, grid::Any; verbose=true)
             !grid.regular_grid &&
             i_old > 0 && j_old > 0 &&
             isPointInCell(grid, i_old, j_old,
-                          simulation.grains[idx].lin_pos, sw, se, ne, nw)
+                          simulation.grains[idx].lin_pos[1:2], sw, se, ne, nw)
             i = i_old
             j = j_old
 
         else
 
             if grid.regular_grid
-                i, j = Int.(floor.((simulation.grains[idx].lin_pos - grid.origo)
+                i, j = Int.(floor.((simulation.grains[idx].lin_pos[1:2]
+                                    - grid.origo)
                                    ./ grid.dx[1:2])) + [1,1]
             else
 
@@ -185,8 +186,9 @@ function sortGrainsInGrid!(simulation::Simulation, grid::Any; verbose=true)
                         j_t = max(min(j_old + j_rel, ny), 1)
                         
                         @inbounds if isPointInCell(grid, i_t, j_t,
-                                         simulation.grains[idx].lin_pos,
-                                         sw, se, ne, nw)
+                                                   simulation.grains[idx].
+                                                   lin_pos[1:2],
+                                                   sw, se, ne, nw)
                             i = i_t
                             j = j_t
                             found = true
@@ -201,7 +203,7 @@ function sortGrainsInGrid!(simulation::Simulation, grid::Any; verbose=true)
                 if !found
                     i, j = findCellContainingPoint(grid,
                                                    simulation.grains[idx].
-                                                   lin_pos,
+                                                   lin_pos[1:2],
                                                    sw, se, ne, nw)
                 end
             end
@@ -285,10 +287,10 @@ This function is a wrapper for `getCellCornerCoordinates()` and
 function getNonDimensionalCellCoordinates(grid::Any, i::Int, j::Int,
                                           point::Vector{Float64})
     if grid.regular_grid
-        return (point - Float64.([i-1,j-1]).*grid.dx[1:2])./grid.dx[1:2]
+        return (point[1:2] - Float64.([i-1,j-1]).*grid.dx[1:2])./grid.dx[1:2]
     else
         sw, se, ne, nw = getCellCornerCoordinates(grid.xq, grid.yq, i, j)
-        return conformalQuadrilateralCoordinates(sw, se, ne, nw, point)
+        return conformalQuadrilateralCoordinates(sw, se, ne, nw, point[1:2])
     end
 end
 
@@ -308,7 +310,7 @@ function isPointInCell(grid::Any, i::Int, j::Int,
                        method::String="Conformal")
 
     if grid.regular_grid
-        if [i,j] == Int.(floor.((point - grid.origo) ./ grid.dx[1:2])) + [1,1]
+        if [i,j] == Int.(floor.((point[1:2] - grid.origo) ./ grid.dx[1:2])) + [1,1]
             return true
         else
             return false
@@ -620,8 +622,8 @@ function findEmptyPositionInGridCell(simulation::Simulation,
                     # traverse list of grains in the target cell and check 
                     # for overlaps
                     for grain_idx in grid.grain_list[it, jt]
-                        overlap = norm(simulation.grains[grain_idx].lin_pos - 
-                                       pos) -
+                        overlap = norm(simulation.grains[grain_idx].
+                                       lin_pos[1:2] - pos) -
                         (simulation.grains[grain_idx].contact_radius + r)
 
                         if overlap < 0.
diff --git a/src/interaction.jl b/src/interaction.jl
@@ -63,10 +63,11 @@ function interactWalls!(sim::Simulation)
 
             # get overlap distance by projecting grain position onto wall-normal
             # vector. Overlap when δ_n < 0.0
-            δ_n = abs(dot(sim.walls[iw].normal, sim.grains[i].lin_pos) -
-                      sim.walls[iw].pos) - sim.grains[i].contact_radius
+            δ_n = norm(dot(sim.walls[iw].normal[1:2],
+                           sim.grains[i].lin_pos[1:2]) -
+                       sim.walls[iw].pos) - sim.grains[i].contact_radius
 
-            vel_n = dot(sim.walls[iw].normal, sim.grains[i].lin_vel)
+            vel_n = dot(sim.walls[iw].normal[1:2], sim.grains[i].lin_vel[1:2])
 
             if δ_n < 0.
                 if sim.grains[i].youngs_modulus > 0.
@@ -98,7 +99,7 @@ function interactWalls!(sim::Simulation)
                 end
 
                 sim.grains[i].force += -force_n .* sim.walls[iw].normal .*
-                    orientation
+                                               orientation
                 sim.walls[iw].force += force_n * orientation
             end
         end
@@ -127,7 +128,7 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
 
     # Inter-position vector, use stored value which is corrected for boundary
     # periodicity
-    p = simulation.grains[i].position_vector[ic]
+    p = simulation.grains[i].position_vector[ic][1:2]
     dist = norm(p)
 
     r_i = simulation.grains[i].contact_radius
@@ -137,11 +138,12 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
     δ_n = dist - (r_i + r_j)
 
     # Local axes
-    n = p/dist
+    n = p/max(dist, 1e-12)
     t = [-n[2], n[1]]
 
-    # Contact kinematics
-    vel_lin = simulation.grains[i].lin_vel - simulation.grains[j].lin_vel
+    # Contact kinematics (2d)
+    vel_lin = simulation.grains[i].lin_vel[1:2] -
+        simulation.grains[j].lin_vel[1:2]
     vel_n = dot(vel_lin, n)
 
     if !simulation.grains[i].rotating && !simulation.grains[j].rotating 
@@ -152,25 +154,38 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
     
     if rotation
         vel_t = dot(vel_lin, t) -
-            harmonicMean(r_i, r_j)*(simulation.grains[i].ang_vel +
-                                    simulation.grains[j].ang_vel)
+            harmonicMean(r_i, r_j)*(simulation.grains[i].ang_vel[3] +
+                                    simulation.grains[j].ang_vel[3])
 
         # Correct old tangential displacement for contact rotation and add new
-        δ_t0 = simulation.grains[i].contact_parallel_displacement[ic]
-        δ_t = dot(t, δ_t0 - (n*dot(n, δ_t0))) + vel_t*simulation.time_step
+        δ_t0 = simulation.grains[i].contact_parallel_displacement[ic][1:2]
+        if !simulation.grains[i].compressive_failure[ic]
+            δ_t = dot(t, δ_t0 - (n*dot(n, δ_t0))) + vel_t*simulation.time_step
+        else
+            # Use δ_t as total slip-distance vector
+            δ_t = δ_t0 .+ (vel_lin .+ vel_t.*t) .* simulation.time_step
+        end
 
-        # Determine the contact rotation
-        θ_t = simulation.grains[i].contact_rotation[ic] +
-            (simulation.grains[j].ang_vel - simulation.grains[i].ang_vel) *
-            simulation.time_step
+        # Determine the contact rotation (2d)
+        θ_t = simulation.grains[i].contact_rotation[ic][3] +
+            (simulation.grains[j].ang_vel[3] - 
+             simulation.grains[i].ang_vel[3]) * simulation.time_step
     end
 
     # Effective radius
     R_ij = harmonicMean(r_i, r_j)
 
+    # Determine which ice floe is the thinnest
+    h_min, idx_thinnest = findmin([simulation.grains[i].thickness,
+                                   simulation.grains[j].thickness])
+    idx_thinnest == 1 ? idx_thinnest = i : idx_thinnest = j
+    
+    # Contact length along z
+    Lz_ij = h_min
+
     # Contact area
-    A_ij = R_ij*min(simulation.grains[i].thickness, 
-                    simulation.grains[j].thickness)
+    A_ij = R_ij*Lz_ij
+    simulation.grains[i].contact_area[ic] = A_ij
 
     # Contact mechanical parameters
     if simulation.grains[i].youngs_modulus > 0. &&
@@ -190,7 +205,7 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
 
     else  # Micromechanical parameterization: k_n and k_t set explicitly
         k_n = harmonicMean(simulation.grains[i].contact_stiffness_normal,
-                         simulation.grains[j].contact_stiffness_normal)
+                           simulation.grains[j].contact_stiffness_normal)
 
         if rotation
             k_t = harmonicMean(simulation.grains[i].contact_stiffness_tangential,
@@ -223,26 +238,20 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
         end
 
     else
-        error("unknown contact_normal_rheology (k_n = $k_n, γ_n = $γ_n")
+        error("unknown contact_normal_rheology (k_n = $k_n, γ_n = $γ_n,
+              E = $E, A_ij = $A_ij, R_ij = $R_ij)
+              ")
     end
 
-    # Determine which grain is the weakest
-    compressive_strength = simulation.grains[i].fracture_toughness * 
-                           sqrt(simulation.grains[i].thickness)
-    compressive_strength_j = simulation.grains[j].fracture_toughness * 
-                             sqrt(simulation.grains[j].thickness)
-    idx_weakest = i
-    if compressive_strength_j < compressive_strength
-        compressive_strength = compressive_strength_j
-        idx_weakest = j
-    end
+    # Determine the compressive strength in Pa by the contact thickness and the
+    # minimum compressive strength [N]
+    compressive_strength = min(simulation.grains[i].fracture_toughness,
+                               simulation.grains[j].fracture_toughness) *
+                           Lz_ij^1.5
 
     # Grain-pair moment of inertia [m^4]
-    if rotation
-        I_ij = 2.0/3.0*R_ij^3*min(simulation.grains[i].thickness,
-                                  simulation.grains[j].thickness)
-    end
-
+    I_ij = 2.0/3.0*R_ij^3*min(simulation.grains[i].thickness,
+                              simulation.grains[j].thickness)
 
     # Contact tensile strength increases linearly with contact age until
     # tensile stress exceeds tensile strength.
@@ -264,7 +273,7 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
 
     # Reset contact age (breaking bond) if bond strength is exceeded
     if rotation
-        if δ_n >= 0.0 && abs(force_n)/A_ij + abs(M_t)*R_ij/I_ij > tensile_strength
+        if δ_n >= 0.0 && norm(force_n)/A_ij + norm(M_t)*R_ij/I_ij > tensile_strength
             force_n = 0.
             force_t = 0.
             simulation.grains[i].contacts[ic] = 0  # remove contact
@@ -272,7 +281,7 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
             simulation.grains[j].n_contacts -= 1
         end
     else
-        if δ_n >= 0.0 && abs(force_n)/A_ij > tensile_strength
+        if δ_n >= 0.0 && norm(force_n)/A_ij > tensile_strength
             force_n = 0.
             force_t = 0.
             simulation.grains[i].contacts[ic] = 0  # remove contact
@@ -281,36 +290,20 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
         end
     end
 
-    # Limit compressive stress if the prefactor is set to a positive value
-    if δ_n <= 0.0 && compressive_strength > 0. &&
-        abs(force_n) >= compressive_strength
-
-        # Determine the overlap distance where yeild stress is reached
-        δ_n_yield = -compressive_strength*A_ij/k_n
-
-        # Determine the excess overlap distance relative to yield
-        Δr = abs(δ_n) - abs(δ_n_yield)
-        
-        # Adjust radius and thickness of the weakest grain
-        simulation.grains[idx_weakest].contact_radius -= Δr
-        simulation.grains[idx_weakest].areal_radius -= Δr
-        simulation.grains[idx_weakest].thickness += 1.0/(π*Δr)
-    end
-
-    if rotation
+    if rotation && !simulation.grains[i].compressive_failure[ic]
         if k_t ≈ 0. && γ_t ≈ 0.
             # do nothing
 
         elseif k_t ≈ 0. && γ_t > 0.
-            force_t = abs(γ_t * vel_t)
+            force_t = norm(γ_t * vel_t)
 
             # Coulomb slip
-            if force_t > μ_d_minimum*abs(force_n)
-                force_t = μ_d_minimum*abs(force_n)
+            if force_t > μ_d_minimum*norm(force_n)
+                force_t = μ_d_minimum*norm(force_n)
 
                 # Nguyen et al 2009 "Thermomechanical modelling of friction effects
                 # in granular flows using the discrete element method"
-                E_shear = abs(force_t)*abs(vel_t)*simulation.time_step
+                E_shear = norm(force_t)*norm(vel_t)*simulation.time_step
 
                 # Assume equal thermal properties
                 simulation.grains[i].thermal_energy += 0.5*E_shear
@@ -325,13 +318,13 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
             force_t = -k_t*δ_t - γ_t*vel_t
 
             # Coulomb slip
-            if abs(force_t) > μ_d_minimum*abs(force_n)
-                force_t = μ_d_minimum*abs(force_n)*force_t/abs(force_t)
+            if norm(force_t) > μ_d_minimum*norm(force_n)
+                force_t = μ_d_minimum*norm(force_n)*force_t/norm(force_t)
                 δ_t = (-force_t - γ_t*vel_t)/k_t
 
-                # Nguyen et al 2009 "Thermomechanical modelling of friction effects
-                # in granular flows using the discrete element method"
-                E_shear = abs(force_t)*abs(vel_t)*simulation.time_step
+                # Nguyen et al 2009 "Thermomechanical modelling of friction
+                # effects in granular flows using the discrete element method"
+                E_shear = norm(force_t)*norm(vel_t)*simulation.time_step
 
                 # Assume equal thermal properties
                 simulation.grains[i].thermal_energy += 0.5*E_shear
@@ -343,28 +336,104 @@ function interactGrains!(simulation::Simulation, i::Int, j::Int, ic::Int)
         end
     end
 
+    just_failed = false
+    # Detect compressive failure if compressive force exceeds compressive
+    # strength
+    if δ_n <= 0.0 && compressive_strength > 0. &&
+        !simulation.grains[i].compressive_failure[ic] &&
+        norm(force_n.*n .+ force_t.*t) >= compressive_strength
+
+        # Register that compressive failure has occurred for this contact
+        simulation.grains[i].compressive_failure[ic] = true
+        just_failed = true
+    end
+
+    # Overwrite previous force results if compressive failure occurred
+    if simulation.grains[i].compressive_failure[ic] && δ_n < 0.0
+
+        # Normal stress on horizontal interface, assuming bending stresses are
+        # negligible (ocean density and gravity are hardcoded for now)
+        σ_n = (1e3 - simulation.grains[i].density) * 9.81 *
+            (simulation.grains[i].thickness + simulation.grains[j].thickness)
+
+        # Horizontal overlap area (two overlapping circles)
+        if 2.0*min(r_i, r_j) <= abs(δ_n)
+
+            # Complete overlap
+            A_h = π*min(r_i, r_j)^2
+
+        else
+            # Partial overlap, eq 14 from
+            # http://mathworld.wolfram.com/Circle-CircleIntersection.html
+            A_h = r_i^2.0*acos((dist^2.0 + r_i^2.0 - r_j^2.0)/(2.0*dist*r_i)) +
+                  r_j^2.0*acos((dist^2.0 + r_j^2.0 - r_i^2.0)/(2.0*dist*r_j)) -
+                  0.5*sqrt((-dist + r_i + r_j)*
+                           ( dist + r_i - r_j)*
+                           ( dist - r_i + r_j)*
+                           ( dist + r_i + r_j))
+        end
+        simulation.grains[i].contact_area[ic] = A_h
+
+        if just_failed
+            # Use δ_t as travel distance on horizontal slip surface, and set the
+            # original displacement to Coulomb-frictional limit in the direction
+            # of the pre-failure force
+            F = force_n.*n .+ force_t.*t
+            σ_t = μ_d_minimum*norm(σ_n).*F/norm(F)
+            δ_t = σ_t*A_h/(-k_t)
+
+            # Save energy loss in E_shear
+            E_shear = norm(F)*norm(vel_lin)*simulation.time_step
+            simulation.grains[i].thermal_energy += 0.5*E_shear
+            simulation.grains[j].thermal_energy += 0.5*E_shear
+
+        else
+
+            # Find horizontal stress on slip interface
+            σ_t = -k_t*δ_t/A_h
+
+            # Check for Coulomb-failure on the slip interface
+            if norm(σ_t) > μ_d_minimum*norm(σ_n)
+
+                σ_t = μ_d_minimum*norm(σ_n)*σ_t/norm(σ_t)
+                δ_t = σ_t*A_h/(-k_t)
+
+                E_shear = norm(σ_t*A_h)*norm(vel_lin)*simulation.time_step
+                simulation.grains[i].thermal_energy += 0.5*E_shear
+                simulation.grains[j].thermal_energy += 0.5*E_shear
+            end
+        end
+
+        force_n = dot(σ_t, n)*A_h
+        force_t = dot(σ_t, t)*A_h
+    end
+
     # Break bond under extension through bending failure
     if δ_n < 0.0 && tensile_strength > 0.0 && rotation && 
-        shear_strength > 0.0 && abs(force_t)/A_ij > shear_strength
+        shear_strength > 0.0 && norm(force_t)/A_ij > shear_strength
 
         force_n = 0.
         force_t = 0.
         simulation.grains[i].contacts[ic] = 0  # remove contact
         simulation.grains[i].n_contacts -= 1
         simulation.grains[j].n_contacts -= 1
+    else
+        simulation.grains[i].contact_age[ic] += simulation.time_step
     end
 
-    simulation.grains[i].contact_age[ic] += simulation.time_step
-
     if rotation
-        simulation.grains[i].contact_parallel_displacement[ic] = δ_t*t
-        simulation.grains[i].contact_rotation[ic] = θ_t
+        if simulation.grains[i].compressive_failure[ic]
+            simulation.grains[i].contact_parallel_displacement[ic] = vecTo3d(δ_t)
+        else
+            simulation.grains[i].contact_parallel_displacement[ic] = vecTo3d(δ_t.*t)
+        end
+        simulation.grains[i].contact_rotation[ic] = [0., 0., θ_t]
 
-        simulation.grains[i].force += force_n*n + force_t*t;
-        simulation.grains[j].force -= force_n*n + force_t*t;
+        simulation.grains[i].force += vecTo3d(force_n.*n + force_t.*t);
+        simulation.grains[j].force -= vecTo3d(force_n.*n + force_t.*t);
 
-        simulation.grains[i].torque += -force_t*R_ij + M_t
-        simulation.grains[j].torque += -force_t*R_ij - M_t
+        simulation.grains[i].torque[3] += -force_t*R_ij + M_t
+        simulation.grains[j].torque[3] += -force_t*R_ij - M_t
     else
         simulation.grains[i].force += force_n*n;
         simulation.grains[j].force -= force_n*n;
diff --git a/src/io.jl b/src/io.jl
@@ -393,6 +393,8 @@ function writeGrainVTK(simulation::Simulation,
                             "Fixed but allow (x) acceleration [-]")
     WriteVTK.vtk_point_data(vtkfile, ifarr.allow_y_acc,
                             "Fixed but allow (y) acceleration [-]")
+    WriteVTK.vtk_point_data(vtkfile, ifarr.allow_z_acc,
+                            "Fixed but allow (z) acceleration [-]")
     WriteVTK.vtk_point_data(vtkfile, ifarr.rotating, "Free to rotate [-]")
     WriteVTK.vtk_point_data(vtkfile, ifarr.enabled, "Enabled [-]")
 
@@ -481,8 +483,10 @@ function writeGrainInteractionVTK(simulation::Simulation,
     contact_stiffness = Float64[]
     tensile_stress = Float64[]
     shear_displacement = Vector{Float64}[]
-    contact_rotation = Float64[]
+    contact_rotation = Vector{Float64}[]
     contact_age = Float64[]
+    contact_area = Float64[]
+    compressive_failure = Int[]
 
     for i=1:length(simulation.grains)
         for ic=1:simulation.Nc_max
@@ -549,6 +553,9 @@ function writeGrainInteractionVTK(simulation::Simulation,
                       simulation.grains[i].contact_rotation[ic])
 
                 push!(contact_age, simulation.grains[i].contact_age[ic])
+                push!(contact_area, simulation.grains[i].contact_area[ic])
+                push!(compressive_failure,
+                      Int(simulation.grains[i].compressive_failure[ic]))
             end
         end
     end
@@ -577,7 +584,7 @@ function writeGrainInteractionVTK(simulation::Simulation,
         for i=1:length(i1)
             write(f, "$(inter_particle_vector[i][1]) ")
             write(f, "$(inter_particle_vector[i][2]) ")
-            write(f, "0.0 ")
+            write(f, "$(inter_particle_vector[i][3]) ")
         end
         write(f, "\n")
         write(f, "        </DataArray>\n")
@@ -587,7 +594,7 @@ function writeGrainInteractionVTK(simulation::Simulation,
         for i=1:length(i1)
             @inbounds write(f, "$(shear_displacement[i][1]) ")
             @inbounds write(f, "$(shear_displacement[i][2]) ")
-            write(f, "0.0 ")
+            @inbounds write(f, "$(shear_displacement[i][3]) ")
         end
         write(f, "\n")
         write(f, "        </DataArray>\n")
@@ -637,10 +644,12 @@ function writeGrainInteractionVTK(simulation::Simulation,
         write(f, "        </DataArray>\n")
 
         write(f, "        <DataArray type=\"Float32\" " *
-              "Name=\"Contact rotation [rad]\" NumberOfComponents=\"1\" 
+              "Name=\"Contact rotation [rad]\" NumberOfComponents=\"3\" 
         format=\"ascii\">\n")
         for i=1:length(i1)
-            @inbounds write(f, "$(contact_rotation[i]) ")
+            @inbounds write(f, "$(contact_rotation[i][1]) ")
+            @inbounds write(f, "$(contact_rotation[i][2]) ")
+            @inbounds write(f, "$(contact_rotation[i][3]) ")
         end
         write(f, "\n")
         write(f, "        </DataArray>\n")
@@ -654,6 +663,24 @@ function writeGrainInteractionVTK(simulation::Simulation,
         write(f, "\n")
         write(f, "        </DataArray>\n")
 
+        write(f, "        <DataArray type=\"Float32\" " *
+              "Name=\"Contact area [m*m]\" NumberOfComponents=\"1\" 
+        format=\"ascii\">\n")
+        for i=1:length(i1)
+            @inbounds write(f, "$(contact_area[i]) ")
+        end
+        write(f, "\n")
+        write(f, "        </DataArray>\n")
+
+        write(f, "        <DataArray type=\"Float32\" " *
+              "Name=\"Compressive failure [-]\" NumberOfComponents=\"1\" 
+        format=\"ascii\">\n")
+        for i=1:length(i1)
+            @inbounds write(f, "$(Int(compressive_failure[i])) ")
+        end
+        write(f, "\n")
+        write(f, "        </DataArray>\n")
+
         write(f, "      </CellData>\n")
         write(f, "      <Points>\n")
 
@@ -662,7 +689,7 @@ function writeGrainInteractionVTK(simulation::Simulation,
         write(f, "        <DataArray type=\"Float32\" Name=\"Points\" " *
               "NumberOfComponents=\"3\" format=\"ascii\">\n")
         for i in simulation.grains
-            @inbounds write(f, "$(i.lin_pos[1]) $(i.lin_pos[2]) 0.0 ")
+            @inbounds write(f, "$(i.lin_pos[1]) $(i.lin_pos[2]) $(i.lin_pos[3]) ")
         end
         write(f, "\n")
         write(f, "        </DataArray>\n")
@@ -868,6 +895,7 @@ imagegrains.PointArrayStatus = [
 'Fixed in space [-]',
 'Fixed but allow (x) acceleration [-]',
 'Fixed but allow (y) acceleration [-]',
+'Fixed but allow (z) acceleration [-]',
 'Free to rotate [-]',
 'Enabled [-]',
 'Contact stiffness (normal) [N m^-1]',
@@ -1103,6 +1131,12 @@ function render(simulation::Simulation; pvpython::String="pvpython",
                 if isfile("$(simulation.id)/$(simulation.id).mp4")
                     rm("$(simulation.id)/$(simulation.id).avi")
                 end
+            catch return_signal
+                if isa(return_signal, Base.UVError)
+                    Compat.@warn "Could not run external ffmpeg command, " *
+                    "skipping conversion from " *
+                    "$(simulation.id)/$(simulation.id).avi to mp4."
+                end
             end
         end
 
@@ -1131,7 +1165,7 @@ function render(simulation::Simulation; pvpython::String="pvpython",
                 end
             catch return_signal
                 if isa(return_signal, Base.UVError)
-                    Compat.@info "Skipping gif merge since `$convert` " *
+                    Compat.@warn "Skipping gif merge since `$convert` " *
                         "was not found."
                 end
             end
@@ -1321,8 +1355,9 @@ function plotGrains(sim::Simulation;
         contact_stiffness = Float64[]
         tensile_stress = Float64[]
         shear_displacement = Vector{Float64}[]
-        contact_rotation = Float64[]
+        contact_rotation = Vector{Float64}[]
         contact_age = Float64[]
+        compressive_failure = Int[]
         for i=1:length(sim.grains)
             for ic=1:sim.Nc_max
                 if sim.grains[i].contacts[ic] > 0
@@ -1374,6 +1409,8 @@ function plotGrains(sim::Simulation;
                           sim.grains[i].contact_rotation[ic])
 
                     push!(contact_age, sim.grains[i].contact_age[ic])
+                    push!(compressive_failure,
+                      sim.grains[i].compressive_failure[ic])
                 end
             end
         end
diff --git a/src/ocean.jl b/src/ocean.jl
@@ -388,10 +388,10 @@ function applyOceanDragToGrain!(grain::GrainCylindrical,
     drag_force = ρ_o * π *
     (2.0*grain.ocean_drag_coeff_vert*grain.areal_radius*draft + 
         grain.ocean_drag_coeff_horiz*grain.areal_radius^2.0) *
-        ([u, v] - grain.lin_vel)*norm([u, v] - grain.lin_vel)
+        ([u, v] - grain.lin_vel[1:2])*norm([u, v] - grain.lin_vel[1:2])
 
-    grain.force += drag_force
-    grain.ocean_stress = drag_force/grain.horizontal_surface_area
+    grain.force += vecTo3d(drag_force)
+    grain.ocean_stress = vecTo3d(drag_force/grain.horizontal_surface_area)
     nothing
 end
 
@@ -407,11 +407,12 @@ function applyOceanVorticityToGrain!(grain::GrainCylindrical,
     ρ_o = 1000.   # ocean density
     draft = grain.thickness - freeboard  # height of submerged thickness
 
-    grain.torque +=
+    grain.torque[3] +=
         π * grain.areal_radius^4. * ρ_o * 
         (grain.areal_radius/5. * grain.ocean_drag_coeff_horiz + 
         draft * grain.ocean_drag_coeff_vert) * 
-        abs(.5 * ocean_curl - grain.ang_vel) * (.5 * ocean_curl - grain.ang_vel)
+        norm(.5 * ocean_curl - grain.ang_vel[3]) * 
+        (.5 * ocean_curl - grain.ang_vel[3])
     nothing
 end
 
diff --git a/src/packing.jl b/src/packing.jl
@@ -124,7 +124,7 @@ function generateNeighboringPoint(x_i::Vector, r_i::Real,
     #R = rand() * (r_i + r_j) * max_padding_factor + 2. * (r_i + r_j)
     R = r_i + r_j + padding
     T = rand() * 2. * pi
-    return [x_i[1] + R * sin(T), x_i[2] + R * cos(T)], r_j
+    return [x_i[1] + R * sin(T), x_i[2] + R * cos(T), x_i[3]], r_j
 end
 
 function generateRandomDirection()
@@ -132,7 +132,7 @@ function generateRandomDirection()
 end
 
 function getPositionDistancedFromPoint(T::Real, x_i::Vector, dist::Real)
-    return [x_i[1] + dist * sin(T), x_i[2] + dist * cos(T)]
+    return [x_i[1] + dist * sin(T), x_i[2] + dist * cos(T), x_i[3]]
 end
 
 export irregularPacking!
@@ -224,7 +224,7 @@ function irregularPacking!(simulation::Simulation;
     # and add it to the active list. If after `sample_limit` attempts no such
     # point is found, instead remove `i` from the active list.
     j = 0;
-    x_active = zeros(2); x_candidate = zeros(2);
+    x_active = zeros(3); x_candidate = zeros(3);
     r_active = 0.; r_candidate = 0.; T = 0.
     n = 0
     neighbor_found = false
@@ -456,9 +456,9 @@ function rasterMap(sim::Simulation, dx::Real)
         #i, j = Int.(floor.((grain.lin_pos - origo) ./ dx)) + [1,1]
 
         # Find corner indexes for box spanning the grain
-        min_i, min_j = Int.(floor.((grain.lin_pos - origo .-
+        min_i, min_j = Int.(floor.((grain.lin_pos[1:2] - origo .-
                                     grain.contact_radius) ./ dx)) .+ [1,1]
-        max_i, max_j = Int.(floor.((grain.lin_pos - origo .+
+        max_i, max_j = Int.(floor.((grain.lin_pos[1:2] - origo .+
                                     grain.contact_radius) ./ dx)) .+ [1,1]
 
         # For each cell in box, check if the grain is contained
@@ -466,7 +466,7 @@ function rasterMap(sim::Simulation, dx::Real)
             for j = min_j:max_j
                 cell_mid_point = dx .* Vector{Float64}([i,j]) .- 0.5 * dx
 
-                if (norm(cell_mid_point - grain.lin_pos) -
+                if (norm(cell_mid_point - grain.lin_pos[1:2]) -
                     grain.contact_radius < dist)
                     occupied[i,j] = true
                 end
diff --git a/src/simulation.jl b/src/simulation.jl
@@ -259,7 +259,7 @@ export zeroForcesAndTorques!
 function zeroForcesAndTorques!(simulation::Simulation)
     for grain in simulation.grains
         grain.force .= grain.external_body_force
-        grain.torque = 0.
+        grain.torque = zeros(3)
         grain.pressure = 0.
     end
     for wall in simulation.walls
diff --git a/src/temporal_integration.jl b/src/temporal_integration.jl
@@ -51,7 +51,7 @@ Use a two-term Taylor expansion for integrating the kinematic degrees of freedom
 for a `grain`.
 """
 function updateGrainKinematicsTwoTermTaylor!(grain::GrainCylindrical,
-                                               simulation::Simulation)
+                                             simulation::Simulation)
     grain.lin_acc = grain.force/grain.mass
     grain.ang_acc = grain.torque/grain.moment_of_inertia
 
@@ -62,9 +62,12 @@ function updateGrainKinematicsTwoTermTaylor!(grain::GrainCylindrical,
         if !grain.allow_y_acc
             grain.lin_acc[2] = 0.
         end
-        grain.ang_acc = 0.
+        if !grain.allow_z_acc
+            grain.lin_acc[3] = 0.
+        end
+        grain.ang_acc = zeros(3)
     elseif !grain.rotating
-        grain.ang_acc = 0.
+        grain.ang_acc = zeros(3)
     end
 
     grain.lin_pos +=
@@ -90,11 +93,11 @@ Use a three-term Taylor expansion for integrating the kinematic degrees of
 freedom for a `grain`.
 """
 function updateGrainKinematicsThreeTermTaylor!(grain::GrainCylindrical,
-                                                 simulation::Simulation)
+                                               simulation::Simulation)
 
     if simulation.time_iteration == 0
-        lin_acc_0 = zeros(2)
-        ang_acc_0 = 0.
+        lin_acc_0 = zeros(3)
+        ang_acc_0 = zeros(3)
     else
         lin_acc_0 = grain.lin_acc
         ang_acc_0 = grain.ang_acc
@@ -110,9 +113,12 @@ function updateGrainKinematicsThreeTermTaylor!(grain::GrainCylindrical,
         if !grain.allow_y_acc
             grain.lin_acc[2] = 0.
         end
-        grain.ang_acc = 0.
+        if !grain.allow_z_acc
+            grain.lin_acc[3] = 0.
+        end
+        grain.ang_acc = zeros(3)
     elseif !grain.rotating
-        grain.ang_acc = 0.
+        grain.ang_acc = zeros(3)
     end
 
     # Temporal gradient in acceleration using backwards differences
diff --git a/src/util.jl b/src/util.jl
@@ -42,3 +42,31 @@ function harmonicMean(a::Number, b::Number)::Number
         return 2. * a * b / (a + b)
     end
 end
+
+export vecTo3d
+"""
+    function vecTo3d(input, fill)
+
+Convert a scalar or 2d vector to 3d by filling the missing component with the
+value `fill`. The returned 3-component vector is a Vector (or 1d Array) of
+the same type as the input.
+
+# Arguments
+* `input`: a scalar or two-component vector.
+* `fill::Real`: value to use for third
+"""
+function vecTo3d(input::Any; fill::Real = 0.0)
+    if length(input) > 3
+        error("vecTo3d requires a scalar or input vector to of length 3 or " *
+              "less, but input is $input")
+    elseif length(input) == 3
+        return input
+    elseif length(input) == 2
+        return [input[1], input[2], typeof(input[1])(fill)]
+    elseif length(input) == 1
+        return [input[1], typeof(input[1])(fill), typeof(input[1])(fill)]
+    else
+        error("input not understood: $input")
+    end
+end
+
diff --git a/src/wall.jl b/src/wall.jl
@@ -16,9 +16,10 @@ The only required arguments are
 # Arguments
 * `simulation::Simulation`: the simulation object where the wall should be
     added to.
-* `normal::Vector{Float64}`: 2d vector denoting the normal to the wall [m].  The
+* `normal::Vector{Float64}`: 3d vector denoting the normal to the wall [m].  The
     wall will only interact in the opposite direction of this vector, so the
-    normal vector should point in the direction of the grains.
+    normal vector should point in the direction of the grains. If a 2d vector is
+    passed, the third (z) component is set to zero.
 * `pos::Float64`: position along axis parallel to the normal vector [m].
 * `bc::String="fixed"`: boundary condition, possible values are `"fixed"`
     (default), `"normal stress"`, or `"velocity"`.
@@ -50,13 +51,13 @@ wall-face normal of `[1., 0.]` (wall along *y* and normal to *x*), a position of
 `1.5` meter:
 
 ```julia
-Granular.addWallLinearFrictionless!(sim, [1., 0.], 1.5)
+Granular.addWallLinearFrictionless!(sim, [1., 0., 0.], 1.5)
 ```
 
 The following example creates a wall with a velocity of 0.5 m/s towards *-y*:
 
 ```julia
-Granular.addWallLinearFrictionless!(sim, [0., 1.], 1.5,
+Granular.addWallLinearFrictionless!(sim, [0., 1., 0.], 1.5,
                                     bc="velocity",
                                     vel=-0.5)
 ```
@@ -65,7 +66,7 @@ To create a wall parallel to the *y* axis pushing downwards with a constant
 normal stress of 100 kPa, starting at a position of y = 3.5 m:
 
 ```julia
-Granular.addWallLinearFrictionless!(sim, [0., 1.], 3.5,
+Granular.addWallLinearFrictionless!(sim, [0., 1., 0.], 3.5,
                                     bc="normal stress",
                                     normal_stress=100e3)
 ```
@@ -85,20 +86,19 @@ function addWallLinearFrictionless!(simulation::Simulation,
                                     verbose::Bool=true)
 
     # Check input values
-    if length(normal) != 2
-        error("Wall normal must be a two-element array (normal = " *
-              "$normal)")
+    if length(normal) != 3
+        normal = vecTo3d(normal)
     end
 
     if bc != "fixed" && bc != "velocity" && bc != "normal stress"
         error("Wall BC must be 'fixed', 'velocity', or 'normal stress'.")
     end
 
-    if !(normal ≈ [1., 0.]) && !(normal ≈ [0., 1.])
+    if !(normal ≈ [1., 0., 0.]) && !(normal ≈ [0., 1., 0.])
         error("Currently only walls with normals orthogonal to the " *
               "coordinate system are allowed, i.e. normals parallel to the " *
               "x or y axes.  Accepted values for `normal` " *
-              "are [1., 0.] and [0., 1.].  The passed normal was $normal")
+              "are [1., 0., 0.] and [0., 1., 0.]. The passed normal was $normal")
     end
 
     # if not set, set wall mass to equal the mass of all grains.
@@ -171,10 +171,10 @@ Returns the surface area of the wall given the grid size and its index.
 """
 function getWallSurfaceArea(sim::Simulation, wall_index::Integer)
 
-    if sim.walls[wall_index].normal ≈ [1., 0.]
+    if sim.walls[wall_index].normal ≈ [1., 0., 0.]
         return (sim.ocean.yq[end,end] - sim.ocean.yq[1,1]) *
             sim.walls[wall_index].thickness
-    elseif sim.walls[wall_index].normal ≈ [0., 1.]
+    elseif sim.walls[wall_index].normal ≈ [0., 1., 0.]
         return (sim.ocean.xq[end,end] - sim.ocean.xq[1,1]) *
             sim.walls[wall_index].thickness
     else
@@ -185,9 +185,9 @@ end
 function getWallSurfaceArea(sim::Simulation, normal::Vector{Float64},
                             thickness::Float64)
 
-    if normal ≈ [1., 0.]
+    if length(normal) == 3 && normal ≈ [1., 0., 0.]
         return (sim.ocean.yq[end,end] - sim.ocean.yq[1,1]) * thickness
-    elseif normal ≈ [0., 1.]
+    elseif length(normal) == 3 && normal ≈ [0., 1., 0.]
         return (sim.ocean.xq[end,end] - sim.ocean.xq[1,1]) * thickness
     else
         error("Wall normal not understood")
diff --git a/test/atmosphere.jl b/test/atmosphere.jl
@@ -3,8 +3,6 @@
 # Check if atmosphere-specific functions and grid operations are functioning 
 # correctly
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Testing regular grid generation"
 sim = Granular.createSimulation()
 sim.atmosphere = Granular.createRegularAtmosphereGrid([6, 6, 6], [1., 1., 1.])
@@ -54,15 +52,15 @@ E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 
 Compat.@info "Testing vortex interaction (static atmosphere)"
 sim = deepcopy(sim_init)
-sim.grains[1].ang_vel = 0.1
+sim.grains[1].ang_vel[3] = 0.1
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_rot_init > E_kin_rot_final  # energy lost to atmosphere
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
 Compat.@info "Testing vortex interaction (static ice floe)"
@@ -77,8 +75,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to atm vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -93,8 +91,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel < 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos < 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] < 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] < 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to atm vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -109,8 +107,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel < 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos < 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] < 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] < 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to atm vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -125,8 +123,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to atm vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
diff --git a/test/cohesion.jl b/test/cohesion.jl
@@ -5,8 +5,6 @@ import Granular
 # Check for conservation of kinetic energy (=momentum) during a normal collision 
 # between two ice cylindrical grains 
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 sim_init = Granular.createSimulation()
@@ -21,6 +19,7 @@ sim = deepcopy(sim_init)
 Granular.setTotalTime!(sim, 10.)
 sim.time_step = 1.
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].contact_age[1] ≈ sim.time
 
 Compat.@info "# Check if bonds add tensile strength"
@@ -31,12 +30,13 @@ sim.grains[1].lin_vel[1] = 0.1
 Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 10.)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] > 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] > 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel ≈ 0.
-@test sim.grains[2].ang_vel ≈ 0.
+@test sim.grains[1].ang_vel ≈ zeros(3)
+@test sim.grains[2].ang_vel ≈ zeros(3)
 
 Compat.@info "# Add shear strength and test bending resistance (one grain rotating)"
 sim = Granular.createSimulation(id="cohesion")
@@ -44,7 +44,7 @@ Granular.addGrainCylindrical!(sim, [0., 0.], 10.1, 1., tensile_strength=500e3,
     shear_strength=500e3)
 Granular.addGrainCylindrical!(sim, [20., 0.], 10., 1., tensile_strength=500e3,
     shear_strength=500e3)
-sim.grains[1].ang_vel = 0.1
+sim.grains[1].ang_vel[3] = 0.1
 Granular.findContacts!(sim) # make sure contact is registered
 sim.grains[1].contact_radius=10.0 # decrease radius so there isn't compression
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
@@ -53,12 +53,13 @@ Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 5.)
 #Granular.setOutputFileInterval!(sim, 0.1)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] ≈ 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] ≈ 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel != 0.
-@test sim.grains[2].ang_vel != 0.
+@test sim.grains[1].ang_vel[3] != 0.
+@test sim.grains[2].ang_vel[3] != 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_therm_final = Granular.totalGrainThermalEnergy(sim)
@@ -71,7 +72,7 @@ Granular.addGrainCylindrical!(sim, [0., 0.], 10.1, 1., tensile_strength=500e3,
     shear_strength=500e3)
 Granular.addGrainCylindrical!(sim, [20., 0.], 10., 1., tensile_strength=500e3,
     shear_strength=500e3)
-sim.grains[2].ang_vel = 0.1
+sim.grains[2].ang_vel[3] = 0.1
 Granular.findContacts!(sim) # make sure contact is registered
 sim.grains[1].contact_radius=10.0 # decrease radius so there isn't compression
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
@@ -80,12 +81,13 @@ Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 5.)
 #Granular.setOutputFileInterval!(sim, 0.1)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] ≈ 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] ≈ 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel != 0.
-@test sim.grains[2].ang_vel != 0.
+@test sim.grains[1].ang_vel[3] != 0.
+@test sim.grains[2].ang_vel[3] != 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_therm_final = Granular.totalGrainThermalEnergy(sim)
@@ -98,8 +100,8 @@ Granular.addGrainCylindrical!(sim, [0., 0.], 10.0000001, 1., tensile_strength=50
     shear_strength=500e3)
 Granular.addGrainCylindrical!(sim, [20., 0.], 10., 1., tensile_strength=500e3,
     shear_strength=500e3)
-sim.grains[1].ang_vel = 0.1
-sim.grains[2].ang_vel = -0.1
+sim.grains[1].ang_vel[3] = 0.1
+sim.grains[2].ang_vel[3] = -0.1
 Granular.findContacts!(sim) # make sure contact is registered
 sim.grains[1].contact_radius=10.0 # decrease radius so there isn't compression
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
@@ -108,12 +110,13 @@ Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 5.)
 #Granular.setOutputFileInterval!(sim, 0.1)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] ≈ 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] ≈ 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel != 0.
-@test sim.grains[2].ang_vel != 0.
+@test sim.grains[1].ang_vel[3] != 0.
+@test sim.grains[2].ang_vel[3] != 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_therm_final = Granular.totalGrainThermalEnergy(sim)
@@ -126,8 +129,8 @@ Granular.addGrainCylindrical!(sim, [0., 0.], 10.0000001, 1., tensile_strength=50
     shear_strength=500e3)
 Granular.addGrainCylindrical!(sim, [20., 0.], 10., 1., tensile_strength=500e3,
     shear_strength=500e3)
-sim.grains[1].ang_vel = 100
-sim.grains[2].ang_vel = -100
+sim.grains[1].ang_vel[3] = 100
+sim.grains[2].ang_vel[3] = -100
 Granular.findContacts!(sim) # make sure contact is registered
 sim.grains[1].contact_radius=10.0 # decrease radius so there isn't compression
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
@@ -136,12 +139,13 @@ Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 5.)
 #Granular.setOutputFileInterval!(sim, 0.1)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] ≈ 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] ≈ 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel != 0.
-@test sim.grains[2].ang_vel != 0.
+@test sim.grains[1].ang_vel[3] != 0.
+@test sim.grains[2].ang_vel[3] != 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_therm_final = Granular.totalGrainThermalEnergy(sim)
@@ -155,8 +159,8 @@ Granular.addGrainCylindrical!(sim, [0., 0.], 10.1, 1., tensile_strength=500e3,
     shear_strength=50e3)
 Granular.addGrainCylindrical!(sim, [20., 0.], 10., 1., tensile_strength=500e3,
     shear_strength=50e3)
-sim.grains[1].ang_vel = 100
-sim.grains[2].ang_vel = 0.0
+sim.grains[1].ang_vel[3] = 100
+sim.grains[2].ang_vel[3] = 0.0
 Granular.findContacts!(sim) # make sure contact is registered
 sim.grains[1].contact_radius=10.0 # decrease radius so there isn't compression
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
@@ -165,12 +169,13 @@ Granular.setTimeStep!(sim)
 Granular.setTotalTime!(sim, 5.)
 #Granular.setOutputFileInterval!(sim, 0.1)
 Granular.run!(sim, verbose=verbose)
+Granular.removeSimulationFiles(sim)
 @test sim.grains[1].lin_vel[1] ≈ 0.
 @test sim.grains[1].lin_vel[2] ≈ 0.
 @test sim.grains[2].lin_vel[1] ≈ 0.
 @test sim.grains[2].lin_vel[2] ≈ 0.
-@test sim.grains[1].ang_vel != 0.
-@test sim.grains[2].ang_vel != 0.
+@test sim.grains[1].ang_vel[3] != 0.
+@test sim.grains[2].ang_vel[3] != 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_therm_final = Granular.totalGrainThermalEnergy(sim)
diff --git a/test/collision-2floes-normal.jl b/test/collision-2floes-normal.jl
@@ -3,8 +3,6 @@
 # Check for conservation of kinetic energy (=momentum) during a normal collision 
 # between two ice cylindrical grains 
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 Compat.@info "# One ice floe fixed"
diff --git a/test/collision-2floes-oblique.jl b/test/collision-2floes-oblique.jl
@@ -3,8 +3,6 @@
 # Check for conservation of kinetic energy (=momentum) during a normal collision 
 # between two ice cylindrical grains 
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 Compat.@info "## Contact-normal elasticity only"
@@ -36,9 +34,6 @@ Granular.run!(sim, temporal_integration_method="Two-term Taylor", verbose=verbos
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 E_thermal_final = Granular.totalGrainThermalEnergy(sim)
-println(E_kin_lin_init)
-println(E_kin_lin_final)
-println(E_thermal_final)
 @test E_kin_lin_init ≈ E_kin_lin_final+E_thermal_final atol=E_kin_lin_init*tol
 @test E_kin_rot_init ≈ E_kin_rot_final
 
@@ -136,19 +131,25 @@ sim_init.grains[2].contact_dynamic_friction = 1e2  # no Coulomb sliding
 sim_init.grains[2].fixed = true
 sim = deepcopy(sim_init)
 
+
 Compat.@info "Testing kinetic energy conservation with Two-term Taylor scheme"
 Granular.setTimeStep!(sim, epsilon=0.07)
 tol = 0.1
 Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
+Granular.setOutputFileInterval!(sim, 1.0)
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos ≈ 0.
-@test sim.grains[2].ang_vel ≈ 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] ≈ 0.
+@test sim.grains[2].ang_vel[3] ≈ 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
+println(E_kin_lin_init)
+println(E_kin_lin_final)
+println(E_kin_rot_init)
+println(E_kin_rot_final)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol
 
 Compat.@info "mu_d = 0."
@@ -162,9 +163,9 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos ≈ 0.
-@test sim.grains[1].ang_vel ≈ 0.
-@test sim.grains[2].ang_pos ≈ 0.
+@test sim.grains[1].ang_pos[3] ≈ 0.
+@test sim.grains[1].ang_vel[3] ≈ 0.
+@test sim.grains[2].ang_pos[3] ≈ 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init ≈ E_kin_lin_final atol=E_kin_lin_init*tol
@@ -178,10 +179,10 @@ Compat.@info "Relative tolerance: $(tol*100.)%"
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos ≈ 0.
-@test sim.grains[2].ang_vel ≈ 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] ≈ 0.
+@test sim.grains[2].ang_vel[3] ≈ 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol
@@ -195,10 +196,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos ≈ 0.
-@test sim.grains[2].ang_vel ≈ 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] ≈ 0.
+@test sim.grains[2].ang_vel[3] ≈ 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol
@@ -228,10 +229,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -257,10 +258,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -291,10 +292,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos > 0.
-@test sim.grains[1].ang_vel > 0.
-@test sim.grains[2].ang_pos > 0.
-@test sim.grains[2].ang_vel > 0.
+@test sim.grains[1].ang_pos[3] > 0.
+@test sim.grains[1].ang_vel[3] > 0.
+@test sim.grains[2].ang_pos[3] > 0.
+@test sim.grains[2].ang_vel[3] > 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -320,10 +321,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos > 0.
-@test sim.grains[1].ang_vel > 0.
-@test sim.grains[2].ang_pos > 0.
-@test sim.grains[2].ang_vel > 0.
+@test sim.grains[1].ang_pos[3] > 0.
+@test sim.grains[1].ang_vel[3] > 0.
+@test sim.grains[2].ang_pos[3] > 0.
+@test sim.grains[2].ang_vel[3] > 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -352,10 +353,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -381,10 +382,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -421,10 +422,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Two-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -450,10 +451,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init ≈ E_kin_lin_final+E_kin_rot_final atol=E_kin_lin_init*tol 
@@ -503,10 +504,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init > E_kin_lin_final+E_kin_rot_final
@@ -556,10 +557,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init > E_kin_lin_final+E_kin_rot_final
@@ -609,10 +610,10 @@ Compat.@info "Relative tolerance: $(tol*100.)% with time step: $(sim.time_step)"
 Granular.run!(sim, temporal_integration_method="Three-term Taylor",
             verbose=verbose)
 
-@test sim.grains[1].ang_pos < 0.
-@test sim.grains[1].ang_vel < 0.
-@test sim.grains[2].ang_pos < 0.
-@test sim.grains[2].ang_vel < 0.
+@test sim.grains[1].ang_pos[3] < 0.
+@test sim.grains[1].ang_vel[3] < 0.
+@test sim.grains[2].ang_pos[3] < 0.
+@test sim.grains[2].ang_vel[3] < 0.
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_lin_init+E_kin_rot_init > E_kin_lin_final+E_kin_rot_final
diff --git a/test/collision-5floes-normal.jl b/test/collision-5floes-normal.jl
@@ -4,8 +4,6 @@ using Compat.LinearAlgebra
 # Check for conservation of kinetic energy (=momentum) during a normal collision 
 # between two ice cylindrical grains 
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 Compat.@info "# One ice floe fixed"
diff --git a/test/compressive_failure.jl b/test/compressive_failure.jl
@@ -0,0 +1,184 @@
+#!/usr/bin/env julia
+using Compat.Test
+import Granular
+
+verbose = false
+debug = false
+if debug
+    import PyPlot
+end
+
+function plot_interaction(sim::Granular.Simulation, output::String)
+    time = Float64[]
+    force_n_1 = Float64[]
+    force_n_2 = Float64[]
+    force_t_1 = Float64[]
+    force_t_2 = Float64[]
+    torque_1 = Float64[]
+    torque_2 = Float64[]
+    compressive_failure = Bool[]
+    while sim.time < sim.time_total
+        Granular.run!(sim, verbose=verbose, single_step=true)
+        append!(time, sim.time)
+        append!(compressive_failure, sim.grains[1].compressive_failure[1])
+        append!(force_n_1, sim.grains[1].force[1])
+        append!(force_n_2, sim.grains[2].force[1])
+        append!(force_t_1, sim.grains[1].force[2])
+        append!(force_t_2, sim.grains[2].force[2])
+        append!(torque_1, sim.grains[1].torque[3])
+        append!(torque_2, sim.grains[2].torque[3])
+    end
+    PyPlot.clf()
+    PyPlot.subplot(3,1,1)
+    PyPlot.plot(time, force_n_1, "-b", label="1")
+    PyPlot.plot(time, force_n_2, "--y", label="2")
+    PyPlot.legend(loc="upper right")
+    PyPlot.ylabel("\$f_x\$ [N]")
+    PyPlot.subplot(3,1,2)
+    PyPlot.plot(time, force_t_1, "-b", label="1")
+    PyPlot.plot(time, force_t_2, "--y", label="2")
+    PyPlot.legend(loc="upper right")
+    PyPlot.ylabel("\$f_y\$ [N]")
+    PyPlot.subplot(3,1,3)
+    PyPlot.plot(time, torque_1, "-b", label="1")
+    PyPlot.plot(time, torque_2, "--y", label="2")
+    PyPlot.legend(loc="upper right")
+    PyPlot.ylabel("Torque [Nm]")
+    PyPlot.xlabel("Time [s]")
+    PyPlot.tight_layout()
+    PyPlot.savefig(output)
+end
+
+Compat.@info "Testing compressive failure: uniaxial compression"
+sim = Granular.createSimulation("compressive_failure_uniaxial")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              lin_vel=[1., 0.], fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [2.,0.], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              fixed=true, verbose=verbose)
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+
+if debug
+    Granular.removeSimulationFiles(sim)
+    Granular.setOutputFileInterval!(sim, 0.01)
+    plot_interaction(sim, sim.id * ".pdf")
+else
+    Granular.run!(sim, verbose=verbose)
+end
+
+@test sim.grains[1].compressive_failure[1] == true
+@test count(x->x==true, sim.grains[1].compressive_failure) == 1
+@test sim.grains[1].force[1] < 0.0
+@test sim.grains[1].force[2] ≈ 0.0
+@test sim.grains[2].force[1] > 0.0
+@test sim.grains[2].force[2] ≈ 0.0
+@test sim.grains[1].torque ≈ zeros(3)
+@test sim.grains[2].torque ≈ zeros(3)
+
+Compat.@info "Testing compressive failure: shear"
+sim = Granular.createSimulation("compressive_failure_shear")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              lin_vel=[0., 1.], fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [1.5,1.5], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              fixed=true, verbose=verbose)
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+
+if debug
+    Granular.removeSimulationFiles(sim)
+    Granular.setOutputFileInterval!(sim, 0.01)
+    plot_interaction(sim, sim.id * ".pdf")
+else
+    Granular.run!(sim, verbose=verbose)
+end
+
+@test sim.grains[1].compressive_failure[1] == true
+@test count(x->x==true, sim.grains[1].compressive_failure) == 1
+@test sim.grains[1].force[1] > 0.0
+@test sim.grains[1].force[2] < 0.0
+@test abs(sim.grains[1].force[1]) < abs(sim.grains[1].force[2])
+@test sim.grains[2].force[1] < 0.0
+@test sim.grains[2].force[2] > 0.0
+@test abs(sim.grains[2].force[1]) < abs(sim.grains[2].force[2])
+@test sim.grains[1].torque[1:2] ≈ zeros(2)
+@test sim.grains[1].torque[3] < 0.0
+@test sim.grains[2].torque[1:2] ≈ zeros(2)
+@test sim.grains[2].torque[3] < 0.0
+
+Compat.@info "Testing robustness of overlap calculations"
+sim = Granular.createSimulation("overlap")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              lin_vel=[0., 1.], fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [2.,0.], 1., 0.5,
+                              fracture_toughness=1285e3,
+                              fixed=true, verbose=verbose)
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+Granular.run!(sim, single_step=true, verbose=verbose)
+@test sim.grains[1].compressive_failure[1] == false
+@test sim.grains[1].contact_area[1] == 0.0
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+
+sim = Granular.createSimulation("overlap")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [0.0+1e-9,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+Granular.run!(sim, single_step=true, verbose=verbose)
+@test sim.grains[1].compressive_failure[1] == true
+@test sim.grains[1].contact_area[1] ≈ π*1.0^2
+
+sim = Granular.createSimulation("overlap")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [0.1,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+Granular.run!(sim, single_step=true, verbose=verbose)
+@test sim.grains[1].compressive_failure[1] == true
+@test sim.grains[1].contact_area[1] < π*1.0^2
+@test sim.grains[1].contact_area[1] > 0.
+
+sim = Granular.createSimulation("overlap")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [0.+1e-9,0.], 0.1, 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+Granular.run!(sim, single_step=true, verbose=verbose)
+@test sim.grains[1].position_vector[1] ≈ [-1e-9, 0., 0.]
+@test sim.grains[1].compressive_failure[1] == true
+@test sim.grains[1].contact_area[1] ≈ π*0.1^2
+
+sim = Granular.createSimulation("overlap")
+Granular.addGrainCylindrical!(sim, [0.,0.], 1., 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+Granular.addGrainCylindrical!(sim, [0.3,0.4], 0.1, 0.5,
+                              fracture_toughness=1.,
+                              fixed=true, verbose=verbose)
+@test count(x->x==true, sim.grains[1].compressive_failure) == 0
+Granular.setTimeStep!(sim, verbose=verbose)
+Granular.setTotalTime!(sim, 1.0)
+Granular.run!(sim, single_step=true, verbose=verbose)
+@test sim.grains[1].compressive_failure[1] == true
+@test sim.grains[1].contact_area[1] ≈ π*0.1^2
diff --git a/test/contact-search-and-geometry.jl b/test/contact-search-and-geometry.jl
@@ -4,8 +4,6 @@ import Granular
 
 # Check the contact search and geometry of a two-particle interaction
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Testing interGrainPositionVector(...) and findOverlap(...)"
 sim = Granular.createSimulation("test")
 sim = Granular.createSimulation(id="test")
@@ -15,7 +13,7 @@ Granular.addGrainCylindrical!(sim, [18.01, 0.01], 10., 1., verbose=false)
 position_ij = Granular.interGrainPositionVector(sim, 1, 2)
 overlap_ij = Granular.findOverlap(sim, 1, 2, position_ij)
 
-@test [-18., 0.] ≈ position_ij
+@test [-18., 0., 0.] ≈ position_ij
 @test -2. ≈ overlap_ij
 
 
@@ -31,16 +29,16 @@ Granular.findContacts!(sim)
 sim.grains[1].enabled = false
 # The contact should be registered in ice floe 1, but not ice floe 2
 @test 2 == sim.grains[1].contacts[1]
-@test [-18., 0.] ≈ sim.grains[1].position_vector[1]
+@test [-18., 0., 0.] ≈ sim.grains[1].position_vector[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -50,16 +48,16 @@ sim = deepcopy(sim_copy)
 Granular.findContacts!(sim)
 
 @test 2 == sim.grains[1].contacts[1]
-@test [-18., 0.] ≈ sim.grains[1].position_vector[1]
+@test [-18., 0., 0.] ≈ sim.grains[1].position_vector[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -72,13 +70,13 @@ sim.grains[2].enabled = false
 Granular.findContacts!(sim)
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 0 == sim.grains[1].n_contacts
 @test 0 == sim.grains[2].n_contacts
@@ -89,13 +87,13 @@ Granular.disableGrain!(sim, 1)
 Granular.findContacts!(sim)
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 0 == sim.grains[1].n_contacts
 @test 0 == sim.grains[2].n_contacts
@@ -107,13 +105,13 @@ Granular.disableGrain!(sim, 2)
 Granular.findContacts!(sim)
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 0 == sim.grains[1].n_contacts
 @test 0 == sim.grains[2].n_contacts
@@ -125,16 +123,16 @@ Granular.interact!(sim)
 Granular.findContacts!(sim)
 
 @test 2 == sim.grains[1].contacts[1]
-@test [-18., 0.] ≈ sim.grains[1].position_vector[1]
+@test [-18., 0., 0.] ≈ sim.grains[1].position_vector[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -149,13 +147,13 @@ Granular.findContactsInGrid!(sim, sim.ocean)
 @test 2 == sim.grains[1].contacts[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -170,13 +168,13 @@ Granular.findContactsInGrid!(sim, sim.ocean)
 @test 2 == sim.grains[1].contacts[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -191,13 +189,13 @@ Granular.findContactsInGrid!(sim, sim.ocean)
 
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 0 == sim.grains[1].n_contacts
 @test 0 == sim.grains[2].n_contacts
@@ -211,13 +209,13 @@ Granular.findContacts!(sim)
 @test 2 == sim.grains[1].contacts[1]
 for ic=2:sim.Nc_max
     @test 0 == sim.grains[1].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[1].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[1].contact_parallel_displacement[ic]
 end
 for ic=1:sim.Nc_max
     @test 0 == sim.grains[2].contacts[ic]
-    @test [0., 0.] ≈ sim.grains[2].position_vector[ic]
-    @test [0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].position_vector[ic]
+    @test [0., 0., 0.] ≈ sim.grains[2].contact_parallel_displacement[ic]
 end
 @test 1 == sim.grains[1].n_contacts
 @test 1 == sim.grains[2].n_contacts
@@ -302,5 +300,3 @@ for j=2:(ny-1)
         @test sim.grains[idx].n_contacts == 4
     end
 end
-
-
diff --git a/test/grain.jl b/test/grain.jl
@@ -2,22 +2,20 @@
 
 # Check the basic icefloe functionality
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Writing simple simulation to VTK file"
 sim = Granular.createSimulation(id="test")
 Granular.addGrainCylindrical!(sim, [ 0., 0.], 10., 1., verbose=false)
 Granular.printGrainInfo(sim.grains[1])
 
 Compat.@info "Testing grain value checks "
-@test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1, .1],
+@test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1, .1, .1],
                                                           10., 1.)
 @test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1],
                                                           10., 1., 
-                                                          lin_vel=[.2,.2,.2])
+                                                          lin_vel=[.2,.2,.2,.2])
 @test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1],
                                                           10., 1., 
-                                                          lin_acc=[.2,.2,.2])
+                                                          lin_acc=[.2,.2,.2,.2])
 @test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1],
                                                           0., 1.)
 @test_throws ErrorException Granular.addGrainCylindrical!(sim, [.1, .1],
@@ -82,18 +80,18 @@ end
 Compat.@info "Testing external body force routines"
 sim = Granular.createSimulation(id="test")
 Granular.addGrainCylindrical!(sim, [ 0., 0.], 10., 1., verbose=false)
-Granular.setBodyForce!(sim.grains[1], [1., 2.])
-@test sim.grains[1].external_body_force ≈ [1., 2.]
-Granular.addBodyForce!(sim.grains[1], [1., 2.])
-@test sim.grains[1].external_body_force ≈ [2., 4.]
+Granular.setBodyForce!(sim.grains[1], [1., 2., 0.])
+@test sim.grains[1].external_body_force ≈ [1., 2., 0.]
+Granular.addBodyForce!(sim.grains[1], [1., 2., 0.])
+@test sim.grains[1].external_body_force ≈ [2., 4., 0.]
 
 Compat.@info "Testing zeroKinematics!()"
-sim.grains[1].force .= ones(2)
-sim.grains[1].lin_acc .= ones(2)
-sim.grains[1].lin_vel .= ones(2)
-sim.grains[1].torque = 1.
-sim.grains[1].ang_acc = 1.
-sim.grains[1].ang_vel = 1.
+sim.grains[1].force .= ones(3)
+sim.grains[1].lin_acc .= ones(3)
+sim.grains[1].lin_vel .= ones(3)
+sim.grains[1].torque .= ones(3)
+sim.grains[1].ang_acc .= ones(3)
+sim.grains[1].ang_vel .= ones(3)
 Granular.zeroKinematics!(sim)
 @test Granular.totalGrainKineticTranslationalEnergy(sim) ≈ 0.
 @test Granular.totalGrainKineticRotationalEnergy(sim) ≈ 0.
diff --git a/test/grid-boundaries.jl b/test/grid-boundaries.jl
@@ -1,8 +1,6 @@
 #!/usr/bin/env julia
 import Compat
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 Compat.@info "## Inactive/Periodic BCs"
@@ -185,7 +183,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "inactive", verbose=false)
 Granular.addGrainCylindrical!(sim, [0.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.1, 0.5] ≈ sim.grains[1].lin_pos
+@test [0.1, 0.5, 0.] ≈ sim.grains[1].lin_pos
 
 # do not readjust inside grid, periodic boundaries
 sim = Granular.createSimulation()
@@ -193,7 +191,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "periodic", verbose=false)
 Granular.addGrainCylindrical!(sim, [0.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.1, 0.5] ≈ sim.grains[1].lin_pos
+@test [0.1, 0.5, 0.] ≈ sim.grains[1].lin_pos
 
 # do not readjust outside grid, inactive boundaries
 sim = Granular.createSimulation()
@@ -201,7 +199,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "inactive", verbose=false)
 Granular.addGrainCylindrical!(sim, [-0.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [-0.1, 0.5] ≈ sim.grains[1].lin_pos
+@test [-0.1, 0.5, 0.] ≈ sim.grains[1].lin_pos
 
 # readjust outside grid, periodic boundaries, -x
 sim = Granular.createSimulation()
@@ -209,7 +207,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "periodic", verbose=false)
 Granular.addGrainCylindrical!(sim, [-0.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.9, 0.5] ≈ sim.grains[1].lin_pos
+@test [0.9, 0.5, 0.] ≈ sim.grains[1].lin_pos
 
 # readjust outside grid, periodic boundaries, +x
 sim = Granular.createSimulation()
@@ -217,7 +215,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "periodic", verbose=false)
 Granular.addGrainCylindrical!(sim, [1.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.1, 0.5] ≈ sim.grains[1].lin_pos
+@test [0.1, 0.5, 0.] ≈ sim.grains[1].lin_pos
 
 # readjust outside grid, periodic boundaries, -y
 sim = Granular.createSimulation()
@@ -225,7 +223,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "periodic", verbose=false)
 Granular.addGrainCylindrical!(sim, [0.3, -0.1], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.3, 0.9] ≈ sim.grains[1].lin_pos
+@test [0.3, 0.9, 0.] ≈ sim.grains[1].lin_pos
 
 # readjust outside grid, periodic boundaries, +y
 sim = Granular.createSimulation()
@@ -233,7 +231,7 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "periodic", verbose=false)
 Granular.addGrainCylindrical!(sim, [0.3, 1.1], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.3, 0.1] ≈ sim.grains[1].lin_pos
+@test [0.3, 0.1, 0.] ≈ sim.grains[1].lin_pos
 
 # throw error if atmosphere and ocean BCs differ
 sim = Granular.createSimulation()
@@ -253,4 +251,4 @@ sim.ocean = Granular.createRegularOceanGrid([5, 5, 2], [1., 1., 1.])
 Granular.setGridBoundaryConditions!(sim.ocean, "inactive", verbose=false)
 Granular.addGrainCylindrical!(sim, [0.1, 0.5], 0.11, 0.1, verbose=false)
 Granular.moveGrainsAcrossPeriodicBoundaries!(sim)
-@test [0.1, 0.5] ≈ sim.grains[1].lin_pos
+@test [0.1, 0.5, 0.] ≈ sim.grains[1].lin_pos
diff --git a/test/grid.jl b/test/grid.jl
@@ -5,8 +5,6 @@ import Granular
 # Check the grid interpolation and sorting functions
 verbose = false
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 if Granular.hasNetCDF
     ocean = Granular.readOceanNetCDF("Baltic/00010101.ocean_month.nc",
                                    "Baltic/ocean_hgrid.nc")
diff --git a/test/jld.jl b/test/jld.jl
@@ -1,8 +1,6 @@
 #!/usr/bin/env julia
 import Compat
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Determining if JLD is installed"
 if Granular.hasJLD
     Compat.@info "JLD found, proceeding with JLD-specific tests"
diff --git a/test/netcdf.jl b/test/netcdf.jl
@@ -2,8 +2,6 @@
 
 # Check if NetCDF files are read correctly from the disk.
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 @test_throws ErrorException Granular.readOceanStateNetCDF("nonexistentfile")
 @test_throws ErrorException Granular.readOceanGridNetCDF("nonexistentfile")
 
diff --git a/test/ocean.jl b/test/ocean.jl
@@ -3,8 +3,6 @@
 # Check if ocean-specific functions and grid operations are functioning 
 # correctly
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Testing regular grid generation"
 sim = Granular.createSimulation()
 sim.ocean = Granular.createRegularOceanGrid([6, 6, 6], [1., 1., 1.])
@@ -58,15 +56,15 @@ E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 
 Compat.@info "Testing vortex interaction (static ocean)"
 sim = deepcopy(sim_init)
-sim.grains[1].ang_vel = 0.1
+sim.grains[1].ang_vel[3] = 0.1
 E_kin_lin_init = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
 @test E_kin_rot_init > E_kin_rot_final  # energy lost to ocean
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
 Compat.@info "Testing vortex interaction (static ice floe)"
@@ -81,8 +79,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to ocean vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -97,8 +95,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel < 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos < 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] < 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] < 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to ocean vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -113,8 +111,8 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel < 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos < 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] < 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] < 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to ocean vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
 
@@ -129,7 +127,7 @@ E_kin_rot_init = Granular.totalGrainKineticRotationalEnergy(sim)
 Granular.run!(sim, verbose=false)
 E_kin_lin_final = Granular.totalGrainKineticTranslationalEnergy(sim)
 E_kin_rot_final = Granular.totalGrainKineticRotationalEnergy(sim)
-@test sim.grains[1].ang_vel > 0.     # check angular velocity orientation
-@test sim.grains[1].ang_pos > 0.     # check angular position orientation
+@test sim.grains[1].ang_vel[3] > 0.     # check angular velocity orientation
+@test sim.grains[1].ang_pos[3] > 0.     # check angular position orientation
 @test E_kin_rot_init < E_kin_rot_final  # rotation after due to ocean vortex
 @test E_kin_lin_init ≈ E_kin_lin_final  # no linear velocity gained
diff --git a/test/packing.jl b/test/packing.jl
@@ -6,8 +6,6 @@ verbose = false
 plot = false
 plot_packings=false
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Testing regular packing generation (power law GSD)"
 sim = Granular.createSimulation()
 Granular.regularPacking!(sim, [2, 2], 1., 1., size_distribution="powerlaw")
diff --git a/test/profiling.jl b/test/profiling.jl
@@ -9,8 +9,6 @@ import Plots
 import Granular
 import CurveFit
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 verbose=false
 
 Compat.@info "Testing performance with many interacting grains"
diff --git a/test/runtests.jl b/test/runtests.jl
@@ -3,22 +3,28 @@ using Compat.Test
 using Compat.LinearAlgebra
 import Granular
 
-include("collision-2floes-normal.jl")
-include("collision-5floes-normal.jl")
-include("collision-2floes-oblique.jl")
-include("grid.jl")
-include("contact-search-and-geometry.jl")
-include("grid-boundaries.jl")
-include("ocean.jl")
-include("atmosphere.jl")
-include("wall.jl")
-include("grain.jl")
-include("packing.jl")
-include("util.jl")
-include("temporal.jl")
-include("cohesion.jl")
+function run_test(filename::String)
+    Compat.printstyled("Info: #### $filename ####\n", color=:green)
+    include(filename)
+end
+
+run_test("compressive_failure.jl")
+run_test("cohesion.jl")
+run_test("grain.jl")
+run_test("vtk.jl")
+run_test("collision-2floes-normal.jl")
+run_test("collision-5floes-normal.jl")
+run_test("collision-2floes-oblique.jl")
+run_test("grid.jl")
+run_test("contact-search-and-geometry.jl")
+run_test("grid-boundaries.jl")
+run_test("ocean.jl")
+run_test("atmosphere.jl")
+run_test("wall.jl")
+run_test("packing.jl")
+run_test("util.jl")
+run_test("temporal.jl")
 if Granular.hasNetCDF
-    include("netcdf.jl")
+    run_test("netcdf.jl")
 end
-include("vtk.jl")
-include("jld.jl")
+run_test("jld.jl")
diff --git a/test/util.jl b/test/util.jl
@@ -1,8 +1,8 @@
 #!/usr/bin/env julia
+import Granular
 import Compat
 using Compat.Random
-
-Compat.@info "#### $(basename(@__FILE__)) ####"
+using Compat.Test
 
 Compat.@info "Testing power-law RNG"
 
@@ -26,3 +26,12 @@ for i=1:10^5
     @test 0. <= minimum(Granular.randpower(5, 1., 0., 1.))
     @test 1. >= minimum(Granular.randpower(5, 1., 0., 1.))
 end
+
+@test [1,2,0] == Granular.vecTo3d([1,2])
+@test [1,2,3] == Granular.vecTo3d([1,2], fill=3)
+@test [1,3,3] == Granular.vecTo3d([1], fill=3)
+@test [1,3,3] == Granular.vecTo3d(1, fill=3)
+@test [1.,2.,3.] == Granular.vecTo3d([1.,2.], fill=3.)
+@test [1.,3.,3.] == Granular.vecTo3d(1., fill=3.)
+@test [1.,0.,0.] == Granular.vecTo3d(1.)
+@test [1.,0.,0.] == Granular.vecTo3d([1.])
diff --git a/test/vtk.jl b/test/vtk.jl
@@ -3,8 +3,6 @@ import Compat
 
 # Check the contact search and geometry of a two-particle interaction
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "Writing simple simulation to VTK file"
 sim = Granular.createSimulation(id="test")
 Granular.addGrainCylindrical!(sim, [ 0., 0.], 10., 1., verbose=false)
@@ -29,12 +27,12 @@ end
 
 grainpath = "test/test.grains.1.vtu"
 grainchecksum = 
-"eff130f14975dd5da06186c5b61c0c1a9d679f2188d03019c8144898ddc902b6  " *
+"b1748cb82e8270d951d9c1acaea0d151f0a5c48a1255e75de350bf9bcbc15fe9  " *
 grainpath * "\n"
 
 graininteractionpath = "test/test.grain-interaction.1.vtp"
 graininteractionchecksum = 
-"c61f314a997c4405eaa98e6a4e3f81368ab2e1c60d06d9a0d998b69d7c8fb1bf  " *
+"b8e49252a0ac87c2fce05e68ffab46589853429dc9f75d89818e4a37b953b137  " *
 graininteractionpath * "\n"
 
 oceanpath = "test/test.ocean.1.vts"
diff --git a/test/wall.jl b/test/wall.jl
@@ -2,14 +2,12 @@
 
 # Check the basic dynamic wall functionality
 
-Compat.@info "#### $(basename(@__FILE__)) ####"
-
 Compat.@info "# Test wall initialization"
 Compat.@info "Testing argument value checks"
 sim = Granular.createSimulation()
 Granular.addGrainCylindrical!(sim, [ 0., 0.], 10., 2., verbose=false)
 @test_throws ErrorException Granular.addWallLinearFrictionless!(sim,
-                                                                [.1, .1, .1],
+                                                                [.1, .1, .1, .1],
                                                                 1.)
 @test_throws ErrorException Granular.addWallLinearFrictionless!(sim,
                                                                 [1., 1.],
@@ -18,7 +16,7 @@ Granular.addGrainCylindrical!(sim, [ 0., 0.], 10., 2., verbose=false)
                                                                 [.1, .1, .1],
                                                                 1.)
 @test_throws ErrorException Granular.addWallLinearFrictionless!(sim,
-                                                                [0., 1.], 1.,
+                                                                [0., 1., 1.], 1.,
                                                                 bc="asdf")
 sim = Granular.createSimulation()
 @test_throws ErrorException Granular.addWallLinearFrictionless!(sim, [1., 0.],
@@ -56,7 +54,7 @@ sim.walls[1].force = 1.0
 @test Granular.getWallNormalStress(sim, wall_index=1, stress_type="effective") ≈ 1.0/(20.0*2.0)
 @test_throws ErrorException Granular.getWallNormalStress(sim, wall_index=1, stress_type="nonexistent")
 
-sim.walls[1].normal = [1.0, 1.0]
+sim.walls[1].normal = [1.0, 1.0, 1.0]
 @test_throws ErrorException Granular.getWallSurfaceArea(sim, 1)
 @test_throws ErrorException Granular.getWallSurfaceArea(sim, [1.,1.], 0.5)