Update SpGEMM_copyCt2C_kernels.cl

Update SpGEMM_copyCt2C_kernels.cl

/* ************************************************************************
* The MIT License (MIT)
* Copyright 2014-2015 weifengliu

*  Permission is hereby granted, free of charge, to any person obtaining a copy
*  of this software and associated documentation files (the "Software"), to deal
*  in the Software without restriction, including without limitation the rights
*  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the Software is
*  furnished to do so, subject to the following conditions:

*  The above copyright notice and this permission notice shall be included in
*  all copies or substantial portions of the Software.

*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
*  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
*  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
*  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
*  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
*  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
*  THE SOFTWARE.
* ************************************************************************ */
//////////////////////////////////////////////////////////////////////////
// < A CUDA/OpenCL General Sparse Matrix-Matrix Multiplication Program >
//
// < See paper:
// Weifeng Liu and Brian Vinter, "An Efficient GPU General Sparse
// Matrix-Matrix Multiplication for Irregular Data," Parallel and
// Distributed Processing Symposium, 2014 IEEE 28th International
// (IPDPS '14), pp.370-381, 19-23 May 2014
// for details. >
//////////////////////////////////////////////////////////////////////////

#pragma OPENCL EXTENSION cl_khr_fp64 : enable

#define TUPLE_QUEUE 6
typedef double   vT;

__kernel
void copyCt2C_Single(__global const int   *d_csrRowPtrC,
                     __global       int   *d_csrColIndC,
                     __global       vT *d_csrValC,
                     __global const int   *d_csrRowPtrCt,
                     __global const int   *d_csrColIndCt,
                     __global const vT *d_csrValCt,
                     __global const int   *d_queue,
                     const          int    size,
                     const          int    d_queue_offset)
{
    int global_id = get_global_id(0);

    bool valid = (global_id < size);

    int row_id = valid ? d_queue[TUPLE_QUEUE * (d_queue_offset + global_id)] : 0;

    int Ct_base_start = valid ? d_queue[TUPLE_QUEUE * (d_queue_offset + global_id) + 1] : 0; //d_csrRowPtrCt[row_id] : 0;
    int C_base_start  = valid ? d_csrRowPtrC[row_id] : 0;

    int colC   = valid ? d_csrColIndCt[Ct_base_start] : 0;
    vT valC = valid ? d_csrValCt[Ct_base_start] : 0.0f;

    if (valid)
    {
        d_csrColIndC[C_base_start] = colC;
        d_csrValC[C_base_start]    = valC;
    }
}

__kernel
void copyCt2C_Loopless(__global const int   *d_csrRowPtrC,
                       __global       int   *d_csrColIndC,
                       __global       vT *d_csrValC,
                       __global const int   *d_csrRowPtrCt,
                       __global const int   *d_csrColIndCt,
                       __global const vT *d_csrValCt,
                       __global const int   *d_queue,
                       const          int    d_queue_offset)
{
    int local_id = get_local_id(0);
    int group_id = get_group_id(0);

    int row_id = d_queue[TUPLE_QUEUE * (d_queue_offset + group_id)];

    int Ct_base_start = d_queue[TUPLE_QUEUE * (d_queue_offset + group_id) + 1] + local_id; //d_csrRowPtrCt[row_id] + local_id;
    int C_base_start  = d_csrRowPtrC[row_id]  + local_id;
    int C_base_stop   = d_csrRowPtrC[row_id + 1];

    if (C_base_start < C_base_stop)
    {
        d_csrColIndC[C_base_start] = d_csrColIndCt[Ct_base_start];
        d_csrValC[C_base_start]    = d_csrValCt[Ct_base_start];
    }
}

__kernel
void copyCt2C_Loop(__global const int   *d_csrRowPtrC,
                   __global       int   *d_csrColIndC,
                   __global       vT *d_csrValC,
                   __global const int   *d_csrRowPtrCt,
                   __global const int   *d_csrColIndCt,
                   __global const vT *d_csrValCt,
                   __global const int   *d_queue,
                   const          int    d_queue_offset)
{
    int local_id = get_local_id(0);
    int group_id = get_group_id(0);
    int local_size = get_local_size(0);

    int row_id = d_queue[TUPLE_QUEUE * (d_queue_offset + group_id)];

    int Ct_base_start = d_queue[TUPLE_QUEUE * (d_queue_offset + group_id) + 1]; //d_csrRowPtrCt[row_id];
    int C_base_start  = d_csrRowPtrC[row_id];
    int C_base_stop   = d_csrRowPtrC[row_id + 1];
    int stride        = C_base_stop - C_base_start;

    bool valid;

    int loop = ceil((float)stride / (float)local_size);

    C_base_start  += local_id;
    Ct_base_start += local_id;

    for (int i = 0; i < loop; i++)
    {
        valid = (C_base_start < C_base_stop);

        if (valid)
        {
            d_csrColIndC[C_base_start] = d_csrColIndCt[Ct_base_start];
            d_csrValC[C_base_start]    = d_csrValCt[Ct_base_start];
        }

        C_base_start += local_size;
        Ct_base_start += local_size;
    }
}


Implement sqrt using the sqrtf(). This enables Clang to replace it
with llvm.sqrt* intrinstics in case looser math flags are used to
build the builtin library.

__builtin_sqrt() is not replaced as intrinsics this way for some reason.
TODO: fix this in Clang.

/* OpenCL built-in library: sqrt()

   Copyright (c) 2011 Erik Schnetter <eschnetter@perimeterinstitute.ca>
                      Perimeter Institute for Theoretical Physics

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.
*/

#include "templates.h"

float _CL_OVERLOADABLE sqrt(float a)
{
  return sqrtf(a);
}

float2 _CL_OVERLOADABLE sqrt(float2 a)
{
  return (float2)(sqrt(a.x), sqrt(a.y));
}

float3 _CL_OVERLOADABLE sqrt(float3 a)
{
  return (float3)(sqrt(a.x), sqrt(a.y), sqrt(a.z));
}

float4 _CL_OVERLOADABLE sqrt(float4 a)
{
  return (float4)(sqrt(a.x), sqrt(a.y), sqrt(a.z), sqrt(a.w));
}

float8 _CL_OVERLOADABLE sqrt(float8 a)
{
  return (float8)(sqrt(a.x), sqrt(a.y), sqrt(a.z), sqrt(a.w),
		  sqrt(a.s4), sqrt(a.s5), sqrt(a.s6), sqrt(a.s7));
}

float16 _CL_OVERLOADABLE sqrt(float16 a)
{
  return (float16)(sqrtf(a.x), sqrtf(a.y), sqrtf(a.z), sqrtf(a.w),
		   sqrtf(a.s4), sqrtf(a.s5), sqrtf(a.s6), sqrtf(a.s7),
		   sqrtf(a.s8), sqrtf(a.s9), sqrtf(a.sa), sqrtf(a.sb),
		   sqrtf(a.sc), sqrtf(a.sd), sqrtf(a.se), sqrtf(a.sf));

}

DEFINE_EXPR_F_F(half_sqrt, sqrt(a))
DEFINE_EXPR_F_F(native_sqrt, sqrt(a))

/* OpenCL built-in library: sqrt()

   Copyright (c) 2011 Erik Schnetter <eschnetter@perimeterinstitute.ca>
                      Perimeter Institute for Theoretical Physics

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.
*/

#include "templates.h"

DEFINE_BUILTIN_V_V(sqrt)

DEFINE_EXPR_F_F(half_sqrt, sqrt(a))
DEFINE_EXPR_F_F(native_sqrt, sqrt(a))


Adding FMAs to CSR-General and some consts and other type correctness.

R"(
#ifdef cl_khr_fp64
    #pragma OPENCL EXTENSION cl_khr_fp64 : enable
#elif defined(cl_amd_fp64)
    #pragma OPENCL EXTENSION cl_amd_fp64 : enable
#else
    #error "Double precision floating point not supported by OpenCL implementation."
#endif

#ifndef INDEX_TYPE
#error INDEX_TYPE undefined!
#endif

#ifndef VALUE_TYPE
#error VALUE_TYPE undefined!
#endif

#ifndef SIZE_TYPE
#error SIZE_TYPE undefined!
#endif

#ifndef WG_SIZE
#error WG_SIZE undefined!
#endif

#ifndef WAVE_SIZE
#error WAVE_SIZE undefined!
#endif

#ifndef SUBWAVE_SIZE
#error SUBWAVE_SIZE undefined!
#endif

#if ( (SUBWAVE_SIZE > WAVE_SIZE) || (SUBWAVE_SIZE != 2 && SUBWAVE_SIZE != 4 && SUBWAVE_SIZE != 8 && SUBWAVE_SIZE != 16 && SUBWAVE_SIZE != 32 && SUBWAVE_SIZE != 64) )
# error SUBWAVE_SIZE is not  a power of two!
#endif

#define EXTENDED_PRECISION

// Knuth's Two-Sum algorithm, which allows us to add together two floating
// point numbers and exactly tranform the answer into a sum and a
// rounding error.
// Inputs: x and y, the two inputs to be aded together.
// In/Out: *sumk_err, which is incremented (by reference) -- holds the
//         error value as a result of the 2sum calculation.
// Returns: The non-corrected sum of inputs x and y.
VALUE_TYPE two_sum( VALUE_TYPE x,
        VALUE_TYPE y,
        VALUE_TYPE * restrict const sumk_err)
{
    const VALUE_TYPE sumk_s = x + y;
#ifdef EXTENDED_PRECISION
    // We use this 2Sum algorithm to perform a compensated summation,
    // which can reduce the cummulative rounding errors in our SpMV summation.
    // Our compensated sumation is based on the SumK algorithm (with K==2) from
    // Ogita, Rump, and Oishi, "Accurate Sum and Dot Product" in
    // SIAM J. on Scientific Computing 26(6) pp 1955-1988, Jun. 2005.

    // 2Sum can be done in 6 FLOPs without a branch. However, calculating
    // double precision is slower than single precision on every existing GPU.
    // As such, replacing 2Sum with Fast2Sum when using DPFP results in slightly
    // better performance. This is especially true on non-workstation GPUs with
    // low DPFP rates. Fast2Sum is faster even though we must ensure that
    // |a| > |b|. Branch divergence is better than the DPFP slowdown.
    // Thus, for DPFP, our compensated summation algorithm is actually described
    // by both Pichat and Neumaier in "Correction d'une somme en arithmetique
    // a virgule flottante" (J. Numerische Mathematik 19(5) pp. 400-406, 1972)
    // and "Rundungsfehleranalyse einiger Verfahren zur Summation endlicher
    // Summen (ZAMM Z. Angewandte Mathematik und Mechanik 54(1) pp. 39-51,
    // 1974), respectively.
    if (fabs(x) < fabs(y))
    {
        const VALUE_TYPE swap = x;
        x = y;
        y = swap;
    }
    (*sumk_err) += (y - (sumk_s - x));
    // Original 6 FLOP 2Sum algorithm.
    //VALUE_TYPE bp = sumk_s - x;
    //(*sumk_err) += ((x - (sumk_s - bp)) + (y - bp));
#endif
    return sumk_s;
}

// Performs (x_vals * x_vec) + y using an FMA.
// Ideally, we would perform an error-free transformation here and return the
// appropriate error. However, the EFT of an FMA is very expensive. As such,
// if we are in EXTENDED_PRECISION mode, this function devolves into two_sum
// with x_vals and x_vec inputs multiplied separately from the compensated add.
VALUE_TYPE two_fma( const VALUE_TYPE x_vals,
        const VALUE_TYPE x_vec,
        VALUE_TYPE y,
        VALUE_TYPE * restrict const sumk_err )
{
#ifdef EXTENDED_PRECISION
    VALUE_TYPE x = x_vals * x_vec;
    const VALUE_TYPE sumk_s = x + y;
    if (fabs(x) < fabs(y))
    {
        const VALUE_TYPE swap = x;
        x = y;
        y = swap;
    }
    (*sumk_err) += (y - (sumk_s - x));
    // 2Sum in the FMA case. Poor performance on low-DPFP GPUs.
    //const VALUE_TYPE bp = fma(-x_vals, x_vec, sumk_s);
    //(*sumk_err) += (fma(x_vals, x_vec, -(sumk_s - bp)) + (y - bp));
    return sumk_s;
#else
    return fma(x_vals, x_vec, y);
#endif
}

// A method of doing the final reduction without having to copy and paste
// it a bunch of times.
// The EXTENDED_PRECISION section is done as part of the PSum2 addition,
// where we take temporary sums and errors for multiple threads and combine
// them together using the same 2Sum method.
// Inputs:  cur_sum: the input from which our sum starts
//          err: the current running cascade error for this final summation
//          partial: the local memory which holds the values to sum
//                  (we eventually use it to pass down temp. err vals as well)
//          lid: local ID of the work item calling this function.
//          thread_lane: The lane within this SUBWAVE for reduction.
//          round: This parallel summation method operates in multiple rounds
//                  to do a parallel reduction. See the blow comment for usage.
VALUE_TYPE sum2_reduce( VALUE_TYPE cur_sum,
        VALUE_TYPE * restrict const err,
        volatile __local VALUE_TYPE * restrict const partial,
        const INDEX_TYPE lid,
        const INDEX_TYPE thread_lane,
        const INDEX_TYPE round)
{
    if (SUBWAVE_SIZE > round)
    {
#ifdef EXTENDED_PRECISION
        const unsigned int partial_dest = lid + round;
        if (thread_lane < round)
            cur_sum  = two_sum(cur_sum, partial[partial_dest], err);
        // We reuse the LDS entries to move the error values down into lower
        // threads. This saves LDS space, allowing higher occupancy, but requires
        // more barriers, which can reduce performance.
        barrier(CLK_LOCAL_MEM_FENCE);
        // Have all of those upper threads pass their temporary errors
        // into a location that the lower threads can read.
        if (thread_lane >= round)
            partial[lid] = *err;
        barrier(CLK_LOCAL_MEM_FENCE);
        if (thread_lane < round) { // Add those errors in.
            *err += partial[partial_dest];
            partial[lid] = cur_sum;
        }
#else
        // This is the more traditional reduction algorithm. It is up to
        // 25% faster (about 10% on average -- potentially worse on devices
        // with low double-precision calculation rates), but can result in
        // numerical inaccuracies, especially in single precision.
        cur_sum += partial[lid + round];
        partial[lid] = cur_sum;
#endif
    }
    return cur_sum;
}

// Uses macro constants:
// WAVE_SIZE  - "warp size", typically 64 (AMD) or 32 (NV)
// WG_SIZE    - workgroup ("block") size, 1D representation assumed
// INDEX_TYPE - typename for the type of integer data read by the kernel,  usually unsigned int
// VALUE_TYPE - typename for the type of floating point data, usually double
// SUBWAVE_SIZE - the length of a "sub-wave", a power of 2, i.e. 1,2,4,...,WAVE_SIZE, assigned to process a single matrix row
__kernel
__attribute__((reqd_work_group_size(WG_SIZE,1,1)))
void csrmv_general (     const INDEX_TYPE num_rows,
                __global const VALUE_TYPE * const alpha,
                         const SIZE_TYPE off_alpha,
                __global const INDEX_TYPE * const row_offset,
                __global const INDEX_TYPE * const col,
                __global const VALUE_TYPE * const val,
                __global const VALUE_TYPE * const x,
                         const SIZE_TYPE off_x,
                __global const VALUE_TYPE * const beta,
                         const SIZE_TYPE off_beta,
                __global       VALUE_TYPE * y,
                         const SIZE_TYPE off_y)
{
    local volatile VALUE_TYPE sdata [WG_SIZE + SUBWAVE_SIZE / 2];

    //const int vectors_per_block = WG_SIZE/SUBWAVE_SIZE;
    const INDEX_TYPE global_id   = get_global_id(0);         // global workitem id
    const INDEX_TYPE local_id    = get_local_id(0);          // local workitem id
    const INDEX_TYPE thread_lane = local_id & (SUBWAVE_SIZE - 1);
    const INDEX_TYPE vector_id   = global_id / SUBWAVE_SIZE; // global vector id
    //const int vector_lane = local_id / SUBWAVE_SIZE;  // vector id within the workgroup
    const INDEX_TYPE num_vectors = get_global_size(0) / SUBWAVE_SIZE;

    const VALUE_TYPE _alpha = alpha[off_alpha];
    const VALUE_TYPE _beta = beta[off_beta];

    for(INDEX_TYPE row = vector_id; row < num_rows; row += num_vectors)
    {
        const INDEX_TYPE row_start = row_offset[row];
        const INDEX_TYPE row_end   = row_offset[row+1];
        VALUE_TYPE sum = 0.;

        VALUE_TYPE sumk_e = 0.;
        // It is about 5% faster to always multiply by alpha, rather than to
        // check whether alpha is 0, 1, or other and do different code paths.
        for(INDEX_TYPE j = row_start + thread_lane; j < row_end; j += SUBWAVE_SIZE)
            sum = two_fma(_alpha * val[j], x[off_x + col[j]], sum, &sumk_e);
        VALUE_TYPE new_error = 0.;
        sum = two_sum(sum, sumk_e, &new_error);

        // Parallel reduction in shared memory.
        sdata[local_id] = sum;

        // This compensated summation reduces cummulative rounding errors,
        // which can become a problem on GPUs because our reduction order is
        // different than what would be used on a CPU.
        // It is based on the PSumK algorithm (with K==2) from
        // Yamanaka, Ogita, Rump, and Oishi, "A Parallel Algorithm of
        // Accurate Dot Product," in the Journal of Parallel Computing,
        // 34(6-8), pp. 392-410, Jul. 2008.
        #pragma unroll
        for (int i = (WG_SIZE >> 1); i > 0; i >>= 1)
        {
            barrier( CLK_LOCAL_MEM_FENCE );
            sum = sum2_reduce(sum, &new_error, sdata, local_id, thread_lane, i);
        }

        if (thread_lane == 0)
        {
            if (_beta == 0)
                y[off_y + row] = sum + new_error;
            else
            {
                sum = two_fma(_beta, y[off_y + row], sum, &new_error);
                y[off_y + row] = sum + new_error;
            }
        }
    }
}
)"

R"(
#ifdef cl_khr_fp64
    #pragma OPENCL EXTENSION cl_khr_fp64 : enable
#elif defined(cl_amd_fp64)
    #pragma OPENCL EXTENSION cl_amd_fp64 : enable
#else
    #error "Double precision floating point not supported by OpenCL implementation."
#endif

#ifndef INDEX_TYPE
#error INDEX_TYPE undefined!
#endif

#ifndef VALUE_TYPE
#error VALUE_TYPE undefined!
#endif

#ifndef SIZE_TYPE
#error SIZE_TYPE undefined!
#endif

#ifndef WG_SIZE
#error WG_SIZE undefined!
#endif

#ifndef WAVE_SIZE
#error WAVE_SIZE undefined!
#endif

#ifndef SUBWAVE_SIZE
#error SUBWAVE_SIZE undefined!
#endif

#if ( (SUBWAVE_SIZE > WAVE_SIZE) || (SUBWAVE_SIZE != 2 && SUBWAVE_SIZE != 4 && SUBWAVE_SIZE != 8 && SUBWAVE_SIZE != 16 && SUBWAVE_SIZE != 32 && SUBWAVE_SIZE != 64) )
# error SUBWAVE_SIZE is not  a power of two!
#endif

#define EXTENDED_PRECISION

// Knuth's Two-Sum algorithm, which allows us to add together two floating
// point numbers and exactly tranform the answer into a sum and a
// rounding error.
// Inputs: x and y, the two inputs to be aded together.
// In/Out: *sumk_err, which is incremented (by reference) -- holds the
//         error value as a result of the 2sum calculation.
// Returns: The non-corrected sum of inputs x and y.
VALUE_TYPE two_sum( VALUE_TYPE x,
        VALUE_TYPE y,
        VALUE_TYPE *sumk_err)
{
    VALUE_TYPE sumk_s = x + y;
#ifdef EXTENDED_PRECISION
    // We use this 2Sum algorithm to perform a compensated summation,
    // which can reduce the cummulative rounding errors in our SpMV summation.
    // Our compensated sumation is based on the SumK algorithm (with K==2) from
    // Ogita, Rump, and Oishi, "Accurate Sum and Dot Product" in
    // SIAM J. on Scientific Computing 26(6) pp 1955-1988, Jun. 2005.

    // 2Sum can be done in 6 FLOPs without a branch. However, calculating
    // double precision is slower than single precision on every existing GPU.
    // As such, replacing 2Sum with Fast2Sum when using DPFP results in slightly
    // better performance. This is especially true on non-workstation GPUs with
    // low DPFP rates. Fast2Sum is faster even though we must ensure that
    // |a| > |b|. Branch divergence is better than the DPFP slowdown.
    // Thus, for DPFP, our compensated summation algorithm is actually described
    // by both Pichat and Neumaier in "Correction d'une somme en arithmetique
    // a virgule flottante" (J. Numerische Mathematik 19(5) pp. 400-406, 1972)
    // and "Rundungsfehleranalyse einiger Verfahren zur Summation endlicher
    // Summen (ZAMM Z. Angewandte Mathematik und Mechanik 54(1) pp. 39-51,
    // 1974), respectively.
    VALUE_TYPE swap;
    if (fabs(x) < fabs(y))
    {
        swap = x;
        x = y;
        y = swap;
    }
    (*sumk_err) += (y - (sumk_s - x));
    // Original 6 FLOP 2Sum algorithm.
    //VALUE_TYPE bp = sumk_s - x;
    //(*sumk_err) += ((x - (sumk_s - bp)) + (y - bp));
#endif
    return sumk_s;
}

// A method of doing the final reduction without having to copy and paste
// it a bunch of times.
// The EXTENDED_PRECISION section is done as part of the PSum2 addition,
// where we take temporary sums and errors for multiple threads and combine
// them together using the same 2Sum method.
// Inputs:  cur_sum: the input from which our sum starts
//          err: the current running cascade error for this final summation
//          partial: the local memory which holds the values to sum
//                  (we eventually use it to pass down temp. err vals as well)
//          lid: local ID of the work item calling this function.
//          thread_lane: The lane within this SUBWAVE for reduction.
//          round: This parallel summation method operates in multiple rounds
//                  to do a parallel reduction. See the blow comment for usage.
VALUE_TYPE sum2_reduce( VALUE_TYPE cur_sum,
        VALUE_TYPE *err,
        volatile __local VALUE_TYPE *partial,
        size_t lid,
        int thread_lane,
        int round)
{
    if (SUBWAVE_SIZE > round)
    {
#ifdef EXTENDED_PRECISION
        if (thread_lane < round)
            cur_sum  = two_sum(cur_sum, partial[lid + round], err);

        // We reuse the LDS entries to move the error values down into lower
        // threads. This saves LDS space, allowing higher occupancy, but requires
        // more barriers, which can reduce performance.
        barrier(CLK_LOCAL_MEM_FENCE);
        // Have all of those upper threads pass their temporary errors
        // into a location that the lower threads can read.
        if (thread_lane >= round)
            partial[lid] = *err;
        barrier(CLK_LOCAL_MEM_FENCE);
        if (thread_lane < round) { // Add those errors in.
            *err += partial[lid + round];
            partial[lid] = cur_sum;
        }
#else
        // This is the more traditional reduction algorithm. It is up to
        // 25% faster (about 10% on average -- potentially worse on devices
        // with low double-precision calculation rates), but can result in
        // numerical inaccuracies, especially in single precision.
        cur_sum += partial[lid + round];
        partial[lid] = cur_sum;
#endif
    }
    return cur_sum;
}

// Uses macro constants:
// WAVE_SIZE  - "warp size", typically 64 (AMD) or 32 (NV)
// WG_SIZE    - workgroup ("block") size, 1D representation assumed
// INDEX_TYPE - typename for the type of integer data read by the kernel,  usually unsigned int
// VALUE_TYPE - typename for the type of floating point data, usually double
// SUBWAVE_SIZE - the length of a "sub-wave", a power of 2, i.e. 1,2,4,...,WAVE_SIZE, assigned to process a single matrix row
__kernel
__attribute__((reqd_work_group_size(WG_SIZE,1,1)))
void csrmv_general (     const INDEX_TYPE num_rows,
                __global const VALUE_TYPE * const alpha,
                         const SIZE_TYPE off_alpha,
                __global const INDEX_TYPE * const row_offset,
                __global const INDEX_TYPE * const col,
                __global const VALUE_TYPE * const val,
                __global const VALUE_TYPE * const x,
                         const SIZE_TYPE off_x,
                __global const VALUE_TYPE * const beta,
                         const SIZE_TYPE off_beta,
                __global       VALUE_TYPE * y,
                         const SIZE_TYPE off_y)
{
    local volatile VALUE_TYPE sdata [WG_SIZE + SUBWAVE_SIZE / 2];

    //const int vectors_per_block = WG_SIZE/SUBWAVE_SIZE;
    const int global_id   = get_global_id(0);         // global workitem id
    const int local_id    = get_local_id(0);          // local workitem id
    const int thread_lane = local_id & (SUBWAVE_SIZE - 1);
    const int vector_id   = global_id / SUBWAVE_SIZE; // global vector id
    //const int vector_lane = local_id / SUBWAVE_SIZE;  // vector id within the workgroup
    const int num_vectors = get_global_size(0) / SUBWAVE_SIZE;

    const VALUE_TYPE _alpha = alpha[off_alpha];
    const VALUE_TYPE _beta = beta[off_beta];

    for(INDEX_TYPE row = vector_id; row < num_rows; row += num_vectors)
    {
        const int row_start = row_offset[row];
        const int row_end   = row_offset[row+1];
        VALUE_TYPE sum = 0.;

        VALUE_TYPE sumk_e = 0.;
        // It is about 5% faster to always multiply by alpha, rather than to
        // check whether alpha is 0, 1, or other and do different code paths.
        for(int j = row_start + thread_lane; j < row_end; j += SUBWAVE_SIZE)
            sum = two_sum(sum, (_alpha * val[j] * x[off_x + col[j]]), &sumk_e);
        VALUE_TYPE new_error = 0.;
        sum = two_sum(sum, sumk_e, &new_error);

        // Parallel reduction in shared memory.
        sdata[local_id] = sum;

        // This compensated summation reduces cummulative rounding errors,
        // which can become a problem on GPUs because our reduction order is
        // different than what would be used on a CPU.
        // It is based on the PSumK algorithm (with K==2) from
        // Yamanaka, Ogita, Rump, and Oishi, "A Parallel Algorithm of
        // Accurate Dot Product," in the Journal of Parallel Computing,
        // 34(6-8), pp. 392-410, Jul. 2008.
        for (int i = (WG_SIZE >> 1); i > 0; i >>= 1)
        {
            barrier( CLK_LOCAL_MEM_FENCE );
            sum = sum2_reduce(sum, &new_error, sdata, local_id, thread_lane, i);
        }

        if (thread_lane == 0)
        {
            if (_beta == 0)
                y[off_y + row] = sum + new_error;
            else
            {
                sum = two_sum(sum, _beta * y[off_y + row], &new_error);
                y[off_y + row] = sum + new_error;
            }
        }
    }
}
)"


messing with ppm more

messing with ppm more

<? if not solver.usePLM then return end ?>

real min3(real x, real y, real z) {
	return min(min(x, y), z);
}

real minmod(real a, real b) {
	if (a * b < 0) return 0;
	return fabs(a) < fabs(b) ? a : b;
}

kernel void calcLR(
	global <?=eqn.consLR_t?>* ULRBuf,
	const global <?=eqn.cons_t?>* UBuf,
	real dt
) {
	SETBOUNDS(1,1);
	const global <?=eqn.cons_t?>* U = UBuf + index;
	real3 x = cell_x(i);

	//TODO skip this lr stuff if we're doing piecewise-constant
	//...and just use the original buffers
	<? for side=0,solver.dim-1 do ?>{
		const int side = <?=side?>;

		//cell-centered index for a particular side...
		int indexForSide = side + dim * index;
		global <?=eqn.consLR_t?>* ULR = ULRBuf + indexForSide;

		//piecewise-linear

<? if solver.usePLM == 'plm-cons' then ?>
/*
#1: slope based on conservative variables
----------------------------------------

phi(x) = minmod(x) = clamp(r, 0, 1)

dUL = U_i - U_i-1
dUR = U_i+1 - U_i
sigma = phi( dUL / dUR ) * dUR

dF = F(U_i+1/2) - F(U_i-1/2)

dx = (x_i+1/2 - x_i-1/2)
U_i-1/2,R = U_i - 1/2 (sigma - dt/dx dF)
U_i+1/2,L = U_i + 1/2 (sigma + dt/dx dF)

Hydrodynamics II slope-limiters (4.4.2) and MUSCL-Hancock (6.6)
and https://en.wikipedia.org/wiki/MUSCL_scheme
works for euler Sod 1D with oscillations
works for euler Sod 2D
works for mhd Brio-Wu 1D with oscillations
works for maxwell with oscillations
works for adm1d_v1 freeflow with oscillations (fails for mirror)
*/

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;
		<?=eqn.cons_t?> dUL, dUR;
		for (int j = 0; j < numIntStates; ++j) {
			dUL.ptr[j] = U->ptr[j] - UL->ptr[j];
			dUR.ptr[j] = UR->ptr[j] - U->ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dUL.ptr[j] = dUR.ptr[j] = 0;
		}

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		<?=eqn.cons_t?> UHalfL, UHalfR;
		for (int j = 0; j < numIntStates; ++j) {
			//Hydrodynamics II slope-limiters (4.4.2) and MUSCL-Hancock (6.6)
			//https://en.wikipedia.org/wiki/MUSCL_scheme

/*
minmod(r) = max(0, min(r, 1))
superbee(r) = max(0, max(
					min(1, 2*r),
					min(2, r)
				))

minmod2(a,b) = a * b < 0
		? 0
		: (|a| < |b| ? a : b)

a = u[i+1] - u[i]
b = u[i] - u[i-1]
r = a / b = (u[i+1] - u[i]) / (u[i] - u[i-1])

minmod2(a,b) = minmod2(rb, b) = |b| minmod2(r sgn b, sgn b)
TODO oops I forgot r can be negative, and so this sgn b < 0 case is bad ...
for sgn b < 0:
	minmod2(a,b) = |b| minmod2(-r, -1) = 0
for sgn b >= 0:
	minmod2(a,b) = |b| minmod2(r, 1) =
		|b| * (r * 1 < 0 ? 0
						: (|r| < 1 ? r : 1))
	minmod2(a,b) = |b| sgn r * max(0, min(|r|, 1))
	minmod2(a,b) = |b| sgn r * minmod(|r|)
*/
#if 1
			real r = dUR.ptr[j] == 0 ? 0 : (dUL.ptr[j] / dUR.ptr[j]);
			real phi = slopeLimiter(r);	//works good with minmod, bad with superbee
			real sigma = phi * dUR.ptr[j];
#else
			real sigma = minmod(minmod(fabs(dUL.ptr[j]),
                                    fabs(dUR.ptr[j])),
                             fabs(.5 * (dUL.ptr[j] - dUR.ptr[j]))
			) * sign(dUL.ptr[j] - dUR.ptr[j]);
#endif
			//q^n_i-1/2,R = q^n_i - 1/2 dx sigma	(Hydrodynamics II 6.58)
			UHalfL.ptr[j] = U->ptr[j] - .5 * sigma;

			//q^n_i+1/2,L = q^n_i + 1/2 dx sigma	(Hydrodynamics II 6.59)
			UHalfR.ptr[j] = U->ptr[j] + .5 * sigma;
		}
		for (int j = numIntStates; j < numStates; ++j) {
			UHalfL.ptr[j] = UHalfR.ptr[j] = U->ptr[j];
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn.cons_t?> FHalfL = fluxFromCons_<?=side?>(UHalfL, xIntL);
		<?=eqn.cons_t?> FHalfR = fluxFromCons_<?=side?>(UHalfR, xIntR);

		for (int j = 0; j < numIntStates; ++j) {
			real dF = FHalfR.ptr[j] - FHalfL.ptr[j];

			//U-cell-L = q^n+1/2_i-1/2,R (Hydrodynamics II 6.62)
			// = q^n_i-1/2,R + 1/2 dt/dx (f_k(q^n_i+1/2,L) - f_k(q_i-1/2,R))
			ULR->L.ptr[j] = UHalfL.ptr[j] + .5 * dt_dx * dF;

			//U-cell R = q^n+1/2_i+1/2,L (Hydrodynamics II 6.63)
			// = q^n_i+1/2,L + 1/2 dt/dx (f_k(q^n_i+1/2,L) - f_k(q_i-1/2,R))
			ULR->R.ptr[j] = UHalfR.ptr[j] + .5 * dt_dx * dF;
		}
		for (int j = numIntStates; j < numStates; ++j) {
			ULR->L.ptr[j] = ULR->R.ptr[j] = U->ptr[j];
		}

<? elseif solver.usePLM == 'plm-eig' then ?>
/*
#2: next step, project into eigenspace
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

dULe = evL( U_i - U_i-1 )
dURe = evL( U_i+1 - U_i )
dUCe = evL( (U_i+1 - U_i-1)/2 )
dUMe_j = step(dULe_j * dURe_j) * sign(dUCe_j) * 2 * min(|dULe_j|, |dURe_j|, |dUCe_j|)

aL = step(wave_j) dUMe_j (1 - wave_j dt/dx)
aR = step(-wave_j) dUMe_j (1 + wave_j dt/dx)

U_i-1/2,R = U_i - evR 1/2 aR
U_i+1/2,L = U_i + evR 1/2 aL

based on https://arxiv.org/pdf/0804.0402v1.pdf
and Trangenstein "Numeric Simulation of Hyperbolic Conservation Laws" section 6.2.5
except I'm projecting the differences in conservative values instead of primitive values.
This also needs modular slope limiter support.
works for Euler 1D Sod
euler 2D Sod this gets strange behavior and slowly diverges.
works for MHD Brio-Wu
works for maxwell
works for adm1d_v1
*/

		//1) calc delta q's ... l r c (eqn 36)
		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;
		<?=eqn.cons_t?> dUL, dUR, dUC;
		for (int j = 0; j < numIntStates; ++j) {
			dUL.ptr[j] = U->ptr[j] - UL->ptr[j];
			dUR.ptr[j] = UR->ptr[j] - U->ptr[j];
			dUC.ptr[j] = .5 * (UR->ptr[j] - UL->ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dUL.ptr[j] = dUR.ptr[j] = dUC.ptr[j] = 0;
		}

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		real dULEig[numWaves], dUREig[numWaves], dUCEig[numWaves];
		eigen_leftTransform_<?=side?>___(dULEig, &eig, dUL.ptr, xIntL);
		eigen_leftTransform_<?=side?>___(dUREig, &eig, dUR.ptr, xIntR);
		eigen_leftTransform_<?=side?>___(dUCEig, &eig, dUC.ptr, x);

		//MUSCL slope of characteristic variables
		//TODO make this based on the choice of slope limiter
		real dUMEig[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			//dUMEig[j] = minmod(minmod(2. * dULEig[j], 2. * dUREig[j]), dUCEig[j]);
			dUMEig[j] = dULEig[j] * dUREig[j] < 0 ? 0 : (
				(dUCEig[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dULEig[j]),
					fabs(dUREig[j]),
					fabs(dUCEig[j]))
			);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

		// slopes in characteristic space
		real aL[numWaves], aR[numWaves];
		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : dUMEig[j] * .5 * (1. - wave_j * dt_dx);
			aR[j] = wave_j > 0 ? 0 : dUMEig[j] * .5 * (1. + wave_j * dt_dx);
		}<? end ?>

		//convert back to conservation variable space
		<?=eqn.cons_t?> sL, sR;
		eigen_rightTransform_<?=side?>___(sL.ptr, &eig, aL, x);
		eigen_rightTransform_<?=side?>___(sR.ptr, &eig, aR, x);

		for (int j = 0; j < numIntStates; ++j) {
			ULR->L.ptr[j] = U->ptr[j] - sR.ptr[j];
			ULR->R.ptr[j] = U->ptr[j] + sL.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			ULR->L.ptr[j] = ULR->R.ptr[j] = U->ptr[j];
		}

<? elseif solver.usePLM == 'plm-eig-prim' or solver.usePLM == 'plm-eig-prim-ref' then ?>
/*
#3a: next step, convert to primitives
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

W_i = W(U_i)

dWLe = evL dU/dW ( W_i - W_i-1 )
dWRe = evL dU/dW ( W_i+1 - W_i )
dWCe = evL dU/dW ( (W_i+1 - W_i-1)/2 )
dWMe_j = step(dWL_j * dWRe_j) * sign(dWCe_j) * 2 * min(|dWLe_j|, |dWRe_j|, |dWCe_j|)

aL = step(wave_j) dWMe_j (1 - wave_j dt/dx)
aR = step(-wave_j) dWMe_j (1 + wave_j dt/dx)

U_i-1/2,R = U(W_i - 1/2 dW/dU evR aR)
U_i+1/2,L = U(W_i + 1/2 dW/dU evR aL)

#3b: next step, subtract out reference state
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

W_i = W(U_i)

dWLe = evL dU/dW ( W_i - W_i-1 )
dWRe = evL dU/dW ( W_i+1 - W_i )
dWCe = evL dU/dW ( (W_i+1 - W_i-1)/2 )
dWMe_j = step(dWL_j * dWRe_j) * sign(dWCe_j) * 2 * min(|dWLe_j|, |dWRe_j|, |dWCe_j|)

aL = step(wave_j) dWMe_j dt/dx (max(wave) - wave_j)
aR = step(-wave_j) dWMe_j dt/dx (wave_j - min(wave))

U_i-1/2,R = U( W_i - 1/2 dW/dU evR (aR + (1 + dt/dx min(wave)) dWMe) )
U_i+1/2,L = U( W_i + 1/2 dW/dU evR (aL + (1 - dt/dx max(wave)) dWMe) )

based on Trangenstein, Athena, etc, except working on primitives like it says to
*/

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		//1) calc delta q's ... l r c (eqn 36)
		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);
		<?=eqn.prim_t?> dWL, dWR, dWC;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWC.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWC.ptr[j] = 0.;
		}

		//calc eigen values and vectors at cell center
		//TODO calculate the eigenstate wrt W instead of U - to save some computations
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		//apply dU/dW before applying left/right eigenvectors so the eigenvectors are of the flux wrt primitives
		//RW = dW/dU RU, LW = LU dU/dW
		<?=eqn.cons_t?> tmp;

		apply_dU_dW(&tmp, &W, &dWL, xIntL); real dWLEig[numWaves]; eigen_leftTransform_<?=side?>___(dWLEig, &eig, tmp.ptr, xIntL);
		apply_dU_dW(&tmp, &W, &dWR, xIntR); real dWREig[numWaves]; eigen_leftTransform_<?=side?>___(dWREig, &eig, tmp.ptr, xIntR);
		apply_dU_dW(&tmp, &W, &dWC, x); real dWCEig[numWaves]; eigen_leftTransform_<?=side?>___(dWCEig, &eig, tmp.ptr, x);

		//MUSCL slope of characteristic variables
		real dWMEig[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			dWMEig[j] = dWLEig[j] * dWREig[j] < 0 ? 0 : (
				(dWCEig[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWLEig[j]),
					fabs(dWREig[j]),
					fabs(dWCEig[j])
				)
			);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

<? 	if solver.usePLM == 'plm-eig-prim' then ?>
		//without reference state

		// calculate left and right slopes in characteristic space
 		real aL[numWaves], aR[numWaves];
 		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : dWMEig[j] * .5 * (1. - wave_j * dt_dx);
			aR[j] = wave_j > 0 ? 0 : dWMEig[j] * .5 * (1. + wave_j * dt_dx);
		}<? end ?>

		// transform slopes back to conserved variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aL, xIntL); <?=eqn.prim_t?> sL; apply_dW_dU(&sL, &W, &tmp, xIntL);
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aR, xIntR); <?=eqn.prim_t?> sR; apply_dW_dU(&sR, &W, &tmp, xIntR);

		// linearly extrapolate the slopes forward and backward from the cell center
 		<?=eqn.prim_t?> W2L, W2R;
 		for (int j = 0; j < numIntStates; ++j) {
			W2L.ptr[j] = W.ptr[j] - sR.ptr[j];
			W2R.ptr[j] = W.ptr[j] + sL.ptr[j];
 		}
		for (int j = numIntStates; j < numStates; ++j) {
			W2L.ptr[j] = W2R.ptr[j] = W.ptr[j];
		}
		ULR->L = consFromPrim(W2L, xIntL);
		ULR->R = consFromPrim(W2R, xIntR);

<?	elseif solver.usePLM == 'plm-eig-prim-ref' then ?>
		//with reference state

		//min and max waves
		//TODO use calcCellMinMaxEigenvalues ... except based on eigen_t
		// so something like calcEigenMinMaxWaves ...
		real waveMin = min(0., <?=eqn:eigenWaveCode(side, '&eig', 'x', 0)?>);
		real waveMax = max(0., <?=eqn:eigenWaveCode(side, '&eig', 'x', eqn.numWaves-1)?>);

		//limited slope in primitive variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, dWMEig, x);
		<?=eqn.prim_t?> dWM;
		apply_dW_dU(&dWM, &W, &tmp, x);

		//left and right reference states
		<?=eqn.prim_t?> WLRef, WRRef;
		for (int j = 0; j < numIntStates; ++j) {
			WLRef.ptr[j] = W.ptr[j] + .5 * (1. - dt_dx * waveMax) * dWM.ptr[j];
			WRRef.ptr[j] = W.ptr[j] - .5 * (1. + dt_dx * waveMin) * dWM.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			WLRef.ptr[j] = WRRef.ptr[j] = 0;
		}

		// calculate left and right slopes in characteristic space
		real aL[numWaves], aR[numWaves];
		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : (dWMEig[j] * dt_dx * (waveMax - wave_j));
			aR[j] = wave_j > 0 ? 0 : (dWMEig[j] * dt_dx * (waveMin - wave_j));
		}<? end ?>

		// transform slopes back to conserved variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aL, xIntL); <?=eqn.prim_t?> sL; apply_dW_dU(&sL, &W, &tmp, xIntL);
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aR, xIntR); <?=eqn.prim_t?> sR; apply_dW_dU(&sR, &W, &tmp, xIntR);

		// linearly extrapolate the slopes forward and backward from the cell center
		<?=eqn.prim_t?> W2L, W2R;
		for (int j = 0; j < numIntStates; ++j) {
			W2R.ptr[j] = WRRef.ptr[j] + .5 * sR.ptr[j];
			W2L.ptr[j] = WLRef.ptr[j] + .5 * sL.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			W2R.ptr[j] = W2L.ptr[j] = W.ptr[j];
		}
		//TODO fix the x's
		ULR->L = consFromPrim(W2R, xIntR);
		ULR->R = consFromPrim(W2L, xIntL);

<? 	end	-- solver.usePLM
elseif solver.usePLM == 'plm-athena' then
?>
		//based on Athena

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		//1) calc delta q's ... l r c (eqn 36)
		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);

		<?=eqn.prim_t?> dWL, dWR, dWC, dWG;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWC.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
			dWG.ptr[j] = (dWL.ptr[j] * dWR.ptr[j]) <= 0. ? 0. : (
				2. * dWL.ptr[j] * dWR.ptr[j] / (dWL.ptr[j] + dWR.ptr[j])
			);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWC.ptr[j] = dWG.ptr[j] = 0.;
		}

		real dal[numWaves], dar[numWaves], dac[numWaves], dag[numWaves];
		eigen_leftTransform_<?=side?>___(dal, &eig, dWL.ptr, xIntL);
		eigen_leftTransform_<?=side?>___(dar, &eig, dWR.ptr, xIntR);
		eigen_leftTransform_<?=side?>___(dac, &eig, dWC.ptr, x);
		eigen_leftTransform_<?=side?>___(dag, &eig, dWG.ptr, x);

		real da[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			da[j] = 0;
			if (dal[j] * dar[j] > 0) {
				real lim_slope1 = min(fabs(dal[j]), fabs(dar[j]));
				real lim_slope2 = min(fabs(dac[j]), fabs(dag[j]));
				da[j] = sign(dac[j]) * min(2. * lim_slope1, lim_slope2);
			}
		}

		<?=eqn.prim_t?> dWm;
		eigen_rightTransform_<?=side?>___(dWm.ptr, &eig, da, x);

		<?=eqn.prim_t?> Wlv, Wrv;
		for (int j = 0; j < numIntStates; ++j) {
			Wlv.ptr[j] = W.ptr[j] - .5 * dWm.ptr[j];
			Wrv.ptr[j] = W.ptr[j] + .5 * dWm.ptr[j];
			real C = Wrv.ptr[j] + Wlv.ptr[j];

			Wlv.ptr[j] = max(min(W.ptr[j], WL.ptr[j]), Wlv.ptr[j]);
			Wlv.ptr[j] = min(max(W.ptr[j], WL.ptr[j]), Wlv.ptr[j]);
			Wrv.ptr[j] = C - Wlv.ptr[j];

			Wrv.ptr[j] = max(min(W.ptr[j],WR.ptr[j]),Wrv.ptr[j]);
			Wrv.ptr[j] = min(max(W.ptr[j],WR.ptr[j]),Wrv.ptr[j]);
			Wlv.ptr[j] = C - Wrv.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			Wlv.ptr[j] = W.ptr[j];
			Wrv.ptr[j] = W.ptr[j];
		}

		ULR->L = consFromPrim(Wlv, xIntL);
		ULR->R = consFromPrim(Wrv, xIntR);

<? elseif solver.usePLM == 'ppm-experimental' then ?>
//here's my attempt at Trangenstein section 5.12 PPM
//with help from Zingale's ppm code

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		//1984 Collela & Woodward eqn 1.7
		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);
		<?=eqn.prim_t?> WR2 = primFromCons(*UR, xR);
		<?=eqn.prim_t?> dWL, dWR, dWR2, dWCL, dWCR;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWR2.ptr[j] = WR2.ptr[j] - WR.ptr[j];
			dWCL.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
			dWCR.ptr[j] = .5 * (WR2.ptr[j] - W.ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWR2.ptr[j] = dWCL.ptr[j] = dWCR.ptr[j] = 0.;
		}

		//slope limiter
		//1984 Collela & Woodward eqn 1.8
		real dWML[numStates];
		for (int j = 0; j < numStates; ++j) {
			dWML[j] = dWL.ptr[j] * dWR.ptr[j] < 0 ? 0 : (
				(dWCL.ptr[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWL.ptr[j]),
					fabs(dWR.ptr[j]),
					fabs(dWCL.ptr[j])
				)
			);
		}
		real dWMR[numStates];
		for (int j = 0; j < numStates; ++j) {
			dWMR[j] = dWR.ptr[j] * dWR2.ptr[j] < 0 ? 0 : (
				(dWCR.ptr[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWR.ptr[j]),
					fabs(dWR2.ptr[j]),
					fabs(dWCR.ptr[j])
				)
			);
		}

		//1984 Collela & Woodward eqn 1.6
		<?=eqn.prim_t?> Wp, Wm;
		for (int j = 0; j < numStates; ++j) {
			Wp.ptr[j] = Wm.ptr[j] = .5 * dWR.ptr[j] - 1./6. * (dWMR[j] - dWML[j]);
		}

		//1984 Collela & Woodward eqn 1.10
		for (int j = 0; j < numStates; ++j) {
			real dWp = Wp.ptr[j] - W.ptr[j];
			real dWm = W.ptr[j] - Wm.ptr[j];
			real dWpm = Wp.ptr[j] - Wm.ptr[j];
			real avgWpm = .5 * (Wp.ptr[j] + Wm.ptr[j]);
			if (dWp * dWm <= 0) {
				Wp.ptr[j] = Wm.ptr[j] = W.ptr[j];
			} else if (dWpm * (W.ptr[j] - avgWpm) > dWpm * dWpm / 6.) {
				Wm.ptr[j] = 3. * W.ptr[j] - 2. * Wp.ptr[j];
			} else if (-dWpm * dWpm / 6. > dWpm * (W.ptr[j] - avgWpm)) {
				Wp.ptr[j] = 3. * W.ptr[j] - 2. * Wm.ptr[j];
			}
		}

		for (int j = 0; j < numStates; ++j) {
			Wm.ptr[j] = .5 * (W.ptr[j] +  Wm.ptr[j]);
			Wp.ptr[j] = .5 * (W.ptr[j] +  Wp.ptr[j]);
		}

		//1984 Collela & Woodward eqn 1.5
		real W6[numStates];
		for (int j = 0; j < numStates; ++j) {
			W6[j] = 6. * W.ptr[j] - 3. * (Wp.ptr[j] + Wm.ptr[j]);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);
		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

		real eval[numWaves];
<? for j=0,eqn.numWaves-1 do
?>		eval[<?=j?>] = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
<? end
?>

		<?=eqn.prim_t?> Ip, Im;
		for (int j = 0; j < numWaves; ++j) {
			real sigma = fabs(eval[j]) * dt_dx;
			if (eval[j] >= 0.) {
				Ip.ptr[j] = Wp.ptr[j] - .5 * sigma * (Wp.ptr[j] - Wm.ptr[j] - (1. - 2./3.*sigma) * W6[j]);
			} else {
				Ip.ptr[j] = W.ptr[j];
			}
			if (eval[j] <= 0.) {
				Im.ptr[j] = Wm.ptr[j] + .5 * sigma * (Wp.ptr[j] - Wm.ptr[j] + (1. - 2./3.*sigma) * W6[j]);
			} else {
				Im.ptr[j] = W.ptr[j];
			}
		}

		real waveMin = <?=eqn:eigenWaveCode(side, '&eig', 'x', 0)?>;
		real waveMax = <?=eqn:eigenWaveCode(side, '&eig', 'x', eqn.numWaves-1)?>;
		<?=eqn.prim_t?> Wref_xp = waveMax >= 0 ? Ip : W;
		<?=eqn.prim_t?> Wref_xm = waveMin <= 0 ? Im : W;

		<?=eqn.prim_t?> Wref_xm_minus_Im, Wref_xp_minus_Ip;
		for (int j = 0; j < numWaves; ++j) {
			Wref_xm_minus_Im.ptr[j] = Wref_xm.ptr[j] - Im.ptr[j];
			Wref_xp_minus_Ip.ptr[j] = Wref_xp.ptr[j] - Ip.ptr[j];
		}

		real beta_xm[numWaves];
		real beta_xp[numWaves];
		eigen_leftTransform_<?=side?>___(beta_xm, &eig, Wref_xm_minus_Im.ptr, x);
		eigen_leftTransform_<?=side?>___(beta_xp, &eig, Wref_xp_minus_Ip.ptr, x);
		for (int j = 0; j < numWaves; ++j) {
			beta_xp[j] = max(0., beta_xp[j]);
			beta_xm[j] = min(0., beta_xm[j]);
		}

		<?=eqn.prim_t?> WresL, WresR;
		eigen_rightTransform_<?=side?>___(WresL.ptr, &eig, beta_xp, x);
		eigen_rightTransform_<?=side?>___(WresR.ptr, &eig, beta_xm, x);
		for (int j = 0; j < numStates; ++j) {
			WresL.ptr[j] = Wref_xp.ptr[j] - WresL.ptr[j];
			WresR.ptr[j] = Wref_xm.ptr[j] - WresR.ptr[j];
		}

		ULR->L = consFromPrim(WresL, x);
		ULR->R = consFromPrim(WresR, x);

<? end ?>

	}<? end ?>
}

<? if not solver.usePLM then return end ?>

real min3(real x, real y, real z) {
	return min(min(x, y), z);
}

real minmod(real a, real b) {
	if (a * b < 0) return 0;
	return fabs(a) < fabs(b) ? a : b;
}

kernel void calcLR(
	global <?=eqn.consLR_t?>* ULRBuf,
	const global <?=eqn.cons_t?>* UBuf,
	real dt
) {
	SETBOUNDS(1,1);
	const global <?=eqn.cons_t?>* U = UBuf + index;
	real3 x = cell_x(i);

	//TODO skip this lr stuff if we're doing piecewise-constant
	//...and just use the original buffers
	<? for side=0,solver.dim-1 do ?>{
		const int side = <?=side?>;

		//cell-centered index for a particular side...
		int indexForSide = side + dim * index;
		global <?=eqn.consLR_t?>* ULR = ULRBuf + indexForSide;

		//piecewise-linear

<? if solver.usePLM == 'plm-cons' then ?>
/*
#1: slope based on conservative variables
----------------------------------------

phi(x) = minmod(x) = clamp(r, 0, 1)

dUL = U_i - U_i-1
dUR = U_i+1 - U_i
sigma = phi( dUL / dUR ) * dUR

dF = F(U_i+1/2) - F(U_i-1/2)

dx = (x_i+1/2 - x_i-1/2)
U_i-1/2,R = U_i - 1/2 (sigma - dt/dx dF)
U_i+1/2,L = U_i + 1/2 (sigma + dt/dx dF)

Hydrodynamics II slope-limiters (4.4.2) and MUSCL-Hancock (6.6)
and https://en.wikipedia.org/wiki/MUSCL_scheme
works for euler Sod 1D with oscillations
works for euler Sod 2D
works for mhd Brio-Wu 1D with oscillations
works for maxwell with oscillations
works for adm1d_v1 freeflow with oscillations (fails for mirror)
*/

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;
		<?=eqn.cons_t?> dUL, dUR;
		for (int j = 0; j < numIntStates; ++j) {
			dUL.ptr[j] = U->ptr[j] - UL->ptr[j];
			dUR.ptr[j] = UR->ptr[j] - U->ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dUL.ptr[j] = dUR.ptr[j] = 0;
		}

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		<?=eqn.cons_t?> UHalfL, UHalfR;
		for (int j = 0; j < numIntStates; ++j) {
			//Hydrodynamics II slope-limiters (4.4.2) and MUSCL-Hancock (6.6)
			//https://en.wikipedia.org/wiki/MUSCL_scheme

/*
minmod(r) = max(0, min(r, 1))
superbee(r) = max(0, max(
					min(1, 2*r),
					min(2, r)
				))

minmod2(a,b) = a * b < 0
		? 0
		: (|a| < |b| ? a : b)

a = u[i+1] - u[i]
b = u[i] - u[i-1]
r = a / b = (u[i+1] - u[i]) / (u[i] - u[i-1])

minmod2(a,b) = minmod2(rb, b) = |b| minmod2(r sgn b, sgn b)
TODO oops I forgot r can be negative, and so this sgn b < 0 case is bad ...
for sgn b < 0:
	minmod2(a,b) = |b| minmod2(-r, -1) = 0
for sgn b >= 0:
	minmod2(a,b) = |b| minmod2(r, 1) =
		|b| * (r * 1 < 0 ? 0
						: (|r| < 1 ? r : 1))
	minmod2(a,b) = |b| sgn r * max(0, min(|r|, 1))
	minmod2(a,b) = |b| sgn r * minmod(|r|)
*/
#if 1
			real r = dUR.ptr[j] == 0 ? 0 : (dUL.ptr[j] / dUR.ptr[j]);
			real phi = slopeLimiter(r);	//works good with minmod, bad with superbee
			real sigma = phi * dUR.ptr[j];
#else
			real sigma = minmod(minmod(fabs(dUL.ptr[j]),
                                    fabs(dUR.ptr[j])),
                             fabs(.5 * (dUL.ptr[j] - dUR.ptr[j]))
			) * sign(dUL.ptr[j] - dUR.ptr[j]);
#endif
			//q^n_i-1/2,R = q^n_i - 1/2 dx sigma	(Hydrodynamics II 6.58)
			UHalfL.ptr[j] = U->ptr[j] - .5 * sigma;

			//q^n_i+1/2,L = q^n_i + 1/2 dx sigma	(Hydrodynamics II 6.59)
			UHalfR.ptr[j] = U->ptr[j] + .5 * sigma;
		}
		for (int j = numIntStates; j < numStates; ++j) {
			UHalfL.ptr[j] = UHalfR.ptr[j] = U->ptr[j];
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn.cons_t?> FHalfL = fluxFromCons_<?=side?>(UHalfL, xIntL);
		<?=eqn.cons_t?> FHalfR = fluxFromCons_<?=side?>(UHalfR, xIntR);

		for (int j = 0; j < numIntStates; ++j) {
			real dF = FHalfR.ptr[j] - FHalfL.ptr[j];

			//U-cell-L = q^n+1/2_i-1/2,R (Hydrodynamics II 6.62)
			// = q^n_i-1/2,R + 1/2 dt/dx (f_k(q^n_i+1/2,L) - f_k(q_i-1/2,R))
			ULR->L.ptr[j] = UHalfL.ptr[j] + .5 * dt_dx * dF;

			//U-cell R = q^n+1/2_i+1/2,L (Hydrodynamics II 6.63)
			// = q^n_i+1/2,L + 1/2 dt/dx (f_k(q^n_i+1/2,L) - f_k(q_i-1/2,R))
			ULR->R.ptr[j] = UHalfR.ptr[j] + .5 * dt_dx * dF;
		}
		for (int j = numIntStates; j < numStates; ++j) {
			ULR->L.ptr[j] = ULR->R.ptr[j] = U->ptr[j];
		}

<? elseif solver.usePLM == 'plm-eig' then ?>
/*
#2: next step, project into eigenspace
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

dULe = evL( U_i - U_i-1 )
dURe = evL( U_i+1 - U_i )
dUCe = evL( (U_i+1 - U_i-1)/2 )
dUMe_j = step(dULe_j * dURe_j) * sign(dUCe_j) * 2 * min(|dULe_j|, |dURe_j|, |dUCe_j|)

aL = step(wave_j) dUMe_j (1 - wave_j dt/dx)
aR = step(-wave_j) dUMe_j (1 + wave_j dt/dx)

U_i-1/2,R = U_i - evR 1/2 aR
U_i+1/2,L = U_i + evR 1/2 aL

based on https://arxiv.org/pdf/0804.0402v1.pdf
and Trangenstein "Numeric Simulation of Hyperbolic Conservation Laws" section 6.2.5
except I'm projecting the differences in conservative values instead of primitive values.
This also needs modular slope limiter support.
works for Euler 1D Sod
euler 2D Sod this gets strange behavior and slowly diverges.
works for MHD Brio-Wu
works for maxwell
works for adm1d_v1
*/

		//1) calc delta q's ... l r c (eqn 36)
		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;
		<?=eqn.cons_t?> dUL, dUR, dUC;
		for (int j = 0; j < numIntStates; ++j) {
			dUL.ptr[j] = U->ptr[j] - UL->ptr[j];
			dUR.ptr[j] = UR->ptr[j] - U->ptr[j];
			dUC.ptr[j] = .5 * (UR->ptr[j] - UL->ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dUL.ptr[j] = dUR.ptr[j] = dUC.ptr[j] = 0;
		}

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		real dULEig[numWaves], dUREig[numWaves], dUCEig[numWaves];
		eigen_leftTransform_<?=side?>___(dULEig, &eig, dUL.ptr, xIntL);
		eigen_leftTransform_<?=side?>___(dUREig, &eig, dUR.ptr, xIntR);
		eigen_leftTransform_<?=side?>___(dUCEig, &eig, dUC.ptr, x);

		//MUSCL slope of characteristic variables
		//TODO make this based on the choice of slope limiter
		real dUMEig[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			//dUMEig[j] = minmod(minmod(2. * dULEig[j], 2. * dUREig[j]), dUCEig[j]);
			dUMEig[j] = dULEig[j] * dUREig[j] < 0 ? 0 : (
				(dUCEig[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dULEig[j]),
					fabs(dUREig[j]),
					fabs(dUCEig[j]))
			);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

		// slopes in characteristic space
		real aL[numWaves], aR[numWaves];
		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : dUMEig[j] * .5 * (1. - wave_j * dt_dx);
			aR[j] = wave_j > 0 ? 0 : dUMEig[j] * .5 * (1. + wave_j * dt_dx);
		}<? end ?>

		//convert back to conservation variable space
		<?=eqn.cons_t?> sL, sR;
		eigen_rightTransform_<?=side?>___(sL.ptr, &eig, aL, x);
		eigen_rightTransform_<?=side?>___(sR.ptr, &eig, aR, x);

		for (int j = 0; j < numIntStates; ++j) {
			ULR->L.ptr[j] = U->ptr[j] - sR.ptr[j];
			ULR->R.ptr[j] = U->ptr[j] + sL.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			ULR->L.ptr[j] = ULR->R.ptr[j] = U->ptr[j];
		}

<? elseif solver.usePLM == 'plm-eig-prim' or solver.usePLM == 'plm-eig-prim-ref' then ?>
/*
#3a: next step, convert to primitives
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

W_i = W(U_i)

dWLe = evL dU/dW ( W_i - W_i-1 )
dWRe = evL dU/dW ( W_i+1 - W_i )
dWCe = evL dU/dW ( (W_i+1 - W_i-1)/2 )
dWMe_j = step(dWL_j * dWRe_j) * sign(dWCe_j) * 2 * min(|dWLe_j|, |dWRe_j|, |dWCe_j|)

aL = step(wave_j) dWMe_j (1 - wave_j dt/dx)
aR = step(-wave_j) dWMe_j (1 + wave_j dt/dx)

U_i-1/2,R = U(W_i - 1/2 dW/dU evR aR)
U_i+1/2,L = U(W_i + 1/2 dW/dU evR aL)

#3b: next step, subtract out reference state
----------------------------------------

evL, evR, wave = eigensystem(dF/dU at i)

W_i = W(U_i)

dWLe = evL dU/dW ( W_i - W_i-1 )
dWRe = evL dU/dW ( W_i+1 - W_i )
dWCe = evL dU/dW ( (W_i+1 - W_i-1)/2 )
dWMe_j = step(dWL_j * dWRe_j) * sign(dWCe_j) * 2 * min(|dWLe_j|, |dWRe_j|, |dWCe_j|)

aL = step(wave_j) dWMe_j dt/dx (max(wave) - wave_j)
aR = step(-wave_j) dWMe_j dt/dx (wave_j - min(wave))

U_i-1/2,R = U( W_i - 1/2 dW/dU evR (aR + (1 + dt/dx min(wave)) dWMe) )
U_i+1/2,L = U( W_i + 1/2 dW/dU evR (aL + (1 - dt/dx max(wave)) dWMe) )

based on Trangenstein, Athena, etc, except working on primitives like it says to
*/

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		//1) calc delta q's ... l r c (eqn 36)
		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);
		<?=eqn.prim_t?> dWL, dWR, dWC;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWC.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWC.ptr[j] = 0.;
		}

		//calc eigen values and vectors at cell center
		//TODO calculate the eigenstate wrt W instead of U - to save some computations
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		//apply dU/dW before applying left/right eigenvectors so the eigenvectors are of the flux wrt primitives
		//RW = dW/dU RU, LW = LU dU/dW
		<?=eqn.cons_t?> tmp;

		apply_dU_dW(&tmp, &W, &dWL, xIntL); real dWLEig[numWaves]; eigen_leftTransform_<?=side?>___(dWLEig, &eig, tmp.ptr, xIntL);
		apply_dU_dW(&tmp, &W, &dWR, xIntR); real dWREig[numWaves]; eigen_leftTransform_<?=side?>___(dWREig, &eig, tmp.ptr, xIntR);
		apply_dU_dW(&tmp, &W, &dWC, x); real dWCEig[numWaves]; eigen_leftTransform_<?=side?>___(dWCEig, &eig, tmp.ptr, x);

		//MUSCL slope of characteristic variables
		real dWMEig[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			dWMEig[j] = dWLEig[j] * dWREig[j] < 0 ? 0 : (
				(dWCEig[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWLEig[j]),
					fabs(dWREig[j]),
					fabs(dWCEig[j])
				)
			);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

<? 	if solver.usePLM == 'plm-eig-prim' then ?>
		//without reference state

		// calculate left and right slopes in characteristic space
 		real aL[numWaves], aR[numWaves];
 		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : dWMEig[j] * .5 * (1. - wave_j * dt_dx);
			aR[j] = wave_j > 0 ? 0 : dWMEig[j] * .5 * (1. + wave_j * dt_dx);
		}<? end ?>

		// transform slopes back to conserved variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aL, xIntL); <?=eqn.prim_t?> sL; apply_dW_dU(&sL, &W, &tmp, xIntL);
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aR, xIntR); <?=eqn.prim_t?> sR; apply_dW_dU(&sR, &W, &tmp, xIntR);

		// linearly extrapolate the slopes forward and backward from the cell center
 		<?=eqn.prim_t?> W2L, W2R;
 		for (int j = 0; j < numIntStates; ++j) {
			W2L.ptr[j] = W.ptr[j] - sR.ptr[j];
			W2R.ptr[j] = W.ptr[j] + sL.ptr[j];
 		}
		for (int j = numIntStates; j < numStates; ++j) {
			W2L.ptr[j] = W2R.ptr[j] = W.ptr[j];
		}
		ULR->L = consFromPrim(W2L, xIntL);
		ULR->R = consFromPrim(W2R, xIntR);

<?	elseif solver.usePLM == 'plm-eig-prim-ref' then ?>
		//with reference state

		//min and max waves
		//TODO use calcCellMinMaxEigenvalues ... except based on eigen_t
		// so something like calcEigenMinMaxWaves ...
		real waveMin = min(0., <?=eqn:eigenWaveCode(side, '&eig', 'x', 0)?>);
		real waveMax = max(0., <?=eqn:eigenWaveCode(side, '&eig', 'x', eqn.numWaves-1)?>);

		//limited slope in primitive variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, dWMEig, x);
		<?=eqn.prim_t?> dWM;
		apply_dW_dU(&dWM, &W, &tmp, x);

		//left and right reference states
		<?=eqn.prim_t?> WLRef, WRRef;
		for (int j = 0; j < numIntStates; ++j) {
			WLRef.ptr[j] = W.ptr[j] + .5 * (1. - dt_dx * waveMax) * dWM.ptr[j];
			WRRef.ptr[j] = W.ptr[j] - .5 * (1. + dt_dx * waveMin) * dWM.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			WLRef.ptr[j] = WRRef.ptr[j] = 0;
		}

		// calculate left and right slopes in characteristic space
		real aL[numWaves], aR[numWaves];
		<? for j=0,eqn.numWaves-1 do ?>{
			const int j = <?=j?>;
			real wave_j = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
			aL[j] = wave_j < 0 ? 0 : (dWMEig[j] * dt_dx * (waveMax - wave_j));
			aR[j] = wave_j > 0 ? 0 : (dWMEig[j] * dt_dx * (waveMin - wave_j));
		}<? end ?>

		// transform slopes back to conserved variable space
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aL, xIntL); <?=eqn.prim_t?> sL; apply_dW_dU(&sL, &W, &tmp, xIntL);
		eigen_rightTransform_<?=side?>___(tmp.ptr, &eig, aR, xIntR); <?=eqn.prim_t?> sR; apply_dW_dU(&sR, &W, &tmp, xIntR);

		// linearly extrapolate the slopes forward and backward from the cell center
		<?=eqn.prim_t?> W2L, W2R;
		for (int j = 0; j < numIntStates; ++j) {
			W2R.ptr[j] = WRRef.ptr[j] + .5 * sR.ptr[j];
			W2L.ptr[j] = WLRef.ptr[j] + .5 * sL.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			W2R.ptr[j] = W2L.ptr[j] = W.ptr[j];
		}
		//TODO fix the x's
		ULR->L = consFromPrim(W2R, xIntR);
		ULR->R = consFromPrim(W2L, xIntL);

<? 	end	-- solver.usePLM
elseif solver.usePLM == 'plm-athena' then
?>
		//based on Athena

		real3 xIntL = x; xIntL.s<?=side?> -= .5 * grid_dx<?=side?>;
		real3 xIntR = x; xIntR.s<?=side?> += .5 * grid_dx<?=side?>;

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		//1) calc delta q's ... l r c (eqn 36)
		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);

		<?=eqn.prim_t?> dWL, dWR, dWC, dWG;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWC.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
			dWG.ptr[j] = (dWL.ptr[j] * dWR.ptr[j]) <= 0. ? 0. : (
				2. * dWL.ptr[j] * dWR.ptr[j] / (dWL.ptr[j] + dWR.ptr[j])
			);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWC.ptr[j] = dWG.ptr[j] = 0.;
		}

		real dal[numWaves], dar[numWaves], dac[numWaves], dag[numWaves];
		eigen_leftTransform_<?=side?>___(dal, &eig, dWL.ptr, xIntL);
		eigen_leftTransform_<?=side?>___(dar, &eig, dWR.ptr, xIntR);
		eigen_leftTransform_<?=side?>___(dac, &eig, dWC.ptr, x);
		eigen_leftTransform_<?=side?>___(dag, &eig, dWG.ptr, x);

		real da[numWaves];
		for (int j = 0; j < numWaves; ++j) {
			da[j] = 0;
			if (dal[j] * dar[j] > 0) {
				real lim_slope1 = min(fabs(dal[j]), fabs(dar[j]));
				real lim_slope2 = min(fabs(dac[j]), fabs(dag[j]));
				da[j] = sign(dac[j]) * min(2. * lim_slope1, lim_slope2);
			}
		}

		<?=eqn.prim_t?> dWm;
		eigen_rightTransform_<?=side?>___(dWm.ptr, &eig, da, x);

		<?=eqn.prim_t?> Wlv, Wrv;
		for (int j = 0; j < numIntStates; ++j) {
			Wlv.ptr[j] = W.ptr[j] - .5 * dWm.ptr[j];
			Wrv.ptr[j] = W.ptr[j] + .5 * dWm.ptr[j];
			real C = Wrv.ptr[j] + Wlv.ptr[j];

			Wlv.ptr[j] = max(min(W.ptr[j], WL.ptr[j]), Wlv.ptr[j]);
			Wlv.ptr[j] = min(max(W.ptr[j], WL.ptr[j]), Wlv.ptr[j]);
			Wrv.ptr[j] = C - Wlv.ptr[j];

			Wrv.ptr[j] = max(min(W.ptr[j],WR.ptr[j]),Wrv.ptr[j]);
			Wrv.ptr[j] = min(max(W.ptr[j],WR.ptr[j]),Wrv.ptr[j]);
			Wlv.ptr[j] = C - Wrv.ptr[j];
		}
		for (int j = numIntStates; j < numStates; ++j) {
			Wlv.ptr[j] = W.ptr[j];
			Wrv.ptr[j] = W.ptr[j];
		}

		ULR->L = consFromPrim(Wlv, xIntL);
		ULR->R = consFromPrim(Wrv, xIntR);

<? elseif solver.usePLM == 'ppm-experimental' then ?>
//here's my attempt at Trangenstein section 5.12 PPM
//with help from Zingale's ppm code

		real3 xL = x; xL.s<?=side?> -= grid_dx<?=side?>;
		real3 xR = x; xR.s<?=side?> += grid_dx<?=side?>;

		const global <?=eqn.cons_t?>* UL = U - stepsize.s<?=side?>;
		const global <?=eqn.cons_t?>* UR = U + stepsize.s<?=side?>;

		//1984 Collela & Woodward eqn 1.7
		<?=eqn.prim_t?> W = primFromCons(*U, x);
		<?=eqn.prim_t?> WL = primFromCons(*UL, xL);
		<?=eqn.prim_t?> WR = primFromCons(*UR, xR);
		<?=eqn.prim_t?> WR2 = primFromCons(*UR, xR);
		<?=eqn.prim_t?> dWL, dWR, dWR2, dWCL, dWCR;
		for (int j = 0; j < numIntStates; ++j) {
			dWL.ptr[j] = W.ptr[j] - WL.ptr[j];
			dWR.ptr[j] = WR.ptr[j] - W.ptr[j];
			dWR2.ptr[j] = WR2.ptr[j] - WR.ptr[j];
			dWCL.ptr[j] = .5 * (WR.ptr[j] - WL.ptr[j]);
			dWCR.ptr[j] = .5 * (WR2.ptr[j] - W.ptr[j]);
		}
		for (int j = numIntStates; j < numStates; ++j) {
			dWL.ptr[j] = dWR.ptr[j] = dWR2.ptr[j] = dWCL.ptr[j] = dWCR.ptr[j] = 0.;
		}

		//slope limiter
		//1984 Collela & Woodward eqn 1.8
		real dWML[numStates];
		for (int j = 0; j < numStates; ++j) {
			dWML[j] = dWL.ptr[j] * dWR.ptr[j] < 0 ? 0 : (
				(dWCL.ptr[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWL.ptr[j]),
					fabs(dWR.ptr[j]),
					fabs(dWCL.ptr[j])
				)
			);
		}
		real dWMR[numStates];
		for (int j = 0; j < numStates; ++j) {
			dWMR[j] = dWR.ptr[j] * dWR2.ptr[j] < 0 ? 0 : (
				(dWCR.ptr[j] >= 0. ? 1. : -1.)
				* 2. * min3(
					fabs(dWR.ptr[j]),
					fabs(dWR2.ptr[j]),
					fabs(dWCR.ptr[j])
				)
			);
		}

		//1984 Collela & Woodward eqn 1.6
		<?=eqn.prim_t?> Wp, Wm;
		for (int j = 0; j < numStates; ++j) {
			Wp.ptr[j] = Wm.ptr[j] = .5 * dWR.ptr[j] - 1./6. * (dWMR[j] - dWML[j]);
		}

		//1984 Collela & Woodward eqn 1.10
		for (int j = 0; j < numStates; ++j) {
			real dWp = Wp.ptr[j] - W.ptr[j];
			real dWm = W.ptr[j] - Wm.ptr[j];
			real dWpm = Wp.ptr[j] - Wm.ptr[j];
			real avgWpm = .5 * (Wp.ptr[j] + Wm.ptr[j]);
			if (dWp * dWm <= 0) {
				Wp.ptr[j] = Wm.ptr[j] = W.ptr[j];
			} else if (dWpm * (W.ptr[j] - avgWpm) > dWpm * dWpm / 6.) {
				Wm.ptr[j] = 3. * W.ptr[j] - 2. * Wp.ptr[j];
			} else if (-dWpm * dWpm / 6. > dWpm * (W.ptr[j] - avgWpm)) {
				Wp.ptr[j] = 3. * W.ptr[j] - 2. * Wm.ptr[j];
			}
		}

		for (int j = 0; j < numStates; ++j) {
			Wm.ptr[j] = .5 * (W.ptr[j] +  Wm.ptr[j]);
			Wp.ptr[j] = .5 * (W.ptr[j] +  Wp.ptr[j]);
		}

		//1984 Collela & Woodward eqn 1.5
		real W6[numStates];
		for (int j = 0; j < numStates; ++j) {
			W6[j] = 6. * W.ptr[j] - 3. * (Wp.ptr[j] + Wm.ptr[j]);
		}

		real dx = dx<?=side?>_at(i);
		real dt_dx = dt / dx;

		//calc eigen values and vectors at cell center
		<?=eqn.eigen_t?> eig = eigen_forCell_<?=side?>(U, x);
		<?=eqn:eigenWaveCodePrefix(side, '&eig', 'x')?>

		real eval[numWaves];
<? for j=0,eqn.numWaves-1 do
?>		eval[<?=j?>] = <?=eqn:eigenWaveCode(side, '&eig', 'x', j)?>;
<? end
?>

		<?=eqn.prim_t?> Ip, Im;
		for (int j = 0; j < numWaves; ++j) {
			real sigma = fabs(eval[j]) * dt_dx;
			if (eval[j] >= 0.) {
				Ip.ptr[j] = Wp.ptr[j] - .5 * sigma * (Wp.ptr[j] - Wm.ptr[j] - (1. - 2./3.*sigma) * W6[j]);
			} else {
				Ip.ptr[j] = W.ptr[j];
			}
			if (eval[j] <= 0.) {
				Im.ptr[j] = Wm.ptr[j] + .5 * sigma * (Wp.ptr[j] - Wm.ptr[j] + (1. - 2./3.*sigma) * W6[j]);
			} else {
				Im.ptr[j] = W.ptr[j];
			}
		}

		real waveMin = <?=eqn:eigenWaveCode(side, '&eig', 'x', 0)?>;
		real waveMax = <?=eqn:eigenWaveCode(side, '&eig', 'x', eqn.numWaves-1)?>;
		<?=eqn.prim_t?> Wref_xp = waveMax >= 0 ? Ip : W;
		<?=eqn.prim_t?> Wref_xm = waveMin <= 0 ? Im : W;

		<?=eqn.prim_t?> Wref_xm_minus_Im, Wref_xp_minus_Ip;
		for (int j = 0; j < numWaves; ++j) {
			Wref_xm_minus_Im.ptr[j] = Wref_xm.ptr[j] - Im.ptr[j];
			Wref_xp_minus_Ip.ptr[j] = Wref_xp.ptr[j] - Ip.ptr[j];
		}

		real beta_xm[numWaves];
		real beta_xp[numWaves];
		eigen_leftTransform_<?=side?>___(beta_xm, &eig, Wref_xm_minus_Im.ptr, x);
		eigen_leftTransform_<?=side?>___(beta_xp, &eig, Wref_xp_minus_Ip.ptr, x);
		for (int j = 0; j < numWaves; ++j) {
			beta_xp[j] = max(0., beta_xp[j]);
			beta_xm[j] = min(0., beta_xm[j]);
		}

		<?=eqn.prim_t?> WresL, WresR;
		eigen_rightTransform_<?=side?>___(WresL.ptr, &eig, beta_xp, x);
		eigen_rightTransform_<?=side?>___(WresR.ptr, &eig, beta_xm, x);
		for (int j = 0; j < numStates; ++j) {
			WresL.ptr[j] = Wref_xp.ptr[j] - WresL.ptr[j];
			WresR.ptr[j] = Wref_xm.ptr[j] - WresR.ptr[j];
		}

		ULR->L = consFromPrim(WresR, x);
		ULR->R = consFromPrim(WresR, x);

<? end ?>

	}<? end ?>
}


Add fact function test

Add fact function test

a = ((7))
f a b = 1
f 5 1
\a b -> 6
let x = 10 y = 20 in 3
fact 1 = 1
fact n = n * fact (n - 1)
print (fact 5)
fact 10


Redid the colors, added a few comments

Redid the colors, added a few comments

uint4 white_light(uint4 input, float3 light, int3 mask) {

	input.w = input.w + acos(
		dot(
			normalize(light),
			normalize(fabs(convert_float3(mask)))
			)
		) * 50;

	return (input);

}

__kernel void min_kern(
        global char* map,
        global int3* map_dim,
        global int2* resolution,
        global float3* projection_matrix,
        global float3* cam_dir,
        global float3* cam_pos,
		global float* lights,
		global int* light_count,
        __write_only image2d_t image
){

    // Get the pixel position of this worker
    size_t id = get_global_id(0);
    int2 pixel = {id % resolution->x, id / resolution->x};

    // Slew the ray into it's correct position based on the view matrix's starting position
    // and the camera's current direction

    float3 ray_dir = projection_matrix[pixel.x + resolution->x * pixel.y];

    // Yaw
    ray_dir = (float3)(
            ray_dir.z * sin(cam_dir->y) + ray_dir.x * cos(cam_dir->y),
            ray_dir.y,
            ray_dir.z * cos(cam_dir->y) - ray_dir.x * sin(cam_dir->y)
    );

    // Pitch
    ray_dir = (float3)(
          ray_dir.x * cos(cam_dir->z) - ray_dir.y * sin(cam_dir->z),
          ray_dir.x * sin(cam_dir->z) + ray_dir.y * cos(cam_dir->z),
          ray_dir.z
    );

    // Setup the voxel step based on what direction the ray is pointing
    int3 voxel_step = {1, 1, 1};
	voxel_step *= (ray_dir > 0) - (ray_dir < 0);

    // Setup the voxel coords from the camera origin
	int3 voxel = convert_int3(*cam_pos);

    // Delta T is the units a ray must travel along an axis in order to
    // traverse an integer split
	float3 delta_t = fabs(1.0f / ray_dir);

    // Intersection T is the collection of the next intersection points
    // for all 3 axis XYZ.
	float3 intersection_t = delta_t;

    // Create a psuedo random number for view fog
	int2 randoms = { 3, 14 };
	uint seed = randoms.x + id;
	uint t = seed ^ (seed << 11);
	uint result = randoms.y ^ (randoms.y >> 19) ^ (t ^ (t >> 8));

    // Distance a ray can travel before it terminates
	int max_dist = 200 + result % 50;
	int dist = 0;

    // Bitmask to keep track of which axis was tripped
	int3 mask = { 0, 0, 0 };

    // Andrew Woo's raycasting algo
    do {

        // Non-branching test of the lowest delta_t value
		mask = intersection_t.xyz <= min(intersection_t.yzx, intersection_t.zxy);

		// Based on the result increment the voxel and intersection
		intersection_t += delta_t * fabs(convert_float3(mask.xyz));
		voxel.xyz += voxel_step.xyz * mask.xyz;

        // If the ray went out of bounds
		int3 overshoot = voxel <= *map_dim;
		int3 undershoot = voxel > 0;

        // "Sky"
		if (overshoot.x == 0 || overshoot.y == 0 || overshoot.z == 0 || undershoot.x == 0 || undershoot.y == 0){
			write_imageui(image, pixel, (uint4)(135, 206, 235, 255));
			return;
		}

		// "Water"
		if (undershoot.z == 0) {
			write_imageui(image, pixel, (uint4)(64, 164, 223, 255));
			return;
		}

        // If we hit a voxel
        int index = voxel.x + map_dim->x * (voxel.y + map_dim->z * voxel.z);
        int voxel_data = map[index];

		if (voxel_data != 0) {
			switch (voxel_data) {
			case 1:
				write_imageui(image, pixel, (uint4)(50, 0, 0, 255));
				return;
			case 2:
				write_imageui(image, pixel, (uint4)(0, 50, 40, 255));
				return;
			case 3:
				write_imageui(image, pixel, (uint4)(0, 0, 50, 255));
				return;
			case 4:
				write_imageui(image, pixel, (uint4)(25, 0, 25, 255));
				return;
			case 5:
				//write_imageui(image, pixel, (uint4)(200, 200, 200, 255));
				write_imageui(image, pixel, white_light((uint4)(44, 176, 55, 100), (float3)(lights[7], lights[8], lights[9]), mask));
				return;
			case 6:
				write_imageui(image, pixel, (uint4)(30, 80, 10, 255));
				return;
			}
		}

        dist++;
    } while (dist < max_dist);

    write_imageui(image, pixel, (uint4)(135, 206, 235, 255));
    return;
}

uint4 white_light(uint4 input, float3 light, int3 mask) {

	input.w = input.w + acos(
		dot(
			normalize(light),
			normalize(fabs(convert_float3(mask)))
			)
		) * 50;

	return (input);

}

__kernel void min_kern(
        global char* map,
        global int3* map_dim,
        global int2* resolution,
        global float3* projection_matrix,
        global float3* cam_dir,
        global float3* cam_pos,
		global float* lights,
		global int* light_count,
        __write_only image2d_t image
){

    size_t id = get_global_id(0);
    int2 pixel = {id % resolution->x, id / resolution->x};
    //int2 pixel = {1, 1};

    float3 ray_dir = projection_matrix[pixel.x + resolution->x * pixel.y];

    ray_dir = (float3)(
            ray_dir.z * sin(cam_dir->y) + ray_dir.x * cos(cam_dir->y),
            ray_dir.y,
            ray_dir.z * cos(cam_dir->y) - ray_dir.x * sin(cam_dir->y)
    );
//
//    float a = cam_dir->x;
//    float b = cam_dir->y;
//    float c = cam_dir->z;
//
//    ray_dir.x = ray_dir.z * sin(b) + ray_dir.x * cos(b);
//    ray_dir.y = ray_dir.y;
//    ray_dir.z = ray_dir.z * cos(b) - ray_dir.x * sin(b);
//
//
//    float3 ray_dir2 = (float3)(
//    ray_dir.x * cos(c) - ray_dir.y * sin(c),
//    ray_dir.x * sin(c) + ray_dir.y * cos(c),
//    ray_dir.z);
//
//    printf("%f, %f, %f", ray_dir2.x, ray_dir2.y, ray_dir2.z);

    ray_dir = (float3)(
          ray_dir.x * cos(cam_dir->z) - ray_dir.y * sin(cam_dir->z),
          ray_dir.x * sin(cam_dir->z) + ray_dir.y * cos(cam_dir->z),
          ray_dir.z
    );

    // Setup the voxel step based on what direction the ray is pointing
    int3 voxel_step = {1, 1, 1};
	voxel_step *= (ray_dir > 0) - (ray_dir < 0);

    /*voxel_step.x *= (ray_dir.x > 0) - (ray_dir.x < 0);
    voxel_step.y *= (ray_dir.y > 0) - (ray_dir.y < 0);
    voxel_step.z *= (ray_dir.z > 0) - (ray_dir.z < 0);*/

    // Setup the voxel coords from the camera origin
	int3 voxel = convert_int3(*cam_pos);

    // Delta T is the units a ray must travel along an axis in order to
    // traverse an integer split
	float3 delta_t = fabs(1.0f / ray_dir);

    // Intersection T is the collection of the next intersection points
    // for all 3 axis XYZ.
	float3 intersection_t = delta_t;

	int2 randoms = { 3, 14 };
	uint seed = randoms.x + id;
	uint t = seed ^ (seed << 11);
	uint result = randoms.y ^ (randoms.y >> 19) ^ (t ^ (t >> 8));

	int max_dist = 500 + result % 50;
	int dist = 0;

	int3 mask = { 0, 0, 0 };

    // Andrew Woo's raycasting algo
    do {

		mask = intersection_t.xyz <= min(intersection_t.yzx, intersection_t.zxy);
		float3 thing = delta_t * fabs(convert_float3(mask.xyz));
		intersection_t += delta_t * fabs(convert_float3(mask.xyz));
		voxel.xyz += voxel_step.xyz * mask.xyz;

        // If the ray went out of bounds
		int3 overshoot = voxel <= *map_dim;
		int3 undershoot = voxel > 0;

		if (overshoot.x == 0 || overshoot.y == 0 || overshoot.z == 0 || undershoot.x == 0 || undershoot.y == 0){
			write_imageui(image, pixel, (uint4)(50, 50, 50, 255));
			return;
		}
		if (undershoot.z == 0) {
			write_imageui(image, pixel, (uint4)(14, 30, 50, 255));
			return;
		}

        // If we hit a voxel
        int index = voxel.x + map_dim->x * (voxel.y + map_dim->z * voxel.z);
        int voxel_data = map[index];

		if (voxel_data != 0) {
			switch (voxel_data) {
			case 1:
				write_imageui(image, pixel, (uint4)(50, 0, 0, 255));
				return;
			case 2:
				write_imageui(image, pixel, (uint4)(0, 50, 40, 255));
				return;
			case 3:
				write_imageui(image, pixel, (uint4)(0, 0, 50, 255));
				return;
			case 4:
				write_imageui(image, pixel, (uint4)(25, 0, 25, 255));
				return;
			case 5:
				//write_imageui(image, pixel, (uint4)(200, 200, 200, 255));
				write_imageui(image, pixel, white_light((uint4)(225, 232, 214, 100), (float3)(lights[7], lights[8], lights[9]), mask));
				return;
			case 6:
				write_imageui(image, pixel, (uint4)(30, 80, 10, 255));
				return;
			}
		}

        dist++;
    } while (dist < max_dist);

    write_imageui(image, pixel, (uint4)(73, 81, 89, 255));
    return;
}