Fix incorrect sigmoid

Fix incorrect sigmoid

/*
 * Copyright (c) 2016 AMD Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and/or associated documentation files (the
 * "Materials"), to deal in the Materials without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Materials, and to
 * permit persons to whom the Materials are 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 Materials.
 *
 * THE MATERIALS ARE 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
 * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 */

#define _FLOAT					float
#define _FLOAT2					float2
#define _FLOAT4					float4
#define _FLOAT8					float8

#define UNUSED __attribute__((__unused__))

#define MLO_NRN_GROUP_SZ2 1

#define MLO_NEURON_PASTHRU		0  //x
#define MLO_NEURON_LOGISTIC	MLO_NEURON_PASTHRU + 1		//	1 / (1 + e^-x)	//Sigmoid
#define MLO_NEURON_TANH		MLO_NEURON_LOGISTIC + 1	//	a * tanh( b * x)
#define MLO_NEURON_RELU		MLO_NEURON_TANH + 1		//	max(0, x)
#define MLO_NEURON_SOFTRELU	MLO_NEURON_RELU + 1		//	log(1 + e^x)   // bonomial normal log likelihood
#define MLO_NEURON_ABS			MLO_NEURON_SOFTRELU + 1	//	abs(x)
#define MLO_NEURON_POWER		MLO_NEURON_ABS + 1		// (a + b * x ) ^power
//#define MLO_NEURON_BRELU		MLO_NEURON_POWER + 1		//	min(a, max(0, x))
//#define MLO_NEURON_SQUARE		BRELU + 1			//	x^2
//#define MLO_NEURON_SQR			MLO_NEURON_SQUARE + 1		//	sqr(x)
//#define MLO_NEURON_LINEAR		MLO_NEURON_SQR	+ 1			//	a + b * x
#define MLO_NEURON_TOTAL		MLO_NEURON_POWER + 1

__attribute__((always_inline))
void ActivationFunction_PassThru(_FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i <4; i++)
	{
		res[i] = data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_ReLU(int n, _FLOAT * res, const _FLOAT* data, _FLOAT slope)
{
	for (int i = 0; i < n; ++i)
	{
		res[i] = (data[i] > 0) ? data[i] : data[i] * slope;
	}
}

__attribute__((always_inline))
void ActivationFunction_BReLU(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha)
{

	for (int i = 0; i < n; ++i)
	{
		res[i] = fmin(alpha, fmax(data[i], 0));
	}
}

__attribute__((always_inline))
void ActivationFunction_Sigmoid(int n, _FLOAT * res, const _FLOAT* data)
{
	for(int i = 0; i < n; i++)
	{
// 1/(1 + exp(-x))
		res[i] = 1.f / (1.f + exp(-data[i]));
	}
}

__attribute__((always_inline))
void ActivationFunction_TanH(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha, _FLOAT beta)
{
	for (int i = 0; i < n; i++)
	{
		// (exp(2x) -1) / (exp(2x) + 1)
		res[i] = alpha* tanh(beta * data[i]);
	}
}
__attribute__((always_inline))
void ActivationFunction_Abs(int n, _FLOAT * res, const _FLOAT* data)
{
	for(int i = 0; i < n; i++)
	{
		res[i] = fabs(data[i]);
	}
}

__attribute__((always_inline))
void ActivationFunction_Square(int n, _FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i < n; i++)
	{

		res[i] = data[i] * data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_Sqrt(int n, _FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i < n; i++)
	{

		res[i] = sqrt(data[i]);
	}
}

__attribute__((always_inline))
void ActivationFunction_Linear(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha, _FLOAT beta)
{
	for (int i = 0; i < n; i++)
	{
		// (exp(2x) -1) / (exp(2x) + 1)
		res[i] = alpha + beta * data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_Power(int n,
							_FLOAT * res,
							const _FLOAT* data,
							_FLOAT power,
							_FLOAT alpha,
							_FLOAT beta)
{
	for(int i = 0; i < n; i++)
	{
// (shift + scale * x ) ^power
		_FLOAT arg = alpha + data[i] * beta;
		_FLOAT run_arg = (arg == 0) ? 1 : arg;
		res[i] = (arg == 0) ? 0 : pow(run_arg, power);

	}
}

__attribute__((always_inline))
void ActivationFunction_BNLL(int n, _FLOAT * res, const _FLOAT* data)

{
	for(int i = 0; i < n; i++)
	{
//	log(1 + exp(x))
		res[i] = (data[i] > 0) ? data[i] + log(1.f + exp(-data[i]))
			: log(1.f + exp(data[i]));
	}
}

void ActivationFunction(int n, _FLOAT * res, const _FLOAT* data,
							_FLOAT power,
							_FLOAT alpha,
							_FLOAT beta)
{
	(void)power;
	(void)alpha;
	(void)beta;
#if		MLO_NRN_OP_ID==MLO_NEURON_PASTHRU
	(void)n;
	ActivationFunction_PassThru(res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_LOGISTIC
// 1/(1 + exp(-x))
	ActivationFunction_Sigmoid(n, res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_TANH
// (exp(2x) -1) / (exp(2x) + 1)
	ActivationFunction_TanH(n, res, data, alpha, beta);

#elif	MLO_NRN_OP_ID==MLO_NEURON_RELU
	ActivationFunction_ReLU(n, res, data, alpha);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_BRELU
//	ActivationFunction_BReLU(n, res, data, alpha);

#elif	MLO_NRN_OP_ID==MLO_NEURON_SOFTRELU
//	log(1 + exp(x))
	ActivationFunction_BNLL(n, res, data);
#elif	MLO_NRN_OP_ID==MLO_NEURON_ABS
	ActivationFunction_Abs(n, res, data);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_SQUARE
//	ActivationFunction_Square(res, data);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_SQR
//	ActivationFunction_Sqrt(n, res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_POWER
// (shift + scale * x ) ^power

	ActivationFunction_Power(n,
							res,
							data,
							power,
							alpha,
							beta);

#endif
}

/******************************************************************************/
/*									DIFF                                      */
/******************************************************************************/

static __constant _FLOAT kBNLL_THRESHOLD = 50.;

__attribute__((always_inline))
void ActivationFunction_ReLU_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data, UNUSED _FLOAT negative_slope)
{

	for (int i = 0; i < n; ++i)
	{
		bot_diff[i] = top_diff[i] * (bot_data[i] > 0);
	}
}

__attribute__((always_inline))
void ActivationFunction_TanH_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *top_data)
{
	for(int i = 0; i < n; i++)
	{
// (exp(2x) -1) / (exp(2x) + 1)
		_FLOAT tanh_x = top_data[i];
		bot_diff[i] = top_diff[i] * (1 - tanh_x*tanh_x);
	}
}

__attribute__((always_inline))
void ActivationFunction_Sigmoid_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *top_data)
{
	for (int i = 0; i < n; i++)
	{
		// 1/(1 + exp(-x))
		_FLOAT sigmoid_x = top_data[i];
		bot_diff[i] = top_diff[i] * sigmoid_x * (1.f - sigmoid_x);
	}
}

__attribute__((always_inline))
void ActivationFunction_Abs_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data)
{
	for (int i = 0; i < n; i++)
	{
		bot_diff[i] = top_diff[i] * ((bot_data != 0 ) ? 1 : -1);
	}
}

// Compute dy/dx = scale * power * (shift + scale * x)^(power - 1)
//               = diff_scale * y / (shift + scale * x)
__attribute__((always_inline))
void ActivationFunction_Power_Diff(int n, _FLOAT * bot_diff, UNUSED const _FLOAT* top_diff, const _FLOAT *top_data, const _FLOAT *bot_data,
_FLOAT diff_scale,
UNUSED _FLOAT power,
_FLOAT scale,
_FLOAT shift)
{

	for (int i = 0; i < n; i++)
	{
		_FLOAT arg = shift + bot_data[i] * scale;
		bot_diff[i] = (arg == 0) ? 0 : diff_scale * top_data[i]/arg;

	}
}

__attribute__((always_inline))
void ActivationFunction_BNLL_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data)
{
	for (int i = 0; i < n; i++)
	{
		//	(log(1 + exp(x)))' = 1/ (1 + exp(-x))
		_FLOAT expval = exp(fmin(bot_data[i], kBNLL_THRESHOLD));
		bot_diff[i] = top_diff[i] * expval / (expval + 1.f);

	}
}

__attribute__((reqd_work_group_size(MLO_NRN_GROUP_SZ0,MLO_NRN_GROUP_SZ1,MLO_NRN_GROUP_SZ2)))
__kernel void MIOpenNeuronFwd(
       const __global _FLOAT * bot,
       __global _FLOAT * top,
		_FLOAT power,
		_FLOAT scale,
		_FLOAT shift
	   )
{
	int x = get_global_id(0); // channel x

	_FLOAT data[MLO_READ_UNIT];
	_FLOAT response[MLO_READ_UNIT];
#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			data[i] = bot[x * MLO_READ_UNIT + i];
		}
		for (; i < MLO_READ_UNIT; ++i)
		{
			data[i] = 1.f;
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			data[i] = bot[x * MLO_READ_UNIT + i];
		}
	}
	ActivationFunction(MLO_READ_UNIT, response,(const _FLOAT*)data, power, scale, shift);

#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			top[x * MLO_READ_UNIT + i] = response[i];
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			top[x * MLO_READ_UNIT + i] = response[i];
		}
	}

}

__attribute__((reqd_work_group_size(MLO_NRN_GROUP_SZ0,MLO_NRN_GROUP_SZ1,MLO_NRN_GROUP_SZ2)))
__kernel void MIOpenNeuronBwd(__global _FLOAT * bot_diff,
							__global  const _FLOAT* top_diff,
							__global const _FLOAT *bot_data,
							__global  const _FLOAT *top_data,
							_FLOAT diff_scale,
							_FLOAT power,
							_FLOAT scale,
							_FLOAT shift	   )
{

	(void)diff_scale;
	(void)power;
	(void)scale;
	(void)shift;

	int x = get_global_id(0); // channel x

	_FLOAT bot_diff_dat[MLO_READ_UNIT];
	_FLOAT top_diff_dat[MLO_READ_UNIT];
	_FLOAT bot_dat[MLO_READ_UNIT];
	_FLOAT top_dat[MLO_READ_UNIT];
#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			top_diff_dat[i] = top_diff[x * MLO_READ_UNIT + i];
			bot_dat[i] = bot_data[x * MLO_READ_UNIT + i];
			top_dat[i] = top_data[x * MLO_READ_UNIT + i];
		}
		for (; i < MLO_READ_UNIT; ++i)
		{
			top_diff_dat[i] = 1.f;
			bot_dat[i] = 1.f;
			top_dat[i] = 1.f;
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			top_diff_dat[i] = top_diff[x * MLO_READ_UNIT + i];
			bot_dat[i] = bot_data[x * MLO_READ_UNIT + i];
			top_dat[i] = top_data[x * MLO_READ_UNIT + i];
		}
	}

#if		MLO_NRN_OP_ID==MLO_NEURON_RELU
{
	ActivationFunction_ReLU_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)bot_dat, scale);
}
#elif	MLO_NRN_OP_ID==MLO_NEURON_LOGISTIC
// 1/(1 + exp(-x))
	ActivationFunction_Sigmoid_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)top_dat);
#elif	MLO_NRN_OP_ID==MLO_NEURON_TANH
// (exp(2x) -1) / (exp(2x) + 1)

	ActivationFunction_TanH_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)top_dat);

#elif	MLO_NRN_OP_ID==MLO_NEURON_ABS

	ActivationFunction_Abs_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *)bot_dat);
#elif	MLO_NRN_OP_ID==MLO_NEURON_POWER
// (shift + scale * x ) ^power

	ActivationFunction_Power_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *) top_dat, (const _FLOAT *)bot_dat,
							diff_scale,	power, scale, shift);

#elif	MLO_NRN_OP_ID==MLO_NEURON_SOFTRELU
//	log(1 + exp(x))
	ActivationFunction_BNLL_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *)bot_dat);
#endif

#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			bot_diff[x * MLO_READ_UNIT + i] = bot_diff_dat[i];
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			bot_diff[x * MLO_READ_UNIT + i] = bot_diff_dat[i];
		}
	}

}

/*
 * Copyright (c) 2016 AMD Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and/or associated documentation files (the
 * "Materials"), to deal in the Materials without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Materials, and to
 * permit persons to whom the Materials are 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 Materials.
 *
 * THE MATERIALS ARE 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
 * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 */

#define _FLOAT					float
#define _FLOAT2					float2
#define _FLOAT4					float4
#define _FLOAT8					float8

#define UNUSED __attribute__((__unused__))

#define MLO_NRN_GROUP_SZ2 1

#define MLO_NEURON_PASTHRU		0  //x
#define MLO_NEURON_LOGISTIC	MLO_NEURON_PASTHRU + 1		//	1 / (1 + e^-x)	//Sigmoid
#define MLO_NEURON_TANH		MLO_NEURON_LOGISTIC + 1	//	a * tanh( b * x)
#define MLO_NEURON_RELU		MLO_NEURON_TANH + 1		//	max(0, x)
#define MLO_NEURON_SOFTRELU	MLO_NEURON_RELU + 1		//	log(1 + e^x)   // bonomial normal log likelihood
#define MLO_NEURON_ABS			MLO_NEURON_SOFTRELU + 1	//	abs(x)
#define MLO_NEURON_POWER		MLO_NEURON_ABS + 1		// (a + b * x ) ^power
//#define MLO_NEURON_BRELU		MLO_NEURON_POWER + 1		//	min(a, max(0, x))
//#define MLO_NEURON_SQUARE		BRELU + 1			//	x^2
//#define MLO_NEURON_SQR			MLO_NEURON_SQUARE + 1		//	sqr(x)
//#define MLO_NEURON_LINEAR		MLO_NEURON_SQR	+ 1			//	a + b * x
#define MLO_NEURON_TOTAL		MLO_NEURON_POWER + 1

__attribute__((always_inline))
void ActivationFunction_PassThru(_FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i <4; i++)
	{
		res[i] = data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_ReLU(int n, _FLOAT * res, const _FLOAT* data, _FLOAT slope)
{
	for (int i = 0; i < n; ++i)
	{
		res[i] = (data[i] > 0) ? data[i] : data[i] * slope;
	}
}

__attribute__((always_inline))
void ActivationFunction_BReLU(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha)
{

	for (int i = 0; i < n; ++i)
	{
		res[i] = fmin(alpha, fmax(data[i], 0));
	}
}

__attribute__((always_inline))
void ActivationFunction_Sigmoid(int n, _FLOAT * res, const _FLOAT* data)
{
	for(int i = 0; i < n; i++)
	{
// 1/(1 + exp(-x))
		res[i] = (1.f + exp(-data[i]));
	}
}

__attribute__((always_inline))
void ActivationFunction_TanH(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha, _FLOAT beta)
{
	for (int i = 0; i < n; i++)
	{
		// (exp(2x) -1) / (exp(2x) + 1)
		res[i] = alpha* tanh(beta * data[i]);
	}
}
__attribute__((always_inline))
void ActivationFunction_Abs(int n, _FLOAT * res, const _FLOAT* data)
{
	for(int i = 0; i < n; i++)
	{
		res[i] = fabs(data[i]);
	}
}

__attribute__((always_inline))
void ActivationFunction_Square(int n, _FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i < n; i++)
	{

		res[i] = data[i] * data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_Sqrt(int n, _FLOAT * res, const _FLOAT* data)
{
	for (int i = 0; i < n; i++)
	{

		res[i] = sqrt(data[i]);
	}
}

__attribute__((always_inline))
void ActivationFunction_Linear(int n, _FLOAT * res, const _FLOAT* data, _FLOAT alpha, _FLOAT beta)
{
	for (int i = 0; i < n; i++)
	{
		// (exp(2x) -1) / (exp(2x) + 1)
		res[i] = alpha + beta * data[i];
	}
}

__attribute__((always_inline))
void ActivationFunction_Power(int n,
							_FLOAT * res,
							const _FLOAT* data,
							_FLOAT power,
							_FLOAT alpha,
							_FLOAT beta)
{
	for(int i = 0; i < n; i++)
	{
// (shift + scale * x ) ^power
		_FLOAT arg = alpha + data[i] * beta;
		_FLOAT run_arg = (arg == 0) ? 1 : arg;
		res[i] = (arg == 0) ? 0 : pow(run_arg, power);

	}
}

__attribute__((always_inline))
void ActivationFunction_BNLL(int n, _FLOAT * res, const _FLOAT* data)

{
	for(int i = 0; i < n; i++)
	{
//	log(1 + exp(x))
		res[i] = (data[i] > 0) ? data[i] + log(1.f + exp(-data[i]))
			: log(1.f + exp(data[i]));
	}
}

void ActivationFunction(int n, _FLOAT * res, const _FLOAT* data,
							_FLOAT power,
							_FLOAT alpha,
							_FLOAT beta)
{
	(void)power;
	(void)alpha;
	(void)beta;
#if		MLO_NRN_OP_ID==MLO_NEURON_PASTHRU
	(void)n;
	ActivationFunction_PassThru(res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_LOGISTIC
// 1/(1 + exp(-x))
	ActivationFunction_Sigmoid(n, res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_TANH
// (exp(2x) -1) / (exp(2x) + 1)
	ActivationFunction_TanH(n, res, data, alpha, beta);

#elif	MLO_NRN_OP_ID==MLO_NEURON_RELU
	ActivationFunction_ReLU(n, res, data, alpha);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_BRELU
//	ActivationFunction_BReLU(n, res, data, alpha);

#elif	MLO_NRN_OP_ID==MLO_NEURON_SOFTRELU
//	log(1 + exp(x))
	ActivationFunction_BNLL(n, res, data);
#elif	MLO_NRN_OP_ID==MLO_NEURON_ABS
	ActivationFunction_Abs(n, res, data);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_SQUARE
//	ActivationFunction_Square(res, data);

//#elif	MLO_NRN_OP_ID==MLO_NEURON_SQR
//	ActivationFunction_Sqrt(n, res, data);

#elif	MLO_NRN_OP_ID==MLO_NEURON_POWER
// (shift + scale * x ) ^power

	ActivationFunction_Power(n,
							res,
							data,
							power,
							alpha,
							beta);

#endif
}

/******************************************************************************/
/*									DIFF                                      */
/******************************************************************************/

static __constant _FLOAT kBNLL_THRESHOLD = 50.;

__attribute__((always_inline))
void ActivationFunction_ReLU_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data, UNUSED _FLOAT negative_slope)
{

	for (int i = 0; i < n; ++i)
	{
		bot_diff[i] = top_diff[i] * (bot_data[i] > 0);
	}
}

__attribute__((always_inline))
void ActivationFunction_TanH_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *top_data)
{
	for(int i = 0; i < n; i++)
	{
// (exp(2x) -1) / (exp(2x) + 1)
		_FLOAT tanh_x = top_data[i];
		bot_diff[i] = top_diff[i] * (1 - tanh_x*tanh_x);
	}
}

__attribute__((always_inline))
void ActivationFunction_Sigmoid_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *top_data)
{
	for (int i = 0; i < n; i++)
	{
		// 1/(1 + exp(-x))
		_FLOAT sigmoid_x = top_data[i];
		bot_diff[i] = top_diff[i] * sigmoid_x * (1.f - sigmoid_x);
	}
}

__attribute__((always_inline))
void ActivationFunction_Abs_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data)
{
	for (int i = 0; i < n; i++)
	{
		bot_diff[i] = top_diff[i] * ((bot_data != 0 ) ? 1 : -1);
	}
}

// Compute dy/dx = scale * power * (shift + scale * x)^(power - 1)
//               = diff_scale * y / (shift + scale * x)
__attribute__((always_inline))
void ActivationFunction_Power_Diff(int n, _FLOAT * bot_diff, UNUSED const _FLOAT* top_diff, const _FLOAT *top_data, const _FLOAT *bot_data,
_FLOAT diff_scale,
UNUSED _FLOAT power,
_FLOAT scale,
_FLOAT shift)
{

	for (int i = 0; i < n; i++)
	{
		_FLOAT arg = shift + bot_data[i] * scale;
		bot_diff[i] = (arg == 0) ? 0 : diff_scale * top_data[i]/arg;

	}
}

__attribute__((always_inline))
void ActivationFunction_BNLL_Diff(int n, _FLOAT * bot_diff, const _FLOAT* top_diff, const _FLOAT *bot_data)
{
	for (int i = 0; i < n; i++)
	{
		//	(log(1 + exp(x)))' = 1/ (1 + exp(-x))
		_FLOAT expval = exp(fmin(bot_data[i], kBNLL_THRESHOLD));
		bot_diff[i] = top_diff[i] * expval / (expval + 1.f);

	}
}

__attribute__((reqd_work_group_size(MLO_NRN_GROUP_SZ0,MLO_NRN_GROUP_SZ1,MLO_NRN_GROUP_SZ2)))
__kernel void MIOpenNeuronFwd(
       const __global _FLOAT * bot,
       __global _FLOAT * top,
		_FLOAT power,
		_FLOAT scale,
		_FLOAT shift
	   )
{
	int x = get_global_id(0); // channel x

	_FLOAT data[MLO_READ_UNIT];
	_FLOAT response[MLO_READ_UNIT];
#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			data[i] = bot[x * MLO_READ_UNIT + i];
		}
		for (; i < MLO_READ_UNIT; ++i)
		{
			data[i] = 1.f;
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			data[i] = bot[x * MLO_READ_UNIT + i];
		}
	}
	ActivationFunction(MLO_READ_UNIT, response,(const _FLOAT*)data, power, scale, shift);

#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			top[x * MLO_READ_UNIT + i] = response[i];
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			top[x * MLO_READ_UNIT + i] = response[i];
		}
	}

}

__attribute__((reqd_work_group_size(MLO_NRN_GROUP_SZ0,MLO_NRN_GROUP_SZ1,MLO_NRN_GROUP_SZ2)))
__kernel void MIOpenNeuronBwd(__global _FLOAT * bot_diff,
							__global  const _FLOAT* top_diff,
							__global const _FLOAT *bot_data,
							__global  const _FLOAT *top_data,
							_FLOAT diff_scale,
							_FLOAT power,
							_FLOAT scale,
							_FLOAT shift	   )
{

	(void)diff_scale;
	(void)power;
	(void)scale;
	(void)shift;

	int x = get_global_id(0); // channel x

	_FLOAT bot_diff_dat[MLO_READ_UNIT];
	_FLOAT top_diff_dat[MLO_READ_UNIT];
	_FLOAT bot_dat[MLO_READ_UNIT];
	_FLOAT top_dat[MLO_READ_UNIT];
#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			top_diff_dat[i] = top_diff[x * MLO_READ_UNIT + i];
			bot_dat[i] = bot_data[x * MLO_READ_UNIT + i];
			top_dat[i] = top_data[x * MLO_READ_UNIT + i];
		}
		for (; i < MLO_READ_UNIT; ++i)
		{
			top_diff_dat[i] = 1.f;
			bot_dat[i] = 1.f;
			top_dat[i] = 1.f;
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			top_diff_dat[i] = top_diff[x * MLO_READ_UNIT + i];
			bot_dat[i] = bot_data[x * MLO_READ_UNIT + i];
			top_dat[i] = top_data[x * MLO_READ_UNIT + i];
		}
	}

#if		MLO_NRN_OP_ID==MLO_NEURON_RELU
{
	ActivationFunction_ReLU_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)bot_dat, scale);
}
#elif	MLO_NRN_OP_ID==MLO_NEURON_LOGISTIC
// 1/(1 + exp(-x))
	ActivationFunction_Sigmoid_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)top_dat);
#elif	MLO_NRN_OP_ID==MLO_NEURON_TANH
// (exp(2x) -1) / (exp(2x) + 1)

	ActivationFunction_TanH_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*)top_diff_dat, (const _FLOAT*)top_dat);

#elif	MLO_NRN_OP_ID==MLO_NEURON_ABS

	ActivationFunction_Abs_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *)bot_dat);
#elif	MLO_NRN_OP_ID==MLO_NEURON_POWER
// (shift + scale * x ) ^power

	ActivationFunction_Power_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *) top_dat, (const _FLOAT *)bot_dat,
							diff_scale,	power, scale, shift);

#elif	MLO_NRN_OP_ID==MLO_NEURON_SOFTRELU
//	log(1 + exp(x))
	ActivationFunction_BNLL_Diff(MLO_READ_UNIT, bot_diff_dat, (const _FLOAT*) top_diff_dat, (const _FLOAT *)bot_dat);
#endif

#if MLO_N_PIXS_OFF > 0
	if (x == MLO_MAP_SZ_ALIGNED - 1)
	{
		int i = 0;
		for (; i < MLO_N_PIXS_OFF; ++i)
		{
			bot_diff[x * MLO_READ_UNIT + i] = bot_diff_dat[i];
		}
	}
	else
#endif
	{
		for (int i = 0; i < MLO_READ_UNIT; ++i)
		{
			bot_diff[x * MLO_READ_UNIT + i] = bot_diff_dat[i];
		}
	}

}


Fix formatting

Fix formatting

#define DTYPE float
#define ACCUMTYPE float

// Must keep this structure synchronized with one in MIOpenCheckNumerics
struct CheckNumericsResult
{
    float sum;
    float absSum;
    float min;
    float max;

    int hasZero;
    int hasNan;
    int hasInf;
};

union AtomicFloat
{
    unsigned int u32;
    float f32;
};

void cl_atomic_add_float(volatile __global float* addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = current.f32 + val;

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

void cl_atomic_min_float(volatile __global float* addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = fmin(current.f32, val);

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

void cl_atomic_max_float(volatile __global float* addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = fmax(current.f32, val);

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

#define GROUP_SIZE 256
#define NUM_STATS 4

#define REDUCE_OPS(w)                                                             \
    if(lid < w)                                                                   \
    {                                                                             \
        stats[NUM_STATS * (lid) + 0] += stats[NUM_STATS * (lid + w) + 0];         \
        stats[NUM_STATS * (lid) + 1] += stats[NUM_STATS * (lid + w) + 1];         \
        stats[NUM_STATS * (lid) + 2] =                                            \
            fmin(stats[NUM_STATS * (lid) + 2], stats[NUM_STATS * (lid + w) + 2]); \
        stats[NUM_STATS * (lid) + 3] =                                            \
            fmax(stats[NUM_STATS * (lid) + 3], stats[NUM_STATS * (lid + w) + 3]); \
        barrier(CLK_LOCAL_MEM_FENCE);                                             \
    }

// Checks a block of data for abnormal numeric values :
__kernel void MIOpenCheckNumerics(const __global DTYPE* data,
                                  int size,
                                  __global struct CheckNumericsResult* abnormal,
                                  int computeStats)
{
    const int lid           = get_local_id(0);
    const int gid           = get_global_id(0);
    const int total_wi_size = get_global_size(0);

    local float stats[4 * GROUP_SIZE];

    int offset       = gid;
    ACCUMTYPE sum    = 0.0f;
    ACCUMTYPE abssum = 0.0f;
    DTYPE minV       = FLT_MAX;
    DTYPE maxV       = FLT_MIN;
    while(offset < size)
    {
        DTYPE value = data[offset];
        sum += value;
        abssum += fabs(value);
        minV = min(minV, value);
        maxV = max(maxV, value);

        if(fabs(value) <= 0.0f)
        { // iszero check
            abnormal->hasZero = 1;
        }
        if(isnan(value))
        {
            abnormal->hasNan = 1;
        }
        if(isinf(value))
        {
            abnormal->hasInf = 1;
        }
        offset += total_wi_size;
    }

    if(computeStats)
    {
        stats[NUM_STATS * lid + 0] = sum;
        stats[NUM_STATS * lid + 1] = abssum;
        stats[NUM_STATS * lid + 2] = minV;
        stats[NUM_STATS * lid + 3] = maxV;
        barrier(CLK_LOCAL_MEM_FENCE);

        REDUCE_OPS(128)
        REDUCE_OPS(64)
        REDUCE_OPS(32)
        REDUCE_OPS(16)
        REDUCE_OPS(8)
        REDUCE_OPS(4)
        REDUCE_OPS(2)
        REDUCE_OPS(1)

        if(lid == 0)
        {
            cl_atomic_add_float(&abnormal->sum, stats[0]);
            cl_atomic_add_float(&abnormal->absSum, stats[1]);
            cl_atomic_min_float(&abnormal->min, stats[2]);
            cl_atomic_max_float(&abnormal->max, stats[3]);
        }
    }
}

#define DTYPE float
#define ACCUMTYPE float

// Must keep this structure synchronized with one in MIOpenCheckNumerics
struct CheckNumericsResult
{
    float     sum;
    float     absSum;
    float     min;
    float     max;

    int       hasZero;
    int       hasNan;
    int       hasInf;
};

union AtomicFloat
{
    unsigned int u32;
    float f32;
};

void cl_atomic_add_float (volatile __global float *addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = current.f32 + val;

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

void cl_atomic_min_float (volatile __global float *addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = fmin(current.f32, val);

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

void cl_atomic_max_float (volatile __global float *addr, float val)
{
    union AtomicFloat current, expected, next;

    current.f32 = *addr;
    do
    {
        expected.f32 = current.f32;
        next.f32     = fmax(current.f32, val);

        current.u32 = atomic_cmpxchg((volatile __global unsigned int*)addr, expected.u32, next.u32);
    } while(current.u32 != expected.u32);
}

#define GROUP_SIZE 256
#define NUM_STATS 4

#define REDUCE_OPS(w)                                                             \
    if(lid < w)                                                                   \
    {                                                                             \
        stats[NUM_STATS * (lid) + 0] += stats[NUM_STATS * (lid + w) + 0];         \
        stats[NUM_STATS * (lid) + 1] += stats[NUM_STATS * (lid + w) + 1];         \
        stats[NUM_STATS * (lid) + 2] =                                            \
            fmin(stats[NUM_STATS * (lid) + 2], stats[NUM_STATS * (lid + w) + 2]); \
        stats[NUM_STATS * (lid) + 3] =                                            \
            fmax(stats[NUM_STATS * (lid) + 3], stats[NUM_STATS * (lid + w) + 3]); \
        barrier(CLK_LOCAL_MEM_FENCE);                                             \
    }

// Checks a block of data for abnormal numeric values :
__kernel void MIOpenCheckNumerics(const __global DTYPE* data,
                                  int size,
                                  __global struct CheckNumericsResult* abnormal,
                                  int computeStats)
{
    const int lid           = get_local_id(0);
    const int gid           = get_global_id(0);
    const int total_wi_size = get_global_size(0);

    local float stats[4 * GROUP_SIZE];

    int offset       = gid;
    ACCUMTYPE sum    = 0.0f;
    ACCUMTYPE abssum = 0.0f;
    DTYPE minV       = FLT_MAX;
    DTYPE maxV       = FLT_MIN;
    while(offset < size)
    {
        DTYPE value = data[offset];
        sum += value;
        abssum += fabs(value);
        minV = min(minV, value);
        maxV = max(maxV, value);

        if (fabs(value) <= 0.0f) { // iszero check
           abnormal->hasZero = 1;
        }
        if (isnan(value)) {
            abnormal->hasNan  = 1;
        }
        if (isinf(value)) {
            abnormal->hasInf  = 1;
        }
        offset += total_wi_size;
    }

    if(computeStats)
    {
        stats[NUM_STATS * lid + 0] = sum;
        stats[NUM_STATS * lid + 1] = abssum;
        stats[NUM_STATS * lid + 2] = minV;
        stats[NUM_STATS * lid + 3] = maxV;
        barrier(CLK_LOCAL_MEM_FENCE);

        REDUCE_OPS(128)
        REDUCE_OPS(64)
        REDUCE_OPS(32)
        REDUCE_OPS(16)
        REDUCE_OPS(8)
        REDUCE_OPS(4)
        REDUCE_OPS(2)
        REDUCE_OPS(1)

        if (lid == 0) {
          cl_atomic_add_float(&abnormal->sum,    stats[0]);
          cl_atomic_add_float(&abnormal->absSum, stats[1]);
          cl_atomic_min_float(&abnormal->min,    stats[2]);
          cl_atomic_max_float(&abnormal->max,    stats[3]);
        }
    }
}


Fixed bug in GMM kernel

Fixed bug in GMM kernel

// #include "fetchpair.cl"
// #include "linalg.cl"
// #include "outlier.cl"

typedef struct
{
   float pi;
   Vector2 mu;
   Matrix2x2 sigma;
   Matrix2x2 sigmaInv;
   float normalizer;
} Component;

typedef struct
{
   __global Component *components;
   int K;
   float logL;
   float entropy;
   __global Vector2 *_Mu;
   __global int *_counts;
   __global float *_logpi;
   __global float *_loggamma;
   __global float *_logGamma;
   float _logGammaSum;
} GMM;

/*!
 * Initialize a mixture component with the given mixture weight and mean.
 *
 * @param component
 * @param pi
 * @param mu
 */
void GMM_Component_initialize(
   __global Component *component,
   float pi,
   __global const Vector2 *mu)
{
   // initialize mixture weight and mean
   component->pi = pi;
   component->mu = *mu;

   // initialize covariance to identity matrix
   matrixInitIdentity(&component->sigma);

   // initialize precision to zero matrix
   matrixInitZero(&component->sigmaInv);

   // initialize normalizer term to 0
   component->normalizer = 0;
}

/*!
 * Pre-compute the precision matrix and normalizer term for a mixture component.
 *
 * @param component
 */
bool GMM_Component_prepare(__global Component *component)
{
   const int D = 2;

   // compute precision (inverse of covariance)
   float det;
   matrixInverse(&component->sigma, &component->sigmaInv, &det);

   // return failure if matrix inverse failed
   if ( det <= 0 )
   {
      return false;
   }

   // compute normalizer term for multivariate normal distribution
   component->normalizer = -0.5f * (D * log(2.0f * M_PI) + log(det));

   // return success
   return true;
}

/*!
 * Compute the log of the probability density function of the multivariate normal
 * distribution conditioned on a single component for each point in X:
 *
 *   P(x|k) = exp(-0.5 * (x - mu)^T Sigma^-1 (x - mu)) / sqrt((2pi)^d det(Sigma))
 *
 * Therefore the log-probability is:
 *
 *   log(P(x|k)) = -0.5 * (x - mu)^T Sigma^-1 (x - mu) - 0.5 * (d * log(2pi) + log(det(Sigma)))
 *
 * @param component
 * @param X
 * @param N
 * @param logP
 */
void GMM_Component_computeLogProbNorm(
   __global const Component *component,
   __global const Vector2 *X, int N,
   __global float *logP)
{
   for (int i = 0; i < N; ++i)
   {
      // compute xm = (x - mu)
      Vector2 xm = X[i];
      vectorSubtract(&xm, &component->mu);

      // compute Sxm = Sigma^-1 xm
      Vector2 Sxm;
      matrixProduct(&component->sigmaInv, &xm, &Sxm);

      // compute xmSxm = xm^T Sigma^-1 xm
      float xmSxm = vectorDot(&xm, &Sxm);

      // compute log(P) = normalizer - 0.5 * xm^T * Sigma^-1 * xm
      logP[i] = component->normalizer - 0.5f * xmSxm;
   }
}

/*!
 * Initialize the mean of each component in the mixture model using k-means
 * clustering.
 *
 * @param gmm
 * @param X
 * @param N
 */
void GMM_initializeMeans(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   const int MAX_ITERATIONS = 20;
   const float TOLERANCE = 1e-3;
   float diff = 0;

   // initialize workspace
   __global Vector2 *Mu = gmm->_Mu;
   __global int *counts = gmm->_counts;

   for (int t = 0; t < MAX_ITERATIONS && diff > TOLERANCE; ++t)
   {
      // compute mean and sample count for each component
      for (int k = 0; k < K; ++k)
      {
         vectorInitZero(&Mu[k]);
         counts[k] = 0;
      }

      for (int i = 0; i < N; ++i)
      {
         // determine the component mean which is nearest to x_i
         float min_dist = INFINITY;
         int min_k = 0;
         for (int k = 0; k < K; ++k)
         {
            float dist = vectorDiffNorm(&X[i], &gmm->components[k].mu);
            if (min_dist > dist)
            {
               min_dist = dist;
               min_k = k;
            }
         }

         // update mean and sample count
         vectorAdd(&Mu[min_k], &X[i]);
         ++counts[min_k];
      }

      // scale each mean by its sample count
      for (int k = 0; k < K; ++k)
      {
         vectorScale(&Mu[k], 1.0f / counts[k]);
      }

      // compute the total change of all means
      diff = 0;
      for (int k = 0; k < K; ++k)
      {
         diff += vectorDiffNorm(&Mu[k], &gmm->components[k].mu);
      }
      diff /= K;

      // update component means
      for (int k = 0; k < K; ++k)
      {
         gmm->components[k].mu = Mu[k];
      }
   }
}

/*!
 * Perform the expectation step of the EM algorithm. In this step we compute
 * loggamma, the posterior probabilities for each component in the mixture model
 * and each sample in X, as well as logGamma, the sum of loggamma across samples,
 * and logGammaSum, another term used in the maximization step:
 *
 *   log(gamma_ki) = log(P(x_i|k)) - log(p(x))
 *
 *   log(p(x)) = a + log(sum(exp(logpi_k + logProb_ki) - a))
 *
 *   log(Gamma_k) = a + log(sum(exp(loggamma_ki) - a))
 *
 *   logGammaSum = a + log(sum(exp(logpi_k + logGamma_k) - a))
 *
 * @param gmm
 * @param X
 * @param N
 */
float GMM_computeEStep(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   // compute the log-probabilities for each component and each point in X
   __global float *logProb = gmm->_loggamma;

   for ( int k = 0; k < K; ++k )
   {
      GMM_Component_computeLogProbNorm(&gmm->components[k], X, N, &logProb[k * N]);
   }

   // compute loggamma and the log-likelihood
   float logL = 0.0;

   for (int i = 0; i < N; ++i)
   {
      // compute a = argmax(logpi_k + logProb_ki, k)
      float maxArg = -INFINITY;
      for (int k = 0; k < K; ++k)
      {
         float arg = gmm->_logpi[k] + logProb[k * N + i];
         if (maxArg < arg)
         {
            maxArg = arg;
         }
      }

      // compute logpx
      float sum = 0.0;
      for (int k = 0; k < K; ++k)
      {
         sum += exp(gmm->_logpi[k] + logProb[k * N + i] - maxArg);
      }

      float logpx = maxArg + log(sum);

      // update loggamma_ki
      for (int k = 0; k < K; ++k)
      {
         gmm->_loggamma[k * N + i] -= logpx;
      }

      // update log-likelihood
      logL += logpx;
   }

   // compute logGamma
   for (int k = 0; k < K; ++k)
   {
      __global const float *loggamma_k = &gmm->_loggamma[k * N];

      // compute a = argmax(loggamma_ki, i)
      float maxArg = -INFINITY;
      for (int i = 0; i < N; ++i)
      {
         float arg = loggamma_k[i];
         if (maxArg < arg)
         {
            maxArg = arg;
         }
      }

      // compute logGamma_k
      float sum = 0;
      for (int i = 0; i < N; ++i)
      {
         sum += exp(loggamma_k[i] - maxArg);
      }

      gmm->_logGamma[k] = maxArg + log(sum);
   }

   // compute logGammaSum
   float maxArg = -INFINITY;
   for (int k = 0; k < K; ++k)
   {
      float arg = gmm->_logpi[k] + gmm->_logGamma[k];
      if (maxArg < arg)
      {
         maxArg = arg;
      }
   }

   float sum = 0;
   for (int k = 0; k < K; ++k)
   {
      sum += exp(gmm->_logpi[k] + gmm->_logGamma[k] - maxArg);
   }

   gmm->_logGammaSum = maxArg + log(sum);

   // return log-likelihood
   return logL;
}

/*!
 * Perform the maximization step of the EM algorithm. In this step we update the
 * parameters of the the mixture model using loggamma, logGamma, and logGammaSum,
 * which are computed during the expectation step:
 *
 *   pi_k = exp(logpi_k + logGamma_k - logGammaSum)
 *
 *   mu_k = sum(exp(loggamma_ki) * x_i)) / exp(logGamma_k)
 *
 *   Sigma_k = sum(exp(loggamma_ki) * (x_i - mu_k) * (x_i - mu_k)^T) / exp(logGamma_k)
 *
 * @param gmm
 * @param X
 * @param N
 */
bool GMM_computeMStep(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   // update mixture weight
   for (int k = 0; k < K; ++k)
   {
      gmm->_logpi[k] += gmm->_logGamma[k] - gmm->_logGammaSum;

      gmm->components[k].pi = exp(gmm->_logpi[k]);
   }

   // convert loggamma to gamma
   for (int k = 0; k < K; ++k)
   {
      for (int i = 0; i < N; ++i)
      {
         gmm->_loggamma[k * N + i] = exp(gmm->_loggamma[k * N + i]);
      }
   }

   // convert logGamma to Gamma
   for (int k = 0; k < K; ++k)
   {
      gmm->_logGamma[k] = exp(gmm->_logGamma[k]);
   }

   // update remaining parameters
   for (int k = 0; k < K; ++k)
   {
      // update mean
      __global Vector2 *mu = &gmm->components[k].mu;

      vectorInitZero(mu);

      for (int i = 0; i < N; ++i)
      {
         vectorAddScaled(mu, gmm->_loggamma[k * N + i], &X[i]);
      }

      vectorScale(mu, 1.0f / gmm->_logGamma[k]);

      // update covariance matrix
      __global Matrix2x2 *sigma = &gmm->components[k].sigma;

      matrixInitZero(sigma);

      for (int i = 0; i < N; ++i)
      {
         // compute xm = (x_i - mu_k)
         Vector2 xm = X[i];
         vectorSubtract(&xm, mu);

         // compute Sigma_ki = gamma_ki * (x_i - mu_k) (x_i - mu_k)^T
         Matrix2x2 outerProduct;
         matrixOuterProduct(&xm, &xm, &outerProduct);

         matrixAddScaled(sigma, gmm->_loggamma[k * N + i], &outerProduct);
      }

      matrixScale(sigma, 1.0f / gmm->_logGamma[k]);

      // pre-compute precision matrix and normalizer term
      bool success = GMM_Component_prepare(&gmm->components[k]);

      // return failure if matrix inverse failed
      if ( !success )
      {
         return false;
      }
   }

   // return success
   return true;
}

/*!
 * Compute the cluster labels of a dataset using gamma:
 *
 *   y_i = argmax(gamma_ki, k)
 *
 * @param gamma
 * @param N
 * @param K
 * @param labels
 */
void GMM_computeLabels(
   __global const float *gamma, int N, int K,
   __global char *labels)
{
   for ( int i = 0; i < N; ++i )
   {
      // determine the value k for which gamma_ki is highest
      int max_k = -1;
      float max_gamma = -INFINITY;

      for ( int k = 0; k < K; ++k )
      {
         if ( max_gamma < gamma[k * N + i] )
         {
            max_k = k;
            max_gamma = gamma[k * N + i];
         }
      }

      // assign x_i to cluster k
      labels[i] = max_k;
   }
}

/*!
 * Compute the entropy of the mixture model for a dataset using gamma
 * and the given cluster labels:
 *
 *   E = sum(sum(z_ki * log(gamma_ki))), z_ki = (y_i == k)
 *
 * @param gamma
 * @param N
 * @param labels
 */
float GMM_computeEntropy(
   __global const float *gamma, int N,
   __global const char *labels)
{
   float E = 0;

   for ( int i = 0; i < N; ++i )
   {
      int k = labels[i];

      E += log(gamma[k * N + i]);
   }

   return E;
}

/*!
 * Fit the mixture model to a pairwise data array and compute the output cluster
 * labels for the data. The data array should only contain clean samples.
 *
 * @param gmm
 * @param X
 * @param N
 * @param K
 * @param labels
 */
bool GMM_fit(
   GMM *gmm,
   __global const Vector2 *X, int N, int K,
   __global char *labels)
{
   ulong state = 1;

   // initialize components
   gmm->K = K;

   for ( int k = 0; k < K; ++k )
   {
      // use uniform mixture weight and randomly sampled mean
      int i = rand(&state) % N;

      GMM_Component_initialize(&gmm->components[k], 1.0f / K, &X[i]);
      GMM_Component_prepare(&gmm->components[k]);
   }

   // initialize means with k-means
   GMM_initializeMeans(gmm, X, N);

   // initialize workspace
   for (int k = 0; k < K; ++k)
   {
      gmm->_logpi[k] = log(gmm->components[k].pi);
   }

   // run EM algorithm
   const int MAX_ITERATIONS = 100;
   const float TOLERANCE = 1e-8;
   float prevLogL = -INFINITY;
   float currLogL = -INFINITY;

   for ( int t = 0; t < MAX_ITERATIONS; ++t )
   {
      // perform E step
      prevLogL = currLogL;
      currLogL = GMM_computeEStep(gmm, X, N);

      // check for convergence
      if ( fabs(currLogL - prevLogL) < TOLERANCE )
      {
         break;
      }

      // perform M step
      bool success = GMM_computeMStep(gmm, X, N);

      // return failure if M-step failed (due to matrix inverse)
      if ( !success )
      {
         return false;
      }
   }

   // save outputs
   gmm->logL = currLogL;
   GMM_computeLabels(gmm->_loggamma, N, K, labels);
   gmm->entropy = GMM_computeEntropy(gmm->_loggamma, N, labels);

   return true;
}

typedef enum
{
   AIC,
   BIC,
   ICL
} Criterion;

/*!
 * Compute the Akaike Information Criterion of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 */
float GMM_computeAIC(int K, int D, float logL)
{
   int p = K * (1 + D + D * D);

   return 2 * p - 2 * logL;
}

/*!
 * Compute the Bayesian Information Criterion of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 * @param N
 */
float GMM_computeBIC(int K, int D, float logL, int N)
{
   int p = K * (1 + D + D * D);

   return log((float) N) * p - 2 * logL;
}

/*!
 * Compute the Integrated Completed Likelihood of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 * @param N
 * @param E
 */
float GMM_computeICL(int K, int D, float logL, int N, float E)
{
   int p = K * (1 + D + D * D);

   return log((float) N) * p - 2 * logL - 2 * E;
}

/*!
 * Determine the number of clusters in a pairwise data array. Several sub-models,
 * each one having a different number of clusters, are fit to the data and the
 * sub-model with the best criterion value is selected. The data array should
 * only contain samples that have a non-negative label.
 */
__kernel void GMM_compute(
   int sampleSize,
   int minSamples,
   char minClusters,
   char maxClusters,
   Criterion criterion,
   int removePreOutliers,
   int removePostOutliers,
   __global Vector2 *work_X,
   __global int *work_N,
   __global float *work_x,
   __global float *work_y,
   __global char *work_labels,
   __global Component *work_components,
   __global Vector2 *work_MP,
   __global int *work_counts,
   __global float *work_logpi,
   __global float *work_loggamma,
   __global float *work_logGamma,
   __global char *out_K,
   __global char *out_labels)
{
   int i = get_global_id(0);

   // initialize workspace variables
   __global Vector2 *data = &work_X[i * sampleSize];
   int numSamples = work_N[i];
   __global float *x_sorted = &work_x[i * sampleSize];
   __global float *y_sorted = &work_y[i * sampleSize];
   __global char *labels = &work_labels[i * sampleSize];
   __global Component *components = &work_components[i * maxClusters];
   __global Vector2 *Mu = &work_MP[i * maxClusters];
   __global int *counts = &work_counts[i * maxClusters];
   __global float *logpi = &work_logpi[i * maxClusters];
   __global float *loggamma = &work_loggamma[i * maxClusters * sampleSize];
   __global float *logGamma = &work_logGamma[i * maxClusters];
   __global char *bestK = &out_K[i];
   __global char *bestLabels = &out_labels[i * sampleSize];

   // initialize GMM struct
   GMM gmm = {
      .components = components,
      ._Mu = Mu,
      ._counts = counts,
      ._logpi = logpi,
      ._loggamma = loggamma,
      ._logGamma = logGamma
   };

   // remove pre-clustering outliers
   if ( removePreOutliers )
   {
      numSamples = removeOutliers(data, bestLabels, sampleSize, 0, -7, x_sorted, y_sorted);
   }

   // perform clustering only if there are enough samples
   *bestK = 0;

   if ( numSamples >= minSamples )
   {
      float bestValue = INFINITY;

      for ( char K = minClusters; K <= maxClusters; ++K )
      {
         // run each clustering sub-model
         bool success = GMM_fit(&gmm, data, numSamples, K, labels);

         if ( !success )
         {
            continue;
         }

         // compute the criterion value of the sub-model
         float value = INFINITY;

         switch (criterion)
         {
         case AIC:
            value = GMM_computeAIC(K, 2, gmm.logL);
            break;
         case BIC:
            value = GMM_computeBIC(K, 2, gmm.logL, numSamples);
            break;
         case ICL:
            value = GMM_computeICL(K, 2, gmm.logL, numSamples, gmm.entropy);
            break;
         }

         // save the best model
         if ( value < bestValue )
         {
            *bestK = K;
            bestValue = value;

            for ( int i = 0, j = 0; i < numSamples; ++i )
            {
               if ( bestLabels[i] >= 0 )
               {
                  bestLabels[i] = labels[j];
                  ++j;
               }
            }
         }
      }
   }

   // remove post-clustering outliers
   if ( *bestK > 1 && removePostOutliers )
   {
      for ( char k = 0; k < *bestK; ++k )
      {
         numSamples = removeOutliers(data, bestLabels, sampleSize, k, -8, x_sorted, y_sorted);
      }
   }
}

// #include "fetchpair.cl"
// #include "linalg.cl"
// #include "outlier.cl"

typedef struct
{
   float pi;
   Vector2 mu;
   Matrix2x2 sigma;
   Matrix2x2 sigmaInv;
   float normalizer;
} Component;

typedef struct
{
   __global Component *components;
   int K;
   float logL;
   float entropy;
   __global Vector2 *_Mu;
   __global int *_counts;
   __global float *_logpi;
   __global float *_loggamma;
   __global float *_logGamma;
   float _logGammaSum;
} GMM;

/*!
 * Initialize a mixture component with the given mixture weight and mean.
 *
 * @param component
 * @param pi
 * @param mu
 */
void GMM_Component_initialize(
   __global Component *component,
   float pi,
   __global const Vector2 *mu)
{
   // initialize mixture weight and mean
   component->pi = pi;
   component->mu = *mu;

   // initialize covariance to identity matrix
   matrixInitIdentity(&component->sigma);

   // initialize precision to zero matrix
   matrixInitZero(&component->sigmaInv);

   // initialize normalizer term to 0
   component->normalizer = 0;
}

/*!
 * Pre-compute the precision matrix and normalizer term for a mixture component.
 *
 * @param component
 */
bool GMM_Component_prepare(__global Component *component)
{
   const int D = 2;

   // compute precision (inverse of covariance)
   float det;
   matrixInverse(&component->sigma, &component->sigmaInv, &det);

   // return failure if matrix inverse failed
   if ( fabs(det) <= 0 )
   {
      return false;
   }

   // compute normalizer term for multivariate normal distribution
   component->normalizer = -0.5f * (D * log(2.0f * M_PI) + log(det));

   // return success
   return true;
}

/*!
 * Compute the log of the probability density function of the multivariate normal
 * distribution conditioned on a single component for each point in X:
 *
 *   P(x|k) = exp(-0.5 * (x - mu)^T Sigma^-1 (x - mu)) / sqrt((2pi)^d det(Sigma))
 *
 * Therefore the log-probability is:
 *
 *   log(P(x|k)) = -0.5 * (x - mu)^T Sigma^-1 (x - mu) - 0.5 * (d * log(2pi) + log(det(Sigma)))
 *
 * @param component
 * @param X
 * @param N
 * @param logP
 */
void GMM_Component_computeLogProbNorm(
   __global const Component *component,
   __global const Vector2 *X, int N,
   __global float *logP)
{
   for (int i = 0; i < N; ++i)
   {
      // compute xm = (x - mu)
      Vector2 xm = X[i];
      vectorSubtract(&xm, &component->mu);

      // compute Sxm = Sigma^-1 xm
      Vector2 Sxm;
      matrixProduct(&component->sigmaInv, &xm, &Sxm);

      // compute xmSxm = xm^T Sigma^-1 xm
      float xmSxm = vectorDot(&xm, &Sxm);

      // compute log(P) = normalizer - 0.5 * xm^T * Sigma^-1 * xm
      logP[i] = component->normalizer - 0.5f * xmSxm;
   }
}

/*!
 * Initialize the mean of each component in the mixture model using k-means
 * clustering.
 *
 * @param gmm
 * @param X
 * @param N
 */
void GMM_initializeMeans(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   const int MAX_ITERATIONS = 20;
   const float TOLERANCE = 1e-3;
   float diff = 0;

   // initialize workspace
   __global Vector2 *Mu = gmm->_Mu;
   __global int *counts = gmm->_counts;

   for (int t = 0; t < MAX_ITERATIONS && diff > TOLERANCE; ++t)
   {
      // compute mean and sample count for each component
      for (int k = 0; k < K; ++k)
      {
         vectorInitZero(&Mu[k]);
         counts[k] = 0;
      }

      for (int i = 0; i < N; ++i)
      {
         // determine the component mean which is nearest to x_i
         float min_dist = INFINITY;
         int min_k = 0;
         for (int k = 0; k < K; ++k)
         {
            float dist = vectorDiffNorm(&X[i], &gmm->components[k].mu);
            if (min_dist > dist)
            {
               min_dist = dist;
               min_k = k;
            }
         }

         // update mean and sample count
         vectorAdd(&Mu[min_k], &X[i]);
         ++counts[min_k];
      }

      // scale each mean by its sample count
      for (int k = 0; k < K; ++k)
      {
         vectorScale(&Mu[k], 1.0f / counts[k]);
      }

      // compute the total change of all means
      diff = 0;
      for (int k = 0; k < K; ++k)
      {
         diff += vectorDiffNorm(&Mu[k], &gmm->components[k].mu);
      }
      diff /= K;

      // update component means
      for (int k = 0; k < K; ++k)
      {
         gmm->components[k].mu = Mu[k];
      }
   }
}

/*!
 * Perform the expectation step of the EM algorithm. In this step we compute
 * loggamma, the posterior probabilities for each component in the mixture model
 * and each sample in X, as well as logGamma, the sum of loggamma across samples,
 * and logGammaSum, another term used in the maximization step:
 *
 *   log(gamma_ki) = log(P(x_i|k)) - log(p(x))
 *
 *   log(p(x)) = a + log(sum(exp(logpi_k + logProb_ki) - a))
 *
 *   log(Gamma_k) = a + log(sum(exp(loggamma_ki) - a))
 *
 *   logGammaSum = a + log(sum(exp(logpi_k + logGamma_k) - a))
 *
 * @param gmm
 * @param X
 * @param N
 */
float GMM_computeEStep(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   // compute the log-probabilities for each component and each point in X
   __global float *logProb = gmm->_loggamma;

   for ( int k = 0; k < K; ++k )
   {
      GMM_Component_computeLogProbNorm(&gmm->components[k], X, N, &logProb[k * N]);
   }

   // compute loggamma and the log-likelihood
   float logL = 0.0;

   for (int i = 0; i < N; ++i)
   {
      // compute a = argmax(logpi_k + logProb_ki, k)
      float maxArg = -INFINITY;
      for (int k = 0; k < K; ++k)
      {
         float arg = gmm->_logpi[k] + logProb[k * N + i];
         if (maxArg < arg)
         {
            maxArg = arg;
         }
      }

      // compute logpx
      float sum = 0.0;
      for (int k = 0; k < K; ++k)
      {
         sum += exp(gmm->_logpi[k] + logProb[k * N + i] - maxArg);
      }

      float logpx = maxArg + log(sum);

      // update loggamma_ki
      for (int k = 0; k < K; ++k)
      {
         gmm->_loggamma[k * N + i] -= logpx;
      }

      // update log-likelihood
      logL += logpx;
   }

   // compute logGamma
   for (int k = 0; k < K; ++k)
   {
      __global const float *loggamma_k = &gmm->_loggamma[k * N];

      // compute a = argmax(loggamma_ki, i)
      float maxArg = -INFINITY;
      for (int i = 0; i < N; ++i)
      {
         float arg = loggamma_k[i];
         if (maxArg < arg)
         {
            maxArg = arg;
         }
      }

      // compute logGamma_k
      float sum = 0;
      for (int i = 0; i < N; ++i)
      {
         sum += exp(loggamma_k[i] - maxArg);
      }

      gmm->_logGamma[k] = maxArg + log(sum);
   }

   // compute logGammaSum
   float maxArg = -INFINITY;
   for (int k = 0; k < K; ++k)
   {
      float arg = gmm->_logpi[k] + gmm->_logGamma[k];
      if (maxArg < arg)
      {
         maxArg = arg;
      }
   }

   float sum = 0;
   for (int k = 0; k < K; ++k)
   {
      sum += exp(gmm->_logpi[k] + gmm->_logGamma[k] - maxArg);
   }

   gmm->_logGammaSum = maxArg + log(sum);

   // return log-likelihood
   return logL;
}

/*!
 * Perform the maximization step of the EM algorithm. In this step we update the
 * parameters of the the mixture model using loggamma, logGamma, and logGammaSum,
 * which are computed during the expectation step:
 *
 *   pi_k = exp(logpi_k + logGamma_k - logGammaSum)
 *
 *   mu_k = sum(exp(loggamma_ki) * x_i)) / exp(logGamma_k)
 *
 *   Sigma_k = sum(exp(loggamma_ki) * (x_i - mu_k) * (x_i - mu_k)^T) / exp(logGamma_k)
 *
 * @param gmm
 * @param X
 * @param N
 */
bool GMM_computeMStep(GMM *gmm, __global const Vector2 *X, int N)
{
   const int K = gmm->K;

   // update mixture weight
   for (int k = 0; k < K; ++k)
   {
      gmm->_logpi[k] += gmm->_logGamma[k] - gmm->_logGammaSum;

      gmm->components[k].pi = exp(gmm->_logpi[k]);
   }

   // convert loggamma to gamma
   for (int k = 0; k < K; ++k)
   {
      for (int i = 0; i < N; ++i)
      {
         gmm->_loggamma[k * N + i] = exp(gmm->_loggamma[k * N + i]);
      }
   }

   // convert logGamma to Gamma
   for (int k = 0; k < K; ++k)
   {
      gmm->_logGamma[k] = exp(gmm->_logGamma[k]);
   }

   // update remaining parameters
   for (int k = 0; k < K; ++k)
   {
      // update mean
      __global Vector2 *mu = &gmm->components[k].mu;

      vectorInitZero(mu);

      for (int i = 0; i < N; ++i)
      {
         vectorAddScaled(mu, gmm->_loggamma[k * N + i], &X[i]);
      }

      vectorScale(mu, 1.0f / gmm->_logGamma[k]);

      // update covariance matrix
      __global Matrix2x2 *sigma = &gmm->components[k].sigma;

      matrixInitZero(sigma);

      for (int i = 0; i < N; ++i)
      {
         // compute xm = (x_i - mu_k)
         Vector2 xm = X[i];
         vectorSubtract(&xm, mu);

         // compute Sigma_ki = gamma_ki * (x_i - mu_k) (x_i - mu_k)^T
         Matrix2x2 outerProduct;
         matrixOuterProduct(&xm, &xm, &outerProduct);

         matrixAddScaled(sigma, gmm->_loggamma[k * N + i], &outerProduct);
      }

      matrixScale(sigma, 1.0f / gmm->_logGamma[k]);

      // pre-compute precision matrix and normalizer term
      bool success = GMM_Component_prepare(&gmm->components[k]);

      // return failure if matrix inverse failed
      if ( !success )
      {
         return false;
      }
   }

   // return success
   return true;
}

/*!
 * Compute the cluster labels of a dataset using gamma:
 *
 *   y_i = argmax(gamma_ki, k)
 *
 * @param gamma
 * @param N
 * @param K
 * @param labels
 */
void GMM_computeLabels(
   __global const float *gamma, int N, int K,
   __global char *labels)
{
   for ( int i = 0; i < N; ++i )
   {
      // determine the value k for which gamma_ki is highest
      int max_k = -1;
      float max_gamma = -INFINITY;

      for ( int k = 0; k < K; ++k )
      {
         if ( max_gamma < gamma[k * N + i] )
         {
            max_k = k;
            max_gamma = gamma[k * N + i];
         }
      }

      // assign x_i to cluster k
      labels[i] = max_k;
   }
}

/*!
 * Compute the entropy of the mixture model for a dataset using gamma
 * and the given cluster labels:
 *
 *   E = sum(sum(z_ki * log(gamma_ki))), z_ki = (y_i == k)
 *
 * @param gamma
 * @param N
 * @param labels
 */
float GMM_computeEntropy(
   __global const float *gamma, int N,
   __global const char *labels)
{
   float E = 0;

   for ( int i = 0; i < N; ++i )
   {
      int k = labels[i];

      E += log(gamma[k * N + i]);
   }

   return E;
}

/*!
 * Fit the mixture model to a pairwise data array and compute the output cluster
 * labels for the data. The data array should only contain clean samples.
 *
 * @param gmm
 * @param X
 * @param N
 * @param K
 * @param labels
 */
bool GMM_fit(
   GMM *gmm,
   __global const Vector2 *X, int N, int K,
   __global char *labels)
{
   ulong state = 1;

   // initialize components
   gmm->K = K;

   for ( int k = 0; k < K; ++k )
   {
      // use uniform mixture weight and randomly sampled mean
      int i = rand(&state) % N;

      GMM_Component_initialize(&gmm->components[k], 1.0f / K, &X[i]);
      GMM_Component_prepare(&gmm->components[k]);
   }

   // initialize means with k-means
   GMM_initializeMeans(gmm, X, N);

   // initialize workspace
   for (int k = 0; k < K; ++k)
   {
      gmm->_logpi[k] = log(gmm->components[k].pi);
   }

   // run EM algorithm
   const int MAX_ITERATIONS = 100;
   const float TOLERANCE = 1e-8;
   float prevLogL = -INFINITY;
   float currLogL = -INFINITY;

   for ( int t = 0; t < MAX_ITERATIONS; ++t )
   {
      // perform E step
      prevLogL = currLogL;
      currLogL = GMM_computeEStep(gmm, X, N);

      // check for convergence
      if ( fabs(currLogL - prevLogL) < TOLERANCE )
      {
         break;
      }

      // perform M step
      bool success = GMM_computeMStep(gmm, X, N);

      // return failure if M-step failed (due to matrix inverse)
      if ( !success )
      {
         return false;
      }
   }

   // save outputs
   gmm->logL = currLogL;
   GMM_computeLabels(gmm->_loggamma, N, K, labels);
   gmm->entropy = GMM_computeEntropy(gmm->_loggamma, N, labels);

   return true;
}

typedef enum
{
   AIC,
   BIC,
   ICL
} Criterion;

/*!
 * Compute the Akaike Information Criterion of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 */
float GMM_computeAIC(int K, int D, float logL)
{
   int p = K * (1 + D + D * D);

   return 2 * p - 2 * logL;
}

/*!
 * Compute the Bayesian Information Criterion of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 * @param N
 */
float GMM_computeBIC(int K, int D, float logL, int N)
{
   int p = K * (1 + D + D * D);

   return log((float) N) * p - 2 * logL;
}

/*!
 * Compute the Integrated Completed Likelihood of a Gaussian mixture model.
 *
 * @param K
 * @param D
 * @param logL
 * @param N
 * @param E
 */
float GMM_computeICL(int K, int D, float logL, int N, float E)
{
   int p = K * (1 + D + D * D);

   return log((float) N) * p - 2 * logL - 2 * E;
}

/*!
 * Determine the number of clusters in a pairwise data array. Several sub-models,
 * each one having a different number of clusters, are fit to the data and the
 * sub-model with the best criterion value is selected. The data array should
 * only contain samples that have a non-negative label.
 */
__kernel void GMM_compute(
   int sampleSize,
   int minSamples,
   char minClusters,
   char maxClusters,
   Criterion criterion,
   int removePreOutliers,
   int removePostOutliers,
   __global Vector2 *work_X,
   __global int *work_N,
   __global float *work_x,
   __global float *work_y,
   __global char *work_labels,
   __global Component *work_components,
   __global Vector2 *work_MP,
   __global int *work_counts,
   __global float *work_logpi,
   __global float *work_loggamma,
   __global float *work_logGamma,
   __global char *out_K,
   __global char *out_labels)
{
   int i = get_global_id(0);

   // initialize workspace variables
   __global Vector2 *data = &work_X[i * sampleSize];
   int numSamples = work_N[i];
   __global float *x_sorted = &work_x[i * sampleSize];
   __global float *y_sorted = &work_y[i * sampleSize];
   __global char *labels = &work_labels[i * sampleSize];
   __global Component *components = &work_components[i * maxClusters];
   __global Vector2 *Mu = &work_MP[i * maxClusters];
   __global int *counts = &work_counts[i * maxClusters];
   __global float *logpi = &work_logpi[i * maxClusters];
   __global float *loggamma = &work_loggamma[i * maxClusters * sampleSize];
   __global float *logGamma = &work_logGamma[i * maxClusters];
   __global char *bestK = &out_K[i];
   __global char *bestLabels = &out_labels[i * sampleSize];

   // initialize GMM struct
   GMM gmm = {
      .components = components,
      ._Mu = Mu,
      ._counts = counts,
      ._logpi = logpi,
      ._loggamma = loggamma,
      ._logGamma = logGamma
   };

   // remove pre-clustering outliers
   if ( removePreOutliers )
   {
      numSamples = removeOutliers(data, bestLabels, sampleSize, 0, -7, x_sorted, y_sorted);
   }

   // perform clustering only if there are enough samples
   *bestK = 0;

   if ( numSamples >= minSamples )
   {
      float bestValue = INFINITY;

      for ( char K = minClusters; K <= maxClusters; ++K )
      {
         // run each clustering sub-model
         bool success = GMM_fit(&gmm, data, numSamples, K, labels);

         if ( !success )
         {
            continue;
         }

         // compute the criterion value of the sub-model
         float value = INFINITY;

         switch (criterion)
         {
         case AIC:
            value = GMM_computeAIC(K, 2, gmm.logL);
            break;
         case BIC:
            value = GMM_computeBIC(K, 2, gmm.logL, numSamples);
            break;
         case ICL:
            value = GMM_computeICL(K, 2, gmm.logL, numSamples, gmm.entropy);
            break;
         }

         // save the best model
         if ( value < bestValue )
         {
            *bestK = K;
            bestValue = value;

            for ( int i = 0, j = 0; i < numSamples; ++i )
            {
               if ( bestLabels[i] >= 0 )
               {
                  bestLabels[i] = labels[j];
                  ++j;
               }
            }
         }
      }
   }

   // remove post-clustering outliers
   if ( *bestK > 1 && removePostOutliers )
   {
      for ( char k = 0; k < *bestK; ++k )
      {
         numSamples = removeOutliers(data, bestLabels, sampleSize, k, -8, x_sorted, y_sorted);
      }
   }
}