From af0e98cb7da9fa5e44debf97013d34d41694807c Mon Sep 17 00:00:00 2001
From: Rainer Weinberger <rainer.weinberger@cfa.harvard.edu>
Date: Tue, 19 Feb 2019 17:07:11 -0500
Subject: [PATCH] Added example shocktube_1d

---
 examples/shocktube_1d/Config.sh  |  30 +++
 examples/shocktube_1d/Riemann.py | 179 ++++++++++++++++++
 examples/shocktube_1d/check.py   | 302 +++++++++++++++++++++++++++++++
 examples/shocktube_1d/create.py  | 108 +++++++++++
 examples/shocktube_1d/param.txt  | 102 +++++++++++
 test.sh                          |   1 +
 6 files changed, 722 insertions(+)
 create mode 100644 examples/shocktube_1d/Config.sh
 create mode 100644 examples/shocktube_1d/Riemann.py
 create mode 100644 examples/shocktube_1d/check.py
 create mode 100644 examples/shocktube_1d/create.py
 create mode 100644 examples/shocktube_1d/param.txt

diff --git a/examples/shocktube_1d/Config.sh b/examples/shocktube_1d/Config.sh
new file mode 100644
index 0000000..1f9c471
--- /dev/null
+++ b/examples/shocktube_1d/Config.sh
@@ -0,0 +1,30 @@
+#!/bin/bash            # this line only there to enable syntax highlighting in this file
+
+## examples/shocktube_1d/Config.sh
+## config file for 1d shocktube probelm
+
+
+#--------------------------------------- Basic operation mode of code
+ONEDIMS                                  # 1d simulation
+REFLECTIVE_X=2                           # X Boundary; 1: Reflective, 2: Inflow/Outflow; not active: periodic
+GAMMA=1.4                                # Adiabatic index of gas; 5/3 if not set
+
+#--------------------------------------- Mesh motion and regularization
+REGULARIZE_MESH_CM_DRIFT                 # Mesh regularization; Move mesh generating point towards center of mass to make cells rounder.
+REGULARIZE_MESH_CM_DRIFT_USE_SOUNDSPEED  # Limit mesh regularization speed by local sound speed
+REGULARIZE_MESH_FACE_ANGLE               # Use maximum face angle as roundness criterion in mesh regularization
+
+#--------------------------------------- Time integration options
+FORCE_EQUAL_TIMESTEPS                    # variable but global timestep
+
+#---------------------------------------- Single/Double Precision
+DOUBLEPRECISION=1                        # Mode of double precision: not defined: single; 1: full double precision 2: mixed, 3: mixed, fewer single precisions; unless short of memory, use 1.
+INPUT_IN_DOUBLEPRECISION                 # initial conditions are in double precision
+OUTPUT_IN_DOUBLEPRECISION                # snapshot files will be written in double precision
+
+#--------------------------------------- Output/Input options
+HAVE_HDF5                                # needed when HDF5 I/O support is desired (recommended)
+
+#--------------------------------------- Testing and Debugging options
+DEBUG                                    # enables core-dumps
+
diff --git a/examples/shocktube_1d/Riemann.py b/examples/shocktube_1d/Riemann.py
new file mode 100644
index 0000000..e7baa27
--- /dev/null
+++ b/examples/shocktube_1d/Riemann.py
@@ -0,0 +1,179 @@
+""" @package examples/shocktube_1d/Riemann.py
+exact solution to Riemann problem
+    
+created by Rainer Weinberger, last modified: 19.02.2019
+"""
+
+import numpy as np
+
+
+def f_K(pres, W_K, gamma):
+    """
+    Flux functions for left or right state of a Riemann problem;
+    Auxiliary function for NewtonRaphson.
+    
+    \param[in] pres float pressure of central state
+    \param[in] W_K array, shape (3,), left/right hand side state of Riemann problem
+    \param[in] gamma float, adiabatic index of gas
+    """
+    integy_K = W_K[2] / ( gamma - 1.0 ) / W_K[0]
+    a_K = np.sqrt(  gamma * W_K[2] / W_K[0] )
+    A = 2.0 / ( gamma + 1.0 ) / W_K[0]
+    B = ( gamma - 1.0 ) / ( gamma + 1.0 ) * W_K[2]
+    
+    if pres > W_K[2] :    ##shock
+        return ( pres - W_K[2] ) * np.sqrt( A / ( pres + B ) )
+    else:    ## rarefaction
+        exponent = ( gamma - 1.0) / 2.0 / gamma
+        return ( 2.0 * a_K / ( gamma - 1.0 ) ) * ( ( pres / W_K[2] )**exponent - 1.0 )
+
+def f_K_prime(pres, W_K, gamma_K):
+    """
+    Derivative of flux functions for left or right state of a Riemann problem;
+    Auxiliary function for NewtonRaphson.
+    
+    \param[in] pres float pressure of central state
+    \param[in] W_K array, shape (3,), left/right hand side state of Riemann problem
+    \param[in] gamma float, adiabatic index of gas
+    """
+    integy_K = W_K[2] / ( gamma_K - 1.0 ) / W_K[0]
+    a_K = np.sqrt( gamma_K *  W_K[2] / W_K[0] )
+    A = 2.0 / ( gamma_K + 1.0 ) / W_K[0]
+    B = ( gamma_K - 1.0 ) / ( gamma_K + 1.0 ) * W_K[2]
+    
+    if pres > W_K[2] :    ##shock
+        return np.sqrt( A / ( B + pres ) ) * ( 1.0 - ( pres - W_K[2] ) / ( 2.0 * ( B + pres ) ) )
+    else:    ## rarefaction
+        exponent = -( gamma_K + 1.0 ) / 2.0 / gamma_K
+        return ( pres / W_K[2] )**exponent / W_K[0] / a_K
+
+def NewtonRaphson(pres, W_L, W_R, gamma):
+    """
+    NewtonRaphson iteration to determine central pressure of a Riemann problem 
+
+    \param[in] W_L array, shape (3,) left hand side density, velocity and pressure
+    \param[in] W_R array, shape (3,) right hand side density, velocity and pressure
+    \param[in] gamma float, adiabatic index of gas
+    
+    \return pres pressure of central region
+    """
+    change = 1.0
+    iter = 0
+    while np.abs(change) > 1e-6:
+        iter += 1
+        ## root finding for function
+        function = f_K(pres, W_L, gamma) + f_K(pres, W_R, gamma) + W_R[1] - W_L[1]
+        function_prime = f_K_prime(pres, W_L, gamma) + f_K_prime(pres, W_R, gamma)
+        pres_new = pres - function / function_prime
+        change = 2.0 * ( pres_new - pres ) / ( pres_new + pres )
+        pres = pres_new
+        if(iter > 100):
+            print("ERROR: NewtonRaphson: maximum number of iterations reached! Could not converge!")
+            break
+    return pres
+    
+def rarefaction_fan_left(W_K, gamma, xx, time):
+    gm1 = (gamma - 1.0)
+    gp1 = (gamma + 1.0)
+    gamma_ratio = gm1 / gp1
+    a = np.sqrt( gamma * W_K[2] / W_K[0] )
+    rho = W_K[0] * (2.0 / gp1 + gamma_ratio / a * ( W_K[1] - xx / time ) )**( 2.0 / gm1 )## wrong?
+    vel = 2.0 / gp1 * ( a + gm1 / 2.0 * W_K[1] + xx / time )
+    pres = W_K[2] * ( 2.0 / gp1 + gamma_ratio / a * ( W_K[1] - xx / time ) )**( 2.0 * gamma / gm1 )
+    return np.array([rho, vel, pres]).T
+        
+def rarefaction_fan_right(W_K, gamma, xx, time):
+    gm1 = (gamma - 1.0)
+    gp1 = (gamma + 1.0)
+    gamma_ratio = gm1 / gp1
+    a = np.sqrt( gamma * W_K[2] / W_K[0] )
+    rho = W_K[0] * (2.0 / gp1 - gamma_ratio / a * ( W_K[1] - xx / time ) )**( 2.0 / gm1 )
+    vel = 2.0 / gp1 * ( -a + gm1 / 2.0 * W_K[1] + xx / time )
+    pres = W_K[2] * ( 2.0 / gp1 - gamma_ratio / a * ( W_K[1] - xx / time ) )**( 2.0 * gamma / gm1 )
+    return np.array([rho, vel, pres]).T
+        
+def RiemannProblem(xx, x0, W_L, W_R, gamma, time):
+    """
+    RiemannProblem: Samples the solution of a Riemann problem at a given time at positions xx
+    
+    \param[in] xx, array, shape (n,), 1d coordinate of grid
+    \param[in] x0, float initial coordinate of discontinuety
+    \param[in] W_L, array, shape(3,), density, velocity and pressure of left state
+    \param[in] W_R, array, shape(3,), density, velocity and pressure of right state
+    \param[in] gamma, float, adiabatic index of gas
+    \param[in] time, time since initial conditions
+    
+    \return xx, W, x_shock; xx: as input; W: array, shape(n,3) densiy, velocity and pressure at pos xx
+            x_shock: postition of charactaeristic features
+    """
+    
+    ## some auxiliary quantitites
+    a_L = np.sqrt( gamma *  W_L[2] / W_L[0] )    ## sound speeds
+    a_R = np.sqrt( gamma *  W_R[2] / W_R[0] )
+    gm1 = gamma - 1.0    ## gamma +- 1
+    gp1 = gamma + 1.0
+    gamma_ratio = gm1 / gp1
+    PosOfCharacteristics = np.zeros(5) ## 0: left most to 4: right most; 2 is rarefaction; if 0 and 1 (3 and 4) have the same value, there is a shock, otherwise rarefaction wave
+
+    ## calculate central pressure; initial guess: arithmetic mean
+    pstar = 0.5 * ( W_L[2] + W_R[2] )
+    pstar = NewtonRaphson( pstar, W_L, W_R, gamma)
+    
+    ## calculate central velocity and sound speed
+    ustar = 0.5 * ( W_L[1] + W_R[1] ) + 0.5 * ( f_K(pstar, W_R, gamma) - f_K(pstar, W_L, gamma) )
+    astar_L = a_L * ( pstar / W_L[2] )**( (gamma - 1.0) / (2.0 * gamma) )
+    astar_R = a_R * ( pstar / W_R[2] )**( (gamma - 1.0) / (2.0 * gamma) )
+    
+    ## decide on shock or rarefaction on left and right; get velocities of features
+    PosOfCharacteristics[2] = ustar 
+    if pstar > W_L[2]:    ## shock on left
+        PosOfCharacteristics[0] = W_L[1] - a_L * np.sqrt( gp1 / 2.0 / gamma * pstar / W_L[2] + gm1 / 2.0 / gamma )
+        PosOfCharacteristics[1] = PosOfCharacteristics[0]
+        leftShock = True
+    else:    ## rarefaction wave on left
+        PosOfCharacteristics[0] = W_L[1] - a_L ## outer
+        PosOfCharacteristics[1] = ustar - astar_L ## inner   
+    if pstar > W_R[2]:    ## shock on right
+        PosOfCharacteristics[4] = W_R[1] + a_R * np.sqrt( gp1 / 2.0 / gamma * pstar / W_R[2] + gm1 / 2.0 / gamma )
+        PosOfCharacteristics[3] = PosOfCharacteristics[4]
+        rightShock = True
+    else:    ## rarefaction wave on right
+        PosOfCharacteristics[4] = W_R[1] + a_R ## outer
+        PosOfCharacteristics[3] = ustar + astar_R ## inner
+    
+    ## self-similar, i.e. pos = velocity * time
+    PosOfCharacteristics[:] *= time
+    
+    ## select different regions
+    i_L, = np.where(xx-x0 < PosOfCharacteristics[0])
+    i_star_L, = np.where( (xx-x0 >= PosOfCharacteristics[1]) & (xx-x0 < PosOfCharacteristics[2]) )
+    i_star_R, = np.where( (xx-x0 >= PosOfCharacteristics[2]) & (xx-x0 < PosOfCharacteristics[3]) )
+    i_R, = np.where(xx-x0 >= PosOfCharacteristics[4])
+    
+    ## sample solution
+    W = np.zeros( [len(xx), 3], dtype=np.float64 )
+    ## left most and right most state: same as initial conditions
+    W[i_L,:] = W_L[:]
+    W[i_R, :] = W_R[:]
+
+    if PosOfCharacteristics[0] != PosOfCharacteristics[1]: ## rarefaction wave on left
+        i_fan_L, = np.where( (xx-x0 >= PosOfCharacteristics[0]) & (xx-x0 < PosOfCharacteristics[1]) )
+        W[i_fan_L,:] = rarefaction_fan_left(W_L, gamma, xx[i_fan_L]-x0, time)    
+        W[i_star_L, 0] = (pstar / W_L[2])**(1./gamma) * W_L[0]
+    else: ## shock on left
+        W[i_star_L, 0] = W_L[0] * ( ( pstar / W_L[2] + gamma_ratio ) / ( gamma_ratio * pstar / W_L[2] + 1.0 ) )
+    
+    W[i_star_L, 1] = ustar
+    W[i_star_L, 2] = pstar
+
+    if PosOfCharacteristics[3] != PosOfCharacteristics[4]:  ## rarefaction wave on right
+        i_fan_R, = np.where( (xx-x0 >= PosOfCharacteristics[3]) & (xx-x0 < PosOfCharacteristics[4]) )
+        W[i_fan_R, :] = rarefaction_fan_right(W_R, gamma, xx[i_fan_R]-x0, time)
+        W[i_star_R, 0] = (pstar / W_R[2])**(1./gamma) * W_R[0]
+    else: ## shock on right
+        W[i_star_R, 0] = W_R[0] * ( ( pstar / W_R[2] + gamma_ratio ) / ( gamma_ratio * pstar / W_R[2] + 1.0 ) )
+
+    W[i_star_R, 1] = ustar
+    W[i_star_R, 2] = pstar
+    
+    return xx, W, PosOfCharacteristics
diff --git a/examples/shocktube_1d/check.py b/examples/shocktube_1d/check.py
new file mode 100644
index 0000000..64436b6
--- /dev/null
+++ b/examples/shocktube_1d/check.py
@@ -0,0 +1,302 @@
+""" @package ./examples/shocktube_1d/check
+Code that checks results of 1d shocktube problem
+
+created by Rainer Weinberger, last modified: 19.02.2019
+"""
+
+""" load libraries """
+import sys    # needed for exit codes
+import numpy as np    # scientific computing package
+import h5py    # hdf5 format
+import matplotlib.pyplot as plt    ## needs to be active for plotting!
+
+from Riemann import *    ## Riemann-solver
+
+createFigures = True
+forceExitOnError=False  ## exits immediately when tolerance is exceeded
+
+""" check functiions """
+def CheckL1Error(Pos, W, W_L, W_R, gamma, position_0, time):
+    """
+    Compare the hydrodynamical quantities of the simulation with the exact 
+    solution of the Riemann problem, calculate the L1 error and check whether
+    avarge L1 error is acceptable
+    
+    \param[in] Pos: shape (n,) array with 1d positions
+    \param[in] W: shape (n,3) array with density, velocity and pressure of each cell
+    \param[in] W_L: shape (3,) array with left initial condition state (density, velocity and pressure)
+    \param[in] W_R: shape (3,) array with right initial condition state (density, velocity and pressure)
+    \param[in] gamma: adiabatic index of gas
+    \param[in] position_0: initial position of discontinuity
+    \param[in] time: time of snapshot data
+    
+    \return status: zero if everything is within tolerances, otherwise 1
+    """
+    xx, W_exact, PosOfCharacteristics = RiemannProblem(Pos, position_0, W_L, W_R, gamma, time)
+    
+    norm = np.abs(W_exact)
+    norm[:,1] = 1 ## use absolute error in velocity component
+    
+    ## calculate L1 norm
+    delta = np.abs(W_exact - W) / norm
+    L1 = np.average(delta, axis=0)
+    
+    ## tolarance value; found empirically, fist order convergence!
+    L1MaxAllowed = 2.0 / np.float(Pos.shape[0])
+    
+    if np.any(L1 > L1MaxAllowed):
+        print("CheckL1Error: ERROR: L1 error too large: %g %g %g; tolerance %g"% (L1[0], L1[1], L1[2], L1MaxAllowed) )
+        return 1
+    else:
+        print("CheckL1Error: L1 error fine: %g %g %g; tolerance %g"% (L1[0], L1[1], L1[2], L1MaxAllowed) )
+        return 0
+
+
+def CheckTotalVariation(Pos, W, W_L, W_R, gamma, position_0, time):
+    """
+    Compare the total variation in simulation quantities with the total 
+    variation in the analytic solution of the Riemann problem
+    
+    \param[in] Pos: shape (n,) array with 1d positions
+    \param[in] W: shape (n,3) array with density, velocity and pressure of each cell
+    \param[in] W_L: shape (3,) array with left initial condition state (density, velocity and pressure)
+    \param[in] W_R: shape (3,) array with right initial condition state (density, velocity and pressure)
+    \param[in] gamma: adiabatic index of gas
+    \param[in] position_0: initial position of discontinuity
+    \param[in] time: time of snapshot data
+    
+    \return status: zero if everything is within tolerances, otherwise 1
+    """
+    xx, W_exact, PosOfCharacteristics = RiemannProblem(Pos, position_0, W_L, W_R, gamma, time)
+    
+    TotalVariationSim = np.zeros(3)
+    TotalVariationExact = np.zeros(3)
+    
+    i_sorted = np.argsort(Pos) ## sorted by position
+    dW = W[i_sorted[1:],:] - W[i_sorted[:-1],:] ## difference of neighbouring cells
+    dW_exact = W_exact[i_sorted[1:],:] - W_exact[i_sorted[:-1],:]
+
+    for i in np.arange(3):  
+        i1_pos,  = np.where(dW[:,i] >= 0)
+        i1_neg,  = np.where(dW[:,i] < 0) 
+        TotalVariationSim[i] = np.sum(dW[i1_pos, i], axis=0) - np.sum(dW[i1_neg, i])
+        TotalVariationExact[i] = np.sum(dW_exact[i1_pos, i], axis=0) - np.sum(dW_exact[i1_neg, i])
+        
+    MaxRatioAllowed = 1.01
+    if np.any( TotalVariationSim / TotalVariationExact > MaxRatioAllowed):
+        print("CheckTotalVariation: ERROR: TotalVariation Sim/Exact: %g %g %g, tolerance: %g" % (TotalVariationSim[0] / TotalVariationExact[0], TotalVariationSim[1] / TotalVariationExact[1], TotalVariationSim[2] / TotalVariationExact[2], MaxRatioAllowed) )
+        return 1
+    else:
+        print("CheckTotalVariation: TotalVariation Sim/Exact fine: %g %g %g, tolerance: %g" % (TotalVariationSim[0] / TotalVariationExact[0], TotalVariationSim[1] / TotalVariationExact[1], TotalVariationSim[2] / TotalVariationExact[2], MaxRatioAllowed) )
+        return 0 
+
+
+def CheckWidthOfDiscontinuities(Pos, W, W_L, W_R, gamma, position_0, time):
+    """
+    Measure the width of the fluid discontinuities in simulation quantities 
+    to assess the numerical diffusivity of the scheme
+    
+    \param[in] Pos: shape (n,) array with 1d positions
+    \param[in] W: shape (n,3) array with density, velocity and pressure of each cell
+    \param[in] W_L: shape (3,) array with left initial condition state (density, velocity and pressure)
+    \param[in] W_R: shape (3,) array with right initial condition state (density, velocity and pressure)
+    \param[in] gamma: adiabatic index of gas
+    \param[in] position_0: initial position of discontinuity
+    \param[in] time: time of snapshot data
+    
+    \return status: zero if everything is within tolerances, otherwise 1
+    """
+    xx, W_exact, PosOfCharacteristics = RiemannProblem(Pos, position_0, W_L, W_R, gamma, time)
+    
+    for i, pos_char in enumerate(PosOfCharacteristics):
+        ReturnFlag = 0
+        ## PosOfCharacteristics will give different values in index 0 and 1 (3 and 4) 
+        ## if it is a rarefaction; in this case, don't check, otherwise do check
+        if i == 1 or i == 4:
+            continue
+        if i == 0 or i == 3:
+            if PosOfCharacteristics[i] != PosOfCharacteristics[i+1]:
+                continue
+
+        ## get hydrodynamic states left and right of jump
+        xx = np.array( [10.0+pos_char-1e-3, 10.0+pos_char+1e-3] )
+        xx, W_exact, dummy = RiemannProblem(xx, 10.0, W_L, W_R, gamma, time)
+
+        ## get threshold values to measure how many cells are within this interval
+        jump = W_exact[1,:] - W_exact[0,:]
+        percentiles = np.array([W_exact[0,:] + 0.05 * jump, W_exact[0,:] + 0.95 * jump])
+        percentile_05 = np.min( percentiles, axis=0 )
+        percentile_95 = np.max( percentiles, axis=0 )
+        i_sorted = np.argsort( np.abs(Pos-pos_char-position_0) )
+        
+        i_low = np.full(3, i_sorted[0], dtype=np.int32)
+        i_high = np.full(3, i_sorted[0], dtype=np.int32)
+        
+        ## check by how many cells 5th to 95th percentile of jump are sampled
+        for j in np.arange(3):
+            while W[i_low[j],j] > percentile_05[j] and W[i_low[j],j] < percentile_95[j]:
+                i_low[j] -= 1
+            while W[i_low[j],j] > percentile_05[j] and W[i_low[j],j] < percentile_95[j]:
+                i_high[j] += 1
+                
+        ## sufficient for exact Riemann solver
+        MaxNumerOfCells = 4
+                
+        if(i == 2):
+            print("CheckWidthOfDiscontinuities: density jump at contact discontinuity resolved by %d cells (5th to 95th precentile), tolerance: %d" \
+                  % (i_high[0]-i_low[0], MaxNumerOfCells) )
+        else:
+            print("CheckWidthOfDiscontinuities: density, velocity and pressure jump at shock resolved by %d, %d and %d cells (5th to 95th precentile), tolerance: %d" \
+                  % (i_high[0]-i_low[0], i_high[1]-i_low[1], i_high[2]-i_low[2], MaxNumerOfCells) )
+        if np.any(i_high-i_low > MaxNumerOfCells):
+            print("CheckWidthOfDiscontinuities: ERROR: discontinuity too wide!")
+            ReturnFlag += 1
+    
+    return ReturnFlag
+
+def PlotSimulationData(Pos, W, W_L, W_R, gamma, position_0, time, simulation_directory):
+    """
+    Plot density, velocity, specific internal energy and pressure of
+    Simulation and exact solution on top
+    
+    \param[in] Pos: shape (n,) array with 1d positions
+    \param[in] W: shape (n,3) array with density, velocity and pressure of each cell
+    \param[in] W_L: shape (3,) array with left initial condition state (density, velocity and pressure)
+    \param[in] W_R: shape (3,) array with right initial condition state (density, velocity and pressure)
+    \param[in] gamma: adiabatic index of gas
+    \param[in] position_0: initial position of discontinuity
+    \param[in] time: time of snapshot data
+    \param[in] simulation_directory: path to simulation
+    
+    \return status: zero if everything went fine
+    """
+    xx = np.linspace(Pos.min(),Pos.max(),1000)
+    xx, W_exact, dummy = RiemannProblem(xx, position_0, W_L, W_R, gamma, time)
+    
+    min = np.zeros(4)
+    max = np.zeros(4)
+    min[0] = np.min( W_exact[:,0] ) - 0.1
+    max[0] = np.max( W_exact[:,0] ) + 0.1
+    min[1] = np.min( W_exact[:,1] ) - 0.1
+    max[1] = np.max( W_exact[:,1] ) + 0.1
+    min[2] = np.min( W_exact[:,2] / W_exact[:,0] / (gamma - 1.0) ) - 0.1
+    max[2] = np.max( W_exact[:,2] / W_exact[:,0] / (gamma - 1.0) ) + 0.1
+    min[3] = np.min( W_exact[:,2] ) - 0.1
+    max[3] = np.max( W_exact[:,2] ) + 0.1
+
+    ## plot:
+    fig, ax = plt.subplots(4, sharex=True, figsize=np.array([6.9,6.0]) )
+    fig.subplots_adjust(left = 0.13, bottom = 0.09,right = 0.98, top = 0.98)
+    
+    nskip = 0
+    i_show = np.arange(nskip, len(density)-nskip)
+    ax[0].plot(Pos, W[:,0], ls="", marker='o')
+    ax[0].plot(xx, W_exact[:,0], color="k")
+    ax[1].plot(Pos, W[:,1], ls="", marker='o')
+    ax[1].plot(xx, W_exact[:,1], color="k")
+    ax[2].plot(Pos, W[:,2]/W[:,0]/(gamma - 1.0), ls="", marker='o')
+    ax[2].plot(xx, W_exact[:,2]/W_exact[:,0]/(gamma - 1.0), color="k")
+    ax[3].plot(Pos, W[:,2], ls="", marker='o')
+    ax[3].plot(xx, W_exact[:,2], color="k")
+    
+    ax[3].set_xlim([0,20])
+    for i_plot in np.arange(4):
+        ax[i_plot].set_ylim([ min[i_plot], max[i_plot] ])
+    
+    ## set labels
+    ax[3].set_xlabel(r"pos")  
+    ax[0].set_ylabel(r"density")
+    ax[1].set_ylabel(r"velocity")
+    ax[2].set_ylabel(r"spec. int. energy")
+    ax[3].set_ylabel(r"pressure")
+
+    print(simulation_directory+"/output/figure_%03d.pdf" % (i_file) )
+    fig.savefig(simulation_directory+"/output/figure_%03d.pdf" % (i_file))
+    plt.close(fig)
+
+    return 0
+
+simulation_directory = str(sys.argv[1])
+print("examples/wave_1d/check.py: checking simulation output in directory " + simulation_directory) 
+
+Dtype = np.float64  # double precision: np.float64, for single use np.float32
+## open initial conditiions to get parameters
+directory = simulation_directory+""
+filename = "IC.hdf5" 
+try:
+    data = h5py.File(directory+filename, "r")
+except:
+    sys.exit(1)
+IC_position = np.array(data["PartType0"]["Coordinates"], dtype = np.float64)
+IC_mass = np.array(data["PartType0"]["Masses"], dtype = np.float64)
+IC_velocity = np.array(data["PartType0"]["Velocities"], dtype = np.float64)
+IC_internalEnergy = np.array(data["PartType0"]["InternalEnergy"], dtype = np.float64)
+NumberOfCells = np.int32(data["Header"].attrs["NumPart_Total"][0])
+DeltaMaxAllowed = 2.0 / np.float(NumberOfCells)
+
+## get parameters of Riemann problem from ICs
+gamma = 1.4  ## needs to be identical to Config.sh =(5./3.) if not specified there!
+dx = IC_position[1,0]-IC_position[0,0] ## assuming a uniform grid
+W_L = np.array([IC_mass[0]/dx, IC_velocity[0,0], (gamma - 1.0)*IC_internalEnergy[0]*IC_mass[0]/dx])
+W_R = np.array([IC_mass[-1]/dx, IC_velocity[-1,0], (gamma - 1.0)*IC_internalEnergy[-1]*IC_mass[-1]/dx])
+i_0, = np.where( (IC_mass[:]/dx == W_R[0]) & (IC_velocity[:,0] == W_R[1]) & ((gamma - 1.0)*IC_internalEnergy[-1]*IC_mass[-1]/dx == W_R[2]) )
+position_0 = 0.5 * (IC_position[i_0[0]-1,0]+IC_position[i_0[0],0]) ## discontinuity at interface position
+
+""" loop over all output files """
+i_file = -1
+ReturnFlag = 0
+while True:
+    i_file += 1
+    
+    ## read in data
+    directory = simulation_directory+"/output/"
+    filename = "snap_%03d.hdf5" % (i_file)
+    print("try to open "+directory+filename)
+    ## open hdf5 file
+    try:
+        data = h5py.File(directory+filename, "r")
+    except:
+        break
+    print("analyzing "+ filename)
+    
+    ## get data from snapshot
+    time = np.float( data["Header"].attrs["Time"] )
+    position = np.array(data["PartType0"]["Coordinates"], dtype = np.float64)
+    density = np.array(data["PartType0"]["Density"], dtype = np.float64)
+    vel = np.array(data["PartType0"]["Velocities"], dtype = np.float64)
+    internalEnergy = np.array(data["PartType0"]["InternalEnergy"], dtype = np.float64)
+    ## convert to more useful data structure
+    W = np.array([density, vel[:,0], (gamma-1.0)*internalEnergy*density], dtype=np.float64).T ## shape: (n,3)
+    
+    
+    """ plot data if you want """
+    if createFigures:
+        ReturnFlag += PlotSimulationData(position[:,0], W, W_L, W_R, gamma, position_0, time, simulation_directory)
+        if ReturnFlag > 0 and forceExitOnError:
+            print('ERROR: something went wrong in plot')
+            sys.exit(ReturnFlag)
+    
+    """ perform checks """
+    ReturnFlag += CheckL1Error(position[:,0], W, W_L, W_R, gamma, position_0, time)
+    if ReturnFlag > 0 and forceExitOnError:
+        print('ERROR: exceeding tolerance!')
+        sys.exit(ReturnFlag)
+        
+    ReturnFlag += CheckTotalVariation(position[:,0], W, W_L, W_R, gamma, position_0, time)
+    if ReturnFlag > 0 and forceExitOnError:
+        print('ERROR: exceeding tolerance!')
+        sys.exit(ReturnFlag)
+    
+    ReturnFlag += CheckWidthOfDiscontinuities(position[:,0], W, W_L, W_R, gamma, position_0, time)
+    if ReturnFlag > 0 and forceExitOnError:
+        print('ERROR: exceeding tolerance!')
+        sys.exit(ReturnFlag)
+        
+if ReturnFlag == 0:
+    print("check.py: success!")
+else:
+    print("check.py: failed!")
+
+sys.exit(ReturnFlag)
+
+
diff --git a/examples/shocktube_1d/create.py b/examples/shocktube_1d/create.py
new file mode 100644
index 0000000..2854575
--- /dev/null
+++ b/examples/shocktube_1d/create.py
@@ -0,0 +1,108 @@
+""" @package examples/shocktube_1d/create.py
+Code that creates 1d shocktube problem initial conditions
+supposed to be as siple as possible and self-contained
+
+created by Rainer Weinberger, 19.02.2019
+"""
+
+""" load libraries """
+import sys    # system specific calls
+import numpy as np    # scientific computing package
+import h5py    # hdf5 format
+
+simulation_directory = str(sys.argv[1])
+print("examples/shocktube_1d/create.py: creating ICs in directory " + simulation_directory)
+
+FloatType = np.float64  # double precision: np.float64, for single use np.float32
+IntType = np.int32
+
+Boxsize = FloatType(20.0)
+NumberOfCells = IntType(128)
+
+""" initial condition parameters """
+FilePath = simulation_directory + '/IC.hdf5'
+
+## Riemann problem
+position_0 = FloatType(10.0)  # initial position of discontinuity
+density_0 = FloatType(1.0)
+density_1 = FloatType(0.125) 
+velocity_0 = FloatType(0.0)
+velocity_1 = FloatType(0.0)
+pressure_0 = FloatType(1.0)
+pressure_1 = FloatType(0.1)
+
+gamma = FloatType(1.4)  ## note: this has to be consistent with the parameter settings for Arepo!
+gamma_minus1 = gamma - FloatType(1.0)
+uthermal_0 = pressure_0 / gamma_minus1 / density_0
+uthermal_1 = pressure_1 / gamma_minus1 / density_1
+
+""" set up grid: uniform 1d grid """
+## spacing
+dx = Boxsize / FloatType(NumberOfCells)
+## position of first and last cell
+pos_first, pos_last = FloatType(0.5) * dx, Boxsize - FloatType(0.5) * dx
+
+## set up grid
+Pos = np.zeros([NumberOfCells, 3], dtype=FloatType)
+Pos[:,0] = np.linspace(pos_first, pos_last, NumberOfCells, dtype=FloatType)
+Volume = np.full(NumberOfCells, dx, dtype=FloatType)
+
+""" set up hydrodynamical quantitites """
+## left state
+Density = np.full(Pos.shape[0], density_0, dtype=FloatType)
+Velocity = np.zeros(Pos.shape, dtype=FloatType)
+Velocity[:,0] = velocity_0
+Uthermal = np.full(Pos.shape[0], uthermal_0, dtype=FloatType)
+
+## right state
+i_right, = np.where( Pos[:,0] > position_0)
+Density[i_right] = density_1
+Velocity[i_right,0] = velocity_1
+Uthermal[i_right] = uthermal_1
+
+## mass instead of density needed for input
+Mass = Density * Volume
+
+""" write *.hdf5 file; minimum number of fields required by Arepo """
+IC = h5py.File(FilePath, 'w')    # open/create file
+
+## create hdf5 groups
+header = IC.create_group("Header")    # create header group
+part0 = IC.create_group("PartType0")    # create particle group for gas cells
+
+## write header entries
+NumPart = np.array([NumberOfCells, 0, 0, 0, 0, 0], dtype=IntType)
+header.attrs.create("NumPart_ThisFile", NumPart)
+header.attrs.create("NumPart_Total", NumPart)
+header.attrs.create("NumPart_Total_HighWord", np.zeros(6, dtype=IntType) )
+header.attrs.create("MassTable", np.zeros(6, dtype=IntType) )
+header.attrs.create("Time", 0.0)
+header.attrs.create("Redshift", 0.0)
+header.attrs.create("BoxSize", Boxsize)
+header.attrs.create("NumFilesPerSnapshot", 1)
+header.attrs.create("Omega0", 0.0)
+header.attrs.create("OmegaB", 0.0)
+header.attrs.create("OmegaLambda", 0.0)
+header.attrs.create("HubbleParam", 1.0)
+header.attrs.create("Flag_Sfr", 0)
+header.attrs.create("Flag_Cooling", 0)
+header.attrs.create("Flag_StellarAge", 0)
+header.attrs.create("Flag_Metals", 0)
+header.attrs.create("Flag_Feedback", 0)
+if Pos.dtype == np.float64:
+    header.attrs.create("Flag_DoublePrecision", 1)
+else:
+    header.attrs.create("Flag_DoublePrecision", 0)
+
+## write cell data
+part0.create_dataset("ParticleIDs", data=np.arange(1, NumberOfCells+1) )
+part0.create_dataset("Coordinates", data=Pos)
+part0.create_dataset("Masses", data=Mass)
+part0.create_dataset("Velocities", data=Velocity)
+part0.create_dataset("InternalEnergy", data=Uthermal)
+
+## close file
+IC.close()
+
+""" normal exit """
+sys.exit(0) 
diff --git a/examples/shocktube_1d/param.txt b/examples/shocktube_1d/param.txt
new file mode 100644
index 0000000..60a410a
--- /dev/null
+++ b/examples/shocktube_1d/param.txt
@@ -0,0 +1,102 @@
+%% examples/shocktube_1d/param.txt
+% parameter file for 1d shocktube problem
+
+%% system options
+MaxMemSize                                2500
+CpuTimeBetRestartFile                     9000
+TimeLimitCPU                              90000
+
+%% initial conditions
+InitCondFile                              ./run/examples/shocktube_1d/IC
+ICFormat                                  3
+
+%% output options
+OutputDir                                 ./run/examples/shocktube_1d/output/
+SnapshotFileBase                          snap
+SnapFormat                                3
+NumFilesPerSnapshot                       1
+NumFilesWrittenInParallel                 1
+
+%% resubmit opitions
+ResubmitOn                                0
+ResubmitCommand                           my-scriptfile
+OutputListFilename                        ol
+OutputListOn                              0
+
+%% simulation mode
+CoolingOn                                 0
+StarformationOn                           0
+PeriodicBoundariesOn                      1
+ComovingIntegrationOn                     0
+
+%% Cosmological parameters
+Omega0                                    0.0
+OmegaBaryon                               0.0
+OmegaLambda                               0.0
+HubbleParam                               1.0
+
+%% Simulation parameters
+BoxSize                                   20.0
+TimeOfFirstSnapshot                       0.0
+TimeBetStatistics                         0.1
+TimeBegin                                 0.0
+TimeMax                                   5.0
+TimeBetSnapshot                           1.0
+
+%% Units
+UnitVelocity_in_cm_per_s                  1.0
+UnitLength_in_cm                          1.0
+UnitMass_in_g                             1.0
+GravityConstantInternal                   0.0
+
+%% settings for gravity
+ErrTolIntAccuracy                         0.1
+ErrTolTheta                               0.1
+ErrTolForceAcc                            0.1
+
+%% timestepping
+MaxSizeTimestep                           0.1
+MinSizeTimestep                           1e-5
+TypeOfTimestepCriterion                   0
+
+%% moving mesh
+CellShapingSpeed                          0.5
+CellMaxAngleFactor                        2.25
+TypeOfOpeningCriterion                    1
+
+%% hydrodynamics
+CourantFac                                0.3
+LimitUBelowThisDensity                    0.0
+LimitUBelowCertainDensityToThisValue      0.0
+DesNumNgb                                 64
+MaxNumNgbDeviation                        2
+InitGasTemp                               0.0
+MinGasTemp                                0.0
+MinEgySpec                                0.0
+MinimumDensityOnStartUp                   0.0
+
+%% domain decomposition
+MultipleDomains                           2
+TopNodeFactor                             4
+ActivePartFracForNewDomainDecomp          0.01
+
+%% gravitational softening
+GasSoftFactor                             0.01
+SofteningComovingType0                    0.1
+SofteningComovingType1                    0.1
+SofteningComovingType2                    0.1
+SofteningComovingType3                    0.1
+SofteningComovingType4                    0.1
+SofteningComovingType5                    0.1
+SofteningMaxPhysType0                     0.1
+SofteningMaxPhysType1                     0.1
+SofteningMaxPhysType2                     0.1
+SofteningMaxPhysType3                     0.1
+SofteningMaxPhysType4                     0.1
+SofteningMaxPhysType5                     0.1
+SofteningTypeOfPartType0                  0
+SofteningTypeOfPartType1                  0
+SofteningTypeOfPartType2                  0
+SofteningTypeOfPartType3                  0
+SofteningTypeOfPartType4                  0
+SofteningTypeOfPartType5                  0
diff --git a/test.sh b/test.sh
index 81dacc9..7314dc6 100644
--- a/test.sh
+++ b/test.sh
@@ -13,6 +13,7 @@ NUMBER_OF_TASKS=1
 TESTS=""
 ## available 1d test cases
 TESTS+="wave_1d "
+TESTS+="shocktube_1d "
 
 ## loop over all tests
 for TEST in $TESTS
-- 
GitLab