#include <armadillo>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
//~ #include <thread>

#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>

#include <iomanip>
#include <sstream>

using namespace std;
using namespace arma;
using namespace boost::filesystem;

class Pdb_data {

    int anchor_len;
    int loop_len_1;
    int loop_len_2;
    int n_atoms;

    mat anchor_points_1;
    mat aligned_anchor_points_1;
    mat anchor_points_2;

    mat loop_points_1;
    mat aligned_loop_points_1;
    mat loop_points_2;

    rowvec anchor_atom_found_1;
    rowvec anchor_atom_found_2;

    rowvec residue_atom_found_1;
    rowvec residue_atom_found_2;

    void get_atom(char[], char[]);
    bool correct_atom(char[]);
    double get_double(string, int, int);
    int atom_index(char[]);
    int get_index(char*, char[][5], int);
    void read_number(char[], int, int, double*);
    void read_file(string, mat&, mat&, rowvec&, rowvec&, char[][5], int, int);
    void Kabsch(mat, mat, mat&, mat&, mat&);
    void Align_anchors();
    template <typename T> int sgn(T);
    void ResidueRM_TSD(mat, mat, rowvec, rowvec, double&, double&, int&);
    string format_number(double);

    public:

    Pdb_data(string, string, char[][5], int, int);
    void Retrieve_Residue(int, int, mat&, rowvec&);
    void calculate_DTW(double*);
    void write_modified_file(const char*, const char*, int);

};

void Pdb_data::get_atom(char line[], char atom[]){
    int atom_index = 0;
    for(int i=12; i<16; i++){
        if(isalnum(line[i])){
            atom[atom_index++] = line[i];
        }
    }
    atom[atom_index] = '\0';
}

bool Pdb_data::correct_atom(char atom[]){
    return strcmp(atom,"C")==0 || strcmp(atom,"CA")==0 || strcmp(atom,"N")==0 || strcmp(atom,"O")==0;
}

double Pdb_data::get_double(string line, int start, int length){
    double x = atof(line.substr(start, length).c_str());
    return x;
}

int Pdb_data::atom_index(char atom[]){
    if(strcmp(atom,"N")==0){
        return 0;
    } else if(strcmp(atom,"CA")==0){
        return 1;
    } else if(strcmp(atom,"C")==0){
        return 2;
    } else if(strcmp(atom,"O")==0){
        return 3;
    }

    cout << "Unrecognized atom " << atom << endl;
}

int Pdb_data::get_index(char* atom, char atoms[][5], int n_atoms){
    for(int i=0; i<n_atoms; i++){
        if(strcmp(atom, atoms[i]) == 0){
            return i;
        }
    }
    return -1;
}

void Pdb_data::read_number(char line[], int from, int length, double *result){
    char num[length+1];
    memcpy(num, &line[from], length);
    num[length] = '\0';
    *result = atof(num);
}

void Pdb_data::read_file(string file, mat &anchor_points, mat &loop_points, rowvec &anchor_atom_found, rowvec &residue_atom_found, char atoms[][5], int n_atoms, int anchor_len){
    boost::filesystem::ifstream input(file);
    char buf[81];
    char c_aa[6], l_aa[6], atom[5];
    char beginning[7];
    const char *ATOM = "ATOM  ";
    const char *HETATM = "HETATM";
    int aa_index = -1;
    rowvec g_atom_found;
    mat points;

    c_aa[5] = '\0';
    l_aa[5] = '\0';
    atom[4] = '\0';
    beginning[4] = '\0';

    mat aa_atoms = zeros<mat>(3, n_atoms);
    //mat empty = zeros<mat>(n_atoms,3);
    rowvec l_atom_found = zeros<rowvec>(n_atoms);
    //rowvec empty_vec = zeros<rowvec>(n_atoms);

    // cout << "Reading file " << file << endl;

    while(input.good()){

        input.getline(buf, 81);

        memcpy(beginning, &buf[0], 6);

        beginning[6] = '\0';

        if (strcmp(beginning, ATOM) == 0 || strcmp(beginning, HETATM) == 0)
        {

            // Change Amino Acid
            memcpy(l_aa, &buf[22], 5);

            //cout << c_aa << endl;

            if(strcmp(c_aa, l_aa) != 0){
                //cout << "Change from" << c_aa << "to" << l_aa << " " << strcmp(c_aa, l_aa) << endl;
                memcpy(c_aa, &l_aa[0], 5);
                aa_index++;
                //cout << aa_index << '\n';
                if(aa_index != 0){
                    points = join_horiz(points, aa_atoms);

                    //cout << points << '\n';

                    g_atom_found = join_horiz(g_atom_found, l_atom_found);
                }

                l_atom_found = zeros<rowvec>(n_atoms);
                aa_atoms = zeros<mat>(3, n_atoms);

            }

            get_atom(buf, atom);
            int atom_index = get_index(atom, atoms, n_atoms);

            if(atom_index != -1){
                double x, y, z;
                read_number(buf,30, 8,&x);
                read_number(buf,38, 8,&y);
                read_number(buf,46, 8,&z);

                colvec point = zeros<colvec>(3);
                point(0) = x;
                point(1) = y;
                point(2) = z;

                aa_atoms.col(atom_index) = point;

                l_atom_found(atom_index) = 1.0;
            }

        }
    }

    points = join_horiz(points, aa_atoms);

    g_atom_found = join_horiz(g_atom_found, l_atom_found);

    int points_len = points.n_cols;

    anchor_points = join_horiz(points.cols(0, anchor_len*n_atoms - 1), points.cols(points_len - anchor_len*n_atoms, points_len - 1));

    loop_points = points.cols(anchor_len*n_atoms, points_len - anchor_len*n_atoms - 1);

    anchor_atom_found = join_horiz(g_atom_found.subvec(0, anchor_len*n_atoms - 1), g_atom_found.subvec(points_len - anchor_len*n_atoms, points_len - 1));

    residue_atom_found = g_atom_found.subvec(anchor_len*n_atoms, points_len - anchor_len*n_atoms - 1);

    //~ cout << loop_points << endl;

}

void Pdb_data::Kabsch(mat P, mat Q, mat &anchor_mass_centre_1, mat &anchor_mass_centre_2, mat &Rotation_mat)
{
    mat Middle_mat;

    mat P_translated;
    mat Q_translated;
    mat P_rotated_translated;
    mat P_finish;

    mat U;
    vec s;
    mat V;

    mat R;

    colvec P_centroid;
    colvec Q_centroid;
    rowvec v_ones;

    mat Covariance_matrix;

    int d;
    int n_points;

    // Calculate the number of datapoints
    n_points = P.n_cols;

    //Calculate the coordinates of the centres of mass
    P_centroid=sum(P,1)/n_points;

    Q_centroid=sum(Q,1)/n_points;

    v_ones = ones<rowvec>(n_points);

    //Translate the points so that the cetre of mass coincides with the origin
    P_translated = P - P_centroid*v_ones;

    Q_translated = Q - Q_centroid*v_ones;

    //Calculate the covariance matrix
    Covariance_matrix=P_translated*Q_translated.t()/n_points;

    //Calculate svd
    svd(U,s,V,Covariance_matrix);

    //Ensure a right-handed coordinate system
    d = sgn(det( V*U.t() ));

    Middle_mat = eye<mat>(3,3);

    Middle_mat(2,2) =  d;

    //Calculate the rotation matrix
    R=V*Middle_mat*U.t();

    //~ cout << Q << endl;
    //~ cout << R*(P - P_centroid*v_ones) + Q_centroid*v_ones << endl;

    aligned_anchor_points_1 = R*(P - P_centroid*v_ones) + Q_centroid*v_ones;

    anchor_mass_centre_1 = P_centroid;
    anchor_mass_centre_2 = Q_centroid;
    Rotation_mat = R;

}

void Pdb_data::Align_anchors(){

    mat anchor_mass_centre_1;
    mat anchor_mass_centre_2;
    mat Rotation_mat;
    int len_1;
    uvec logic_vec_1;

    Kabsch(anchor_points_1, anchor_points_2, anchor_mass_centre_1, anchor_mass_centre_2, Rotation_mat);

    rowvec v_ones = ones<rowvec>(sum(residue_atom_found_1));

    logic_vec_1 = find(residue_atom_found_1 == 1);

    aligned_loop_points_1 = loop_points_1;

    aligned_loop_points_1.cols(logic_vec_1) = aligned_loop_points_1.cols(logic_vec_1) - anchor_mass_centre_1*v_ones;

    aligned_loop_points_1.cols(logic_vec_1) = Rotation_mat*aligned_loop_points_1.cols(logic_vec_1);

    aligned_loop_points_1.cols(logic_vec_1) = aligned_loop_points_1.cols(logic_vec_1) + anchor_mass_centre_2*v_ones;

}

template <typename T> int Pdb_data::sgn(T val) {
    return (T(0) < val) - (val < T(0));
}

void Pdb_data::Retrieve_Residue(int data_index, int res_index, mat &Residue_points, rowvec &individual_atoms_found){

    if(data_index == 1)
    {
        Residue_points = aligned_loop_points_1.cols(res_index*n_atoms, (res_index + 1)*n_atoms - 1);

        individual_atoms_found = residue_atom_found_1.subvec(res_index*n_atoms, (res_index + 1)*n_atoms - 1);

    }
    else if(data_index == 2)
    {
        Residue_points = loop_points_2.cols(res_index*n_atoms, (res_index + 1)*n_atoms - 1);

        individual_atoms_found = residue_atom_found_2.subvec(res_index*n_atoms, (res_index + 1)*n_atoms - 1);

    }
    else
    {
        cout << "There are only two datasets" << endl;

    }

}

void Pdb_data::ResidueRM_TSD(mat Res1, mat Res2, rowvec individual_atoms_found_1, rowvec individual_atoms_found_2, double &RMSD, double &TSD, int &N){

    uvec logic_vec_both;

    mat Diff;

    rowvec total_individual_atoms_found;

    mat Res_inboth_1;

    mat Res_inboth_2;

    total_individual_atoms_found = individual_atoms_found_1 % individual_atoms_found_2;

    N = sum(total_individual_atoms_found);

    logic_vec_both = find(total_individual_atoms_found == 1);

    Res_inboth_1 = Res1.cols(logic_vec_both);

    Res_inboth_2 = Res2.cols(logic_vec_both);

    Diff = Res_inboth_1 - Res_inboth_2;

    RMSD = sqrt(1.0/N * sum(sum(Diff % Diff)));

    TSD = sum(sum(Diff % Diff));

}

void Pdb_data::calculate_DTW(double *result)
{

    double RMSD_val;

    double SD_val;

    int N;

    mat Residue_points_1;
    rowvec individual_atoms_found_1;

    mat Residue_points_2;
    rowvec individual_atoms_found_2;

    mat cost_mat = zeros<mat>(loop_len_1, loop_len_2);

    mat SD_mat = zeros<mat>(loop_len_1, loop_len_2);

    mat N_mat = zeros<mat>(loop_len_1, loop_len_2);

    uword index;

    double min_out_of_three;

    rowvec three_values = zeros<rowvec>(3);
    rowvec SD_three_values = zeros<rowvec>(3);
    rowvec N_three_values = zeros<rowvec>(3);

    Retrieve_Residue(1, 0 ,Residue_points_1, individual_atoms_found_1);
    Retrieve_Residue(2, 0 ,Residue_points_2, individual_atoms_found_2);

    ResidueRM_TSD(Residue_points_1, Residue_points_2, individual_atoms_found_1, individual_atoms_found_2, RMSD_val, SD_val, N);

    cost_mat(0,0) = RMSD_val;
    SD_mat(0,0) = SD_val;
    N_mat(0,0) = N;

    for(int i = 1; i < loop_len_1; i++)
    {
        Retrieve_Residue(1, i ,Residue_points_1, individual_atoms_found_1);

        ResidueRM_TSD(Residue_points_1, Residue_points_2, individual_atoms_found_1, individual_atoms_found_2, RMSD_val, SD_val, N);

        cost_mat(i,0) = cost_mat(i-1,0) + RMSD_val;

        SD_mat(i,0) = SD_mat(i-1,0) + SD_val;

        N_mat(i,0) = N_mat(i-1,0) + N;

    }

    Retrieve_Residue(1, 0 ,Residue_points_1, individual_atoms_found_1);

    for(int j = 1; j < loop_len_2; j++)
    {
        Retrieve_Residue(2, j ,Residue_points_2, individual_atoms_found_2);

        ResidueRM_TSD(Residue_points_1, Residue_points_2, individual_atoms_found_1, individual_atoms_found_2, RMSD_val, SD_val, N);

        cost_mat(0,j) = cost_mat(0, j-1) + RMSD_val;

        SD_mat(0,j) = SD_mat(0, j-1) + SD_val;

        N_mat(0,j) = N_mat(0, j-1) + N;

    }

    for(int i = 1; i < loop_len_1; i++)
    {
        // cout << "Here" << endl;
        for(int j = 1; j< loop_len_2; j++)
        {
            Retrieve_Residue(1, i ,Residue_points_1, individual_atoms_found_1);
            Retrieve_Residue(2, j ,Residue_points_2, individual_atoms_found_2);

            ResidueRM_TSD(Residue_points_1, Residue_points_2, individual_atoms_found_1, individual_atoms_found_2, RMSD_val, SD_val, N);

            three_values(0) = cost_mat(i-1, j);
            three_values(1) = cost_mat(i, j-1);
            three_values(2) = cost_mat(i-1, j-1);

            SD_three_values(0) = SD_mat(i-1, j);
            SD_three_values(1) = SD_mat(i, j-1);
            SD_three_values(2) = SD_mat(i-1, j-1);

            N_three_values(0) = N_mat(i-1, j);
            N_three_values(1) = N_mat(i, j-1);
            N_three_values(2) = N_mat(i-1, j-1);

            min_out_of_three = three_values.min(index);

            //cout << i << ' ' << j <<' '  << SD_val << three_values << endl;

            cost_mat(i,j) = min_out_of_three + RMSD_val;

            SD_mat(i, j) = SD_three_values(index) + SD_val;

            N_mat(i, j) = N_three_values(index) + N;

        }
    }

    //~ cout << cost_mat << endl;
    //~
    //~ cout << SD_mat << endl;
    //~
    //~ cout << N_mat << endl;

    N = N_mat(loop_len_1 - 1, loop_len_2 - 1);

    //~ cout << N << endl;
    //~
    //~ cout << sqrt(1.0/N * SD_mat(loop_len_1 - 1, loop_len_2 - 1)) << endl;

    *result = sqrt(1.0/N * SD_mat(loop_len_1 - 1, loop_len_2 - 1));

}

string Pdb_data::format_number(double Number_to_format)
{
    int wspc_missing;

    string Result;

    string complete_string;

    ostringstream Convert;

    Convert << fixed << setprecision(3) << Number_to_format;

    Result = Convert.str();

    wspc_missing = 7 - Result.length();

    complete_string = string(wspc_missing, ' ') + Result;

    return complete_string;

}

void Pdb_data::write_modified_file(const char *original_file, const char *result_file, int data_index)
{
    mat P_mat;

    if(data_index == 1)
    {
        P_mat = join_horiz(aligned_anchor_points_1.cols(0, anchor_len*n_atoms - 1), aligned_loop_points_1);
        P_mat = join_horiz(P_mat, aligned_anchor_points_1.cols(anchor_len*n_atoms, 2 * anchor_len*n_atoms - 1));

    }
    else if(data_index == 2)
    {
        P_mat = join_horiz(anchor_points_2.cols(0, anchor_len*n_atoms - 1), loop_points_2);
        P_mat = join_horiz(P_mat, anchor_points_2.cols(anchor_len*n_atoms, 2 * anchor_len*n_atoms - 1));

    }
    else
    {
        cout << "There are only two datasets" << endl;

    }

    boost::filesystem::ifstream infile(original_file);
    boost::filesystem::ofstream pdb_modified;

    double x,y,z;

    int i;

    pdb_modified.open(result_file);

    string line;

    string x_string;

    string y_string;

    string z_string;

    i=0;

    while(getline(infile, line))
    {

        if (line.compare(13, 3, "CA ")==0 or line.compare(13, 3, "N  ")==0 or line.compare(13, 3, "C  ")==0 or line.compare(13, 3, "O  ")==0)

        {
            x = P_mat(0,i);

            y = P_mat(1,i);

            z = P_mat(2,i);

            x_string = format_number(x);

            y_string = format_number(y);

            z_string = format_number(z);

            line.replace(31,7,x_string);

            line.replace(39,7,y_string);

            line.replace(47,7,z_string);

            //~ cout << line << line.length() << '\n';

            pdb_modified << line << '\n';

            i++;
        }
    }

    pdb_modified.close();
}

Pdb_data::Pdb_data(string file1, string file2, char atoms[][5], int n_atoms, int anchor_len){

    this->anchor_len = anchor_len;
    this->n_atoms = n_atoms;

    uvec logic_vec;
    rowvec total_anchor_atoms_found;

    //~ thread thread1(read_file, file1, ref(pos1), ref(atom_found1), atoms, n_atoms);
    //~ thread thread2(read_file, file2, ref(pos2), ref(atom_found2), atoms, n_atoms);
    //~
    //~ thread1.join();
    //~ thread2.join();

    // cout << file1 << endl;

    // cout << file2 << endl;

    read_file( file1, anchor_points_1, loop_points_1, anchor_atom_found_1, residue_atom_found_1, atoms, n_atoms, anchor_len);
    read_file( file2, anchor_points_2, loop_points_2, anchor_atom_found_2, residue_atom_found_2, atoms, n_atoms, anchor_len);

    total_anchor_atoms_found = anchor_atom_found_1 % anchor_atom_found_2;

    // cout << anchor_points_1 << endl;

    // cout << anchor_points_2 << endl;

    logic_vec = find(total_anchor_atoms_found == 1);

    anchor_points_1 = anchor_points_1.cols(logic_vec);

    anchor_points_2 = anchor_points_2.cols(logic_vec);

    //~ cout << anchor_points_1 << endl;

    if(anchor_points_1.n_elem != anchor_points_2.n_elem)
    {
        cout << "Anchor Lengths are not the same " << file1 << " " << file2 << endl;
    }

    //~ cout << anchor_points_2 << endl;
    //~
    //~ cout << loop_points_1 << endl;
    //~
    //~ cout << loop_points_2 << endl;

    if(loop_points_1.n_cols % 4 != 0 || loop_points_2.n_cols % 4 != 0)
    {
        cout << "Something's wrong with the number of residues " << file1 << " " << file2 << endl;
    }

    loop_len_1 = loop_points_1.n_cols / 4.0;

    loop_len_2 = loop_points_2.n_cols / 4.0;

    //~ cout << loop_len_1 << " " << loop_len_2 << endl;

    Align_anchors();

    //~ cout << aligned_loop_points_1 << endl;
    //~
    //~ cout << loop_points_2 << endl;

}

int main(int argc, char* argv[])
{

    boost::filesystem::ofstream distmat_file;
    boost::filesystem::ofstream file_list;

    mat P1_kabsch;

    int anchor_len = 5;

    double result;

    char atoms[4][5];

    strcpy(atoms[0],"N");
    strcpy(atoms[1],"CA");
    strcpy(atoms[2],"C");
    strcpy(atoms[3],"O");

    string filename;

    string extension = "pdb";

    string current_extension;

    int filelen;

    int start;

    vector<string> filenames;

    mat distmat;

    int n_files;

    int row, col;

    int res_anchor_len = 5;
    int start_atom_anchor_len, end_atom_anchor_len;

    const char* directory = argv[1];

    const char* output_file_list_name = argv[2];

    const char* output_distmat_name = argv[3];

    path p(directory);

    // Read old file list
    vector<string> filenames_old;
    int n_oldfiles = 0;
    bool found_old = false;
    if (boost::filesystem::exists(output_file_list_name))
    {
        found_old = true;
	cout << "Found output file list\n";
        boost::filesystem::ifstream file_list_old(output_file_list_name);
        while(getline(file_list_old,filename))
        {
            filenames.push_back(filename);
        }
        n_oldfiles = filenames.size();
        cout << "Number of files: "<<n_oldfiles<<endl;
    }
    // Read old distmat
    mat distmat_old;
    if (found_old & boost::filesystem::exists(output_distmat_name))
    {
        boost::filesystem::ifstream distmat_file_old(output_distmat_name);
        distmat_old.load(distmat_file_old);
    }

    for (auto i = directory_iterator(p); i != directory_iterator(); i++)
    {
        if (!is_directory(i->path())) //we eliminate directories
        {

            filename = i->path().string();

            filelen = filename.length();

            start = filelen - 3;

            current_extension = filename.substr(start,filelen);

            if(!current_extension.compare(extension) & std::find(filenames.begin(),filenames.end(),filename) == filenames.end() ){
                filenames.push_back(filename);
            }

        }
        else
            continue;
    }

    cout << "Reading files and calculating distmat..." << endl;

    file_list.open(output_file_list_name);

    n_files = filenames.size();

    distmat = zeros<mat>(n_files, n_files);

    row = 0;

    for(auto i = filenames.begin(); i != filenames.end(); ++i)
    {

        //cout << row << " " << *i << endl;

        col = row + 1;
        file_list << *i << endl;

        for(auto j = i + 1; j != filenames.end(); ++j)
        {
            //cout << *i << " " << *j << " " << row << " " << col << " ";
            if (found_old & col<n_oldfiles)
            {
		//cout << "skipped with "<<distmat_old(row,col)<<endl;
                distmat(row,col) = distmat_old(row,col);
                distmat(col,row) = distmat_old(col,row);
                col++;
                continue;
            }
            //cout << "calculating"<<endl;
            Pdb_data x(*i, *j, atoms, 4, anchor_len);

            x.calculate_DTW(&result);

            distmat(row,col) = result;

            distmat(col,row) = result;

            col++;

        }

        row++;
    }

    distmat_file.open(output_distmat_name);

    distmat_file << distmat;

    distmat_file.close();

    // cout << distmat << endl;

    // cout << "Done !" << endl;

    // Kabsch(all_Ps[0], all_Ps[2], &rmsd);

    // cout << filenames[0] << " " << filenames[1] << endl;

    // cout << rmsd << endl;
}


#include <fstream>
#include <iostream>
#include <filesystem>
#include <string>
#include <random>
#include <unordered_map>
#include <algorithm>
#include <chrono>
#include <future>
#include <utility>
#include <cmath>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <xtensor/xarray.hpp>
#include <xtensor/xmath.hpp>
#include "csv.hpp"
#include "../assembly/assembly.cpp"
#include "../distance/distance.cpp"
#include "../distribution/distribution.cpp"
#include "../probability/probability.cpp"
#include "../probability/probability_util.cpp"
#include "../domain/domain.cpp"
#include "../fasta/fasta.cpp"
#include "../count/tri_count.cpp"
#include "../codons/swap_codons.cpp"

using namespace std;
using namespace std::chrono;
using namespace csv;

unordered_map<string, vector<double>> MakeInterventions(
	FastaParser parser,
	const xt::xarray<double>& genome_wide_dist,
	const string& intervention,
	const int n_samples,
	mt19937 rng
) {
	unordered_map<string, vector<double>> protein_id_to_distances;
	FastaRecord cds_record{};
	while (parser.Get(cds_record)) {
		auto& sequence = cds_record.content;
		string protein_id = cds_record.id;

		// Perform a number of random interventions on the CDS
		vector<double> distances;
		for (int s = 0; s < n_samples; ++s) {
			string seq = "";
			if (intervention == "swap") {
				seq = SwapSynonymousCodons(sequence, rng);
			} else if (intervention == "shuffle") {
				seq = ShuffleCodons(sequence, rng);
			} else {
				throw runtime_error("Unknown intervention type: " + intervention);
			}

			// Count tri-nucleotides
			bool overlap = true;
			bool use_async = false;
			vector<int> counts = CountTriNucleotides(seq, overlap, use_async);

			// Compute distribution
			xt::xarray<double> cds_distribution = xt::zeros<double>({counts.size()});
			int sum = accumulate(counts.begin(), counts.end(), 0);
			for (int cix = 0; cix < counts.size(); ++cix) {
				cds_distribution[cix] = (double) counts[cix] / (double) sum;
			}

			// Compute distance
			double distance = jensen_shannon_distance(genome_wide_dist, cds_distribution);
			if (isinf(distance) || isnan(distance)) {
				cerr << "MakeInterventions: Nan or Inf encountered for protein id: " << protein_id << endl;
				continue;
			} else {
				distances.push_back(distance);
			}
		}
		if (distances.size() > 0) {
			protein_id_to_distances[protein_id] = distances;
		}
	}
	return protein_id_to_distances;
}

bool task_intervention_per_assembly(
	const int task_nb,
	const string& query,
	const string& tail,
	const string intervention,
	const int n_samples,
	const vector<string>& assembly_ids
) {
	auto start_clock = system_clock::now();

	cerr << "Thread " << task_nb << " started." << endl;

	string dataFolder = "../data/";
	string sequencesFolder = dataFolder + "sequences/";
	auto n_assemblies = assembly_ids.size();

	// Load genome-wide distributions
	string assembly_dist_path = (
		dataFolder + "tri_nucleotide_dist_genome_wide_with_overlap.csv"
	);
	ifstream assembly_dist_f(assembly_dist_path);
	Distributions assembly_distributions(assembly_dist_f);

	// Load Protein domains metadata
	string metadata_path = dataFolder + query + "_master.csv";
	auto metadata = LoadDomainMetadata(metadata_path);

	// Random number generator
	random_device rd;
	mt19937 rng(rd());

	// Compute interventions on every assemblies
	int i = 0;
	for (auto& accession : assembly_ids) {
		auto elapsed = duration_cast<seconds>(system_clock::now() - start_clock).count();
		cerr << "Thread " << task_nb << ": ";
		cerr << "Processing assembly " << i + 1 << " / " << n_assemblies;
		cerr << " (elapsed: " << elapsed << " seconds)" << endl;
		++i;

		// Get genome-wide distribution
		xt::xarray<double> genome_wide_dist = assembly_distributions[accession];

		// Load protein domains to protein ids map
		string protein_domains_path = (
			sequencesFolder + accession + "/" +
			accession + "_" + query + ".csv.gz"
		);
		ifstream protein_domains_file(protein_domains_path);
		boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf;
		inbuf.push(boost::iostreams::gzip_decompressor());
		inbuf.push(protein_domains_file);
		istream instream(&inbuf);
		ProteinDomains domains(instream);

		// Instanciate CDS parser
		string cds_path = (
			sequencesFolder +
			accession + "/" +
			accession + "_cds_from_genomic.fna.gz"
		);
		ifstream input_file(cds_path, ios_base::in | ios_base::binary);
		boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf_cds;
		inbuf_cds.push(boost::iostreams::gzip_decompressor());
		inbuf_cds.push(input_file);
		istream instream_cds(&inbuf_cds);
		FastaParser parser(instream_cds);

		// Make interventions: randomly modify sequences a bunch of times and return results as a
		// map of protein ids to the list of distances from the modified sequence distributions
		// the genome-wide distribution.
		unordered_map<string, vector<double>> protein_id_to_distances = MakeInterventions(
			parser,
			genome_wide_dist,
			intervention,
			n_samples,
			rng
		);

		// Compute mean probability per sample
		vector<double> means(n_samples);
		for (int s = 0; s < n_samples; ++s) {
			xt::xarray<double> probs = xt::zeros<double>({protein_id_to_distances.size()});
			int ix = 0;
			for (auto& it : protein_id_to_distances) {
				double distance = it.second[s];
				probs[ix] = compute_probability_from_distance(distance, tail == "left");
				++ix;
			}
			double mean = xt::mean(probs)();
			means[s] = mean;
		}

		// Compute domain probability
		vector<DomainProbability> records;
		for (ProteinDomain& domain : domains.Keys()) {
			// Set description from metadata rather than from annotations
			if (metadata.find(domain.id) != metadata.end()) {
				auto& [domain_query, domain_description] = metadata[domain.id];
				domain.query = domain_query;
				domain.description = domain_description;
			}

			// Get protein ids containing this domain
			auto protein_ids = domains.ProteinIds(domain);

			// Compute gene probabilities for each sample
			int n_records = -1;
			xt::xarray<double> log_probs = xt::zeros<double>({n_samples});
			xt::xarray<double> log_probs_baseline = xt::zeros<double>({n_samples});
			for (int s = 0; s < n_samples; ++s) {
				double mean = means[s];

				xt::xarray<double> probabilities = xt::zeros<double>({protein_ids.size()});
				xt::xarray<double> probabilities_baseline = xt::zeros<double>({protein_ids.size()});
				for (int k = 0; k < protein_ids.size(); ++k) {
					auto& protein_id = protein_ids[k];
					double distance = mean; // Default in case there is no data for protein id
					if (protein_id_to_distances.find(protein_id) != protein_id_to_distances.end()) {
						distance = protein_id_to_distances[protein_id][s];
					}
					probabilities[k] = compute_probability_from_distance(distance, tail == "left");
					probabilities_baseline[k] = mean;
				}

				xt::xarray<double> log_probabilities = xt::eval(xt::log(probabilities));
				xt::xarray<double> log_probabilities_baseline = xt::eval(xt::log(probabilities_baseline));

				double log_prob = product_rule_log(log_probabilities);
				double log_prob_baseline = product_rule_log(log_probabilities_baseline);

				if (n_records == -1) {
					n_records = log_probabilities.size();
				}
				log_probs[s] = log_prob;
				log_probs_baseline[s] = log_prob_baseline;
			}

			// Take the average of all samples
			DomainProbability record(
				domain,
				xt::mean(log_probs)(),
				xt::mean(log_probs_baseline)(),
				n_records
			);
			records.push_back(record);
		}

		// Sort records from best to worse evidence
		sort(records.begin(), records.end(), greater<DomainProbability>());

		// Create assembly directory if it does not exist
		string assembly_domain_prob_out_folder = (
			dataFolder + "intervention/" +  intervention + "/sequences/" + accession + "/"
		);
		filesystem::create_directory(assembly_domain_prob_out_folder);

		// Prepare output writer
		string assembly_domain_prob_out_path = (
			assembly_domain_prob_out_folder +
			accession + "_" + query + "_probability_" + tail + ".csv"
		);
		ofstream of(assembly_domain_prob_out_path);
		auto writer = make_csv_writer(of);
		writer << DomainProbability::RecordHeader();

		// Write assembly outputs
		for (auto& record : records) {
			writer << record.Record();
		}
	}
	return true;
}

bool task_compute_domain_probabilities_per_phylum(
	const int task_nb,
	const string& query,
	const string& tail,
	const string intervention,
	const vector<string>& phyla,
	const unordered_map<string, vector<string>>& assemblies_per_phylum
) {
	cerr << "Thread " << task_nb << " started." << endl;

	string dataFolder = "../data/intervention/" + intervention + "/";
	string sequencesFolder = dataFolder + "sequences/";
	string phylumFolder = dataFolder + "phylum/";
	auto n_phyla = phyla.size();

	// Create phylum directory if it does not exist.
	filesystem::create_directory(phylumFolder);

	int i = 0;
	for (auto& phylum : phyla) {
		auto& assembly_ids = assemblies_per_phylum.at(phylum);
		auto n_assemblies = assembly_ids.size();

		cerr << "Thread " << task_nb << ": ";
		cerr << "Processing phylum " << i + 1 << " / " << n_phyla;
		cerr << ": " << phylum << " (" << n_assemblies << " assemblies)";
		cerr << endl;
		++i;

		set<ProteinDomain> protein_domains;
		unordered_map<ProteinDomain, vector<DomainProbability>> protein_domain_probs;
		for (auto& accession : assembly_ids) {
			string path = (
				sequencesFolder + accession + "/" +
				accession + "_" + query + "_probability_" + tail + ".csv"
			);
			vector<DomainProbability> domains = LoadDomainProbabilities(path);
			for (auto& domain_prob : domains) {
				auto& domain = domain_prob.domain;
				protein_domains.insert(domain);

				if (protein_domain_probs.find(domain) == protein_domain_probs.end()) {
					protein_domain_probs[domain] = vector<DomainProbability>{domain_prob};
				} else {
					protein_domain_probs[domain].push_back(domain_prob);
				}
			}
		}

		string phylum_lower = phylum;
		transform(phylum_lower.begin(), phylum_lower.end(), phylum_lower.begin(), ::tolower);
		transform(phylum_lower.begin(), phylum_lower.end(), phylum_lower.begin(), [](char ch) {
		    return ch == ' ' ? '_' : ch;
		});

		string phylumDir = phylumFolder + phylum_lower + "/";

		filesystem::create_directory(phylumDir);

		string phylum_domain_prob_out_path = (
			phylumDir +
			phylum_lower + "_" + query + "_probability_" + tail + ".csv"
		);
		ofstream of(phylum_domain_prob_out_path);
		auto writer = make_csv_writer(of);
		writer << DomainProbability::RecordHeader();

		vector<DomainProbability> records;
		for (auto& domain : protein_domains) {
			auto& domain_probs = protein_domain_probs[domain];
			auto n_probs = domain_probs.size();
			xt::xarray<double> log_probs = xt::zeros<double>({n_probs});
			xt::xarray<double> log_probs_random = xt::zeros<double>({n_probs});
			for (int ix = 0; ix < n_probs; ++ix) {
				log_probs[ix] = domain_probs[ix].log_probability;
				log_probs_random[ix] = domain_probs[ix].log_probability_random;
			}

			double log_prob = product_rule_log(log_probs);
			double log_prob_random = product_rule_log(log_probs_random);

			try {
				DomainProbability record(
					domain,
					log_prob,
					log_prob_random,
					n_probs
				);
				records.push_back(record);
			}
			catch (exception& e) {
				cerr << "Thread " << task_nb << " | Phylum: " << phylum << " | ";
				cerr << "Exception: " << e.what() << endl;
				throw;
			}
		}

		sort(records.begin(), records.end(), greater<DomainProbability>());

		for (auto& record : records) {
			writer << record.Record();
		}
	}
	cerr << "Thread " << task_nb << ": DONE" << endl;
	return true;
}

void compute_intervention(
	const string query,
	const string tail,
	const string intervention,
	const int n_samples,
	const int n_threads
) {
	auto start_clock = system_clock::now();

	string dataFolder = "../data/";

	bool complete_genome_only = true;
	Assemblies assemblies(dataFolder + "intervention/" + "sample_assemblies.csv");
	auto assembly_ids = assemblies.GetIds();
	auto n_per_thread = ceil((double) assembly_ids.size() / (double) n_threads);

	cerr << "Processing " << assemblies.Size() << " assemblies" << endl;

	//
	// 1) Proceed with intervention and compute domain probability per assembly.
	//
	vector<future<bool>> futures;
	for (int i = 0; i < n_threads; ++i) {
		auto start = assembly_ids.begin() + i * n_per_thread;
		auto end = assembly_ids.end();
		int endInt = i * n_per_thread + n_per_thread;
		if (endInt < assembly_ids.size()) {
			end = assembly_ids.begin() + endInt;
		}
		auto ids = vector<string>(start, end);
		futures.push_back(async(
			task_intervention_per_assembly,
			i+1,
			query,
			tail,
			intervention,
			n_samples,
			ids
		));
	}
	for (auto& f : futures) {
		if(!f.get()) {
			throw runtime_error("Unexpected error while processing assembly output");
		}
	}

	//
	// 2) Compute probability of domains for each phylum.
	//
	cerr << "Processing of domain probabilities per phylum" << endl;

	unordered_map<string, vector<string>> assemblies_per_phylum;
	for (auto& assembly_id : assembly_ids) {
		Assembly& assembly = assemblies.Get(assembly_id);
		string phylum = assembly.phylum;
		if (phylum.empty()) {
			continue;
		}
		if (assemblies_per_phylum.find(phylum) == assemblies_per_phylum.end()) {
			assemblies_per_phylum[phylum] = vector<string>{assembly_id};
		} else {
			assemblies_per_phylum[phylum].push_back(assembly_id);
		}
	}

	set<string> phyla_set;
	for (auto& assembly_id : assembly_ids) {
		Assembly& assembly = assemblies.Get(assembly_id);
		string phylum = assembly.phylum;
		if (!phylum.empty()) {
			phyla_set.insert(phylum);
		}
	}

	vector<string> phyla;
	phyla.assign(phyla_set.begin(), phyla_set.end());
	auto n_phyla = phyla.size();

	cerr << "Processing " << n_phyla << " phyla" << endl;
	cerr << "Starting " << n_threads << " threads" << endl;

	n_per_thread = ceil((double) n_phyla / (double) n_threads);

	vector<future<bool>> futuresP;
	for (int i = 0; i < n_threads; ++i) {
		auto start = phyla.begin() + i * n_per_thread;
		auto end = phyla.end();
		int endInt = i * n_per_thread + n_per_thread;
		if (endInt < n_phyla) {
			end = phyla.begin() + endInt;
		}
		futuresP.push_back(async(
			task_compute_domain_probabilities_per_phylum,
			i+1,
			query,
			tail,
			intervention,
			vector<string>(start, end),
			assemblies_per_phylum
		));
	}
	for (auto& f : futuresP) {
		if(!f.get()) {
			throw runtime_error("Unexpected error while processing phylum output");
		}
	}
	auto elapsed = duration_cast<seconds>(system_clock::now() - start_clock).count();
	cerr << "Processing of domain probabilities per phylum is complete" << endl;
	cerr << "Elapsed: " << elapsed << " seconds" << endl;

	//
	// 3) Compute global probability of domains.
	//
	cerr << "Processing of domain probabilities globally" << endl;
	set<ProteinDomain> protein_domains;
	unordered_map<ProteinDomain, vector<DomainProbability>> protein_domain_probs;
	for (auto& phylum : phyla) {
		string phylum_lower = phylum;
		transform(phylum_lower.begin(), phylum_lower.end(), phylum_lower.begin(), ::tolower);
		transform(phylum_lower.begin(), phylum_lower.end(), phylum_lower.begin(), [](char ch) {
		    return ch == ' ' ? '_' : ch;
		});
		string phylumDir = (
			dataFolder + "intervention/" + intervention + "/phylum/" + phylum_lower + "/"
		);
		string phylum_domain_prob_path = (
			phylumDir +
			phylum_lower + "_" + query + "_probability_" + tail + ".csv"
		);

		vector<DomainProbability> domains = LoadDomainProbabilities(phylum_domain_prob_path);
		for (auto& domain_prob : domains) {
			auto& domain = domain_prob.domain;
			protein_domains.insert(domain);

			if (protein_domain_probs.find(domain) == protein_domain_probs.end()) {
				protein_domain_probs[domain] = vector<DomainProbability>{domain_prob};
			} else {
				protein_domain_probs[domain].push_back(domain_prob);
			}
		}
	}

	const string protein_out_path = (
		dataFolder + "intervention/" + intervention + "/" +
		query + "_probability_" + tail + ".csv"
	);
	ofstream output_file(protein_out_path);
	auto writer = make_csv_writer(output_file);
	writer << DomainProbability::RecordHeader();

	xt::xarray<double> uniform_log_prior = xt::eval(
		xt::log(make_uniform_prior(n_phyla))
	);

	vector<DomainProbability> records;
	for (auto& domain : protein_domains) {
		auto& domain_probs = protein_domain_probs[domain];
		auto n_probs = domain_probs.size();
		xt::xarray<double> log_probs = xt::zeros<double>({n_phyla});
		xt::xarray<double> log_probs_random = xt::zeros<double>({n_phyla});
		for (int ix = 0; ix < n_probs; ++ix) {
			log_probs[ix] = domain_probs[ix].log_probability;
			log_probs_random[ix] = domain_probs[ix].log_probability_random;
		}

		double log_prob = marginalization_log(
			uniform_log_prior,
			log_probs
		);
		double log_prob_random = marginalization_log(
			uniform_log_prior,
			log_probs_random
		);

		try {
			DomainProbability record(
				domain,
				log_prob,
				log_prob_random,
				n_probs
			);
			records.push_back(record);
		}
		catch (exception& e) {
			cerr << "Global computation | ";
			cerr << "Exception: " << e.what() << endl;
			throw;
		}
	}

	sort(records.begin(), records.end(), greater<DomainProbability>());

	for (auto& record : records) {
		writer << record.Record();
	}

	auto elapsed_final = duration_cast<seconds>(system_clock::now() - start_clock).count();
	cerr << "Elapsed: " << elapsed_final << " seconds" << endl;
	cerr << "DONE" << endl;
}


//
//  Functions_model.hpp
//  PAINTOR_V3.1
//
//  Created by Gleb Kichaev on 10/25/17.
//  Copyright © 2017 Gleb Kichaev. All rights reserved.
//

#ifndef Functions_model_hpp
#define Functions_model_hpp

#include <cstdio>
#include <Eigen>
#include <fstream>
#include <iostream>
#include <vector>
#include <cmath>
#include <Eigen>
#include <iterator>
#include <string>
#include <iomanip>
#include <algorithm>
#include <unordered_map>
#include <random>
#include <cmath>

using namespace Eigen;
using namespace std;

struct optimization_input{
    vector<double> current_posterior;
    vector<vector<double> > all_annotations;
};

//default executation parameters
struct execution_parameters{
    string in_dir = "./";
    string input_files = "input.files";
    string out_dir = "./";
    vector<string> model_annotations;
    int max_causal = 2;
    string gammaName = "Enrichment.Values";
    string likeli_name= "Log.BayesFactor";
    int maxIter= 15;
    string annotation_suffix = "annotations";
    string results_suffix = "results";
    vector <string> ld_suffix;
    vector<string> zscore_header;
    string single_post_flag = "False";
    int sampling_seed = time(NULL);
    string gamma_initial;
    int enumerate_flag = 0;
    int mcmc_flag = 0;
    int burn_in = 5000;
    int max_samples = 50000;
    int initialized_gammas=0;
    float prop_ld_eigenvalues = 0.95;
    int num_chains = 5;

};

//Class definition of a locus
//Each locus has its requires, Z-scores, LD, annotations
//Initialized with the gamma parameters (enrichments) along with the prior variance
class Locus{
public:
    Locus(){
        vector<VectorXf> z_scores;
        vector<MatrixXf> ld_matrix;
        MatrixXf annotations;
        vector<double> prior_variance;
        vector<int> config;
        VectorXf posterior_probability;
        VectorXf gamma_parameters;
        vector<string> snp_info;
        default_random_engine generator;
        size_t num_snps;
        size_t num_pops;
        double expected_log_likeli;
    };

    //initialzing functions
    void set_annotations(MatrixXf & update);
    void set_Zscore(VectorXf& update);
    void set_LD(MatrixXf& update);
    void add_info(string& curr_snp);
    void estimate_prior_variance(float proportion_eigenvalues);
    void set_num_snps(unsigned long number_of_snps);
    void set_total_pops(unsigned long number_of_pops);
    void set_locus_name(string& name);

    //acesss private member variables
    size_t get_num_snps();
    void set_gamma(VectorXf& gamma_update);
    MatrixXf get_annotations();
    VectorXd get_posterior_prob();
    double get_prior_variance(int index);
    VectorXf get_gamma();

    //model functions
    double enumerate_posterior(int max_number_of_causals);
    double  mcmc_posterior(size_t burn_in, size_t max_samples);
    double compute_log_prior();
    double compute_log_prior(int flip_bit, bool already_causal);
    void initialize_causal_config(int max_causals);
    void initialize_causal_config();
    void causal_config_to_vector(int max_causals);
    void update_log_posterior(double current_evaluation);
    void normalize_log_posterior(double normalizer);
    void reset_posterior();
    void write_results(string& fname);
    double mcmc_posterior(size_t burn_in, size_t max_samples, int num_chains);
    void next_configuration(int next_bit, bool done_burning);

    //public member variables
    uniform_real_distribution<double> uniform_double_generator;
    uniform_int_distribution<int> uniform_int_generator;
    default_random_engine generator;
    size_t num_pops;
    vector<int> ld_pcs;

private:
    string locus_name;
    vector<VectorXf> z_scores;
    vector<MatrixXf> ld_matrix;
    size_t num_snps;
    MatrixXf annotations;
    vector<double> prior_variance;
    vector<int> causal_index_vector;
    VectorXf causal_index;
    VectorXf causal_config_bit_vector;
    VectorXf annotation_eval;
    VectorXd posterior_probability;
    VectorXf gamma_parameters;
    vector<string> snp_info;
    double expected_log_likeli;

};

//use fixed effects estimator with truncated SVD to estimate the prior variance
// on the non-centrality parameter
void all_prior_variance_esimation(vector<Locus> & all_loci, float prop_eigenvalues);

float compute_prior_variance(VectorXf & z_scores, MatrixXf & ld_matrix);

int next_combo(VectorXf& causal_config, int k, int n);

void initialize_gamma_zero(vector<Locus>& all_loci, int number_of_annotations);

double compute_all_posteriors_enumerate(vector<Locus>& all_loci, int max_number_of_cauals);

double compute_all_posteriors_mcmc(vector<Locus>& all_loci, size_t burn_in, size_t max_samples, int num_chains);

void create_optimization_input(vector<Locus> & all_loci, optimization_input& opt_in);

vector<double> eigen_to_vector(VectorXf& eigen);

void update_all_gammas(vector<Locus>& all_loci, vector<double>& updated_gammas);

double run_EM(vector<Locus> & all_loci, execution_parameters& parameters );

int rand_int(size_t min, size_t max);

double rand_double_uniform(size_t min, size_t max) ;

void initialize_generators(vector<Locus>& all_loci, int seed_value);

#endif /* Functions_model_hpp */


/**
 * Copyright (c) 2011, 2012
 * Claudio Kopper <claudio.kopper@icecube.wisc.edu>
 * and the IceCube Collaboration <http://www.icecube.wisc.edu>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *
 * $Id$
 *
 * @file I3CLSimRandomValueWlenCherenkovNoDispersion.cxx
 * @version $Revision$
 * @date $Date$
 * @author Claudio Kopper
 */

#include <icetray/serialization.h>
#include <icetray/I3Logging.h>
#include <clsim/random_value/I3CLSimRandomValueWlenCherenkovNoDispersion.h>

#include <boost/lexical_cast.hpp>

#include <string>
#include <sstream>
#include <vector>
#include <cmath>
#include <limits>

#include "clsim/I3CLSimHelperToFloatString.h"

I3CLSimRandomValueWlenCherenkovNoDispersion::
I3CLSimRandomValueWlenCherenkovNoDispersion(double fromWlen,
                                            double toWlen)
:
fromWlen_(fromWlen),
toWlen_(toWlen)
{
    if (std::isnan(fromWlen_)) log_fatal("The \"fromWlen\" argument must not be NaN!");
    if (std::isnan(toWlen_)) log_fatal("The \"toWlen\" argument must not be NaN!");

    if (fromWlen_ > toWlen_)
        log_fatal("The \"fromWlen\" argument must not be greater than \"toWlen\".");
}

I3CLSimRandomValueWlenCherenkovNoDispersion::~I3CLSimRandomValueWlenCherenkovNoDispersion()
{
}

I3CLSimRandomValueWlenCherenkovNoDispersion::I3CLSimRandomValueWlenCherenkovNoDispersion() {;}

std::size_t I3CLSimRandomValueWlenCherenkovNoDispersion::NumberOfParameters() const {return 0;}

double I3CLSimRandomValueWlenCherenkovNoDispersion::SampleFromDistribution(const I3RandomServicePtr &random,
                                                                           const std::vector<double> &parameters) const
{
    if (!random) log_fatal("random service is NULL!");
    if (parameters.size() != 0) log_fatal("This distribution expects 0 parameters. Got %zu.", parameters.size());

    const double minVal = 1./toWlen_;
    const double range = (1./fromWlen_) - minVal;

    const double r = random->Uniform();
    return 1./(minVal + r * range);
}

std::string I3CLSimRandomValueWlenCherenkovNoDispersion::GetOpenCLFunction
(const std::string &functionName,
 const std::string &functionArgs,
 const std::string &functionArgsToCall,
 const std::string &uniformRandomCall_co,
 const std::string &uniformRandomCall_oc
 ) const
{
    const double minVal = 1./toWlen_;
    const double range = (1./fromWlen_) - minVal;

    const std::string functionDecl = std::string("inline float ") + functionName + "(" + functionArgs + ")";

    return functionDecl + ";\n\n" + functionDecl + "\n"
    "{\n"
    "    const float r = " + uniformRandomCall_oc + ";\n"
    "#ifdef USE_NATIVE_MATH\n"
    "    return native_recip(" + I3CLSimHelper::ToFloatString(minVal) + " + r * " + I3CLSimHelper::ToFloatString(range) + ");\n"
    "#else\n"
    "    return 1.f/(" + I3CLSimHelper::ToFloatString(minVal) + " + r * " + I3CLSimHelper::ToFloatString(range) + ");\n"
    "#endif\n"
    "}\n"
    ;
}

bool I3CLSimRandomValueWlenCherenkovNoDispersion::CompareTo(const I3CLSimRandomValue &other) const
{
    try
    {
        const I3CLSimRandomValueWlenCherenkovNoDispersion &other_ = dynamic_cast<const I3CLSimRandomValueWlenCherenkovNoDispersion &>(other);

        if (other_.fromWlen_ != fromWlen_) return false;
        if (other_.toWlen_ != toWlen_) return false;

        return true;
    }
    catch (const std::bad_cast& e)
    {
        // not of the same type, treat it as non-equal
        return false;
    }

}

template <class Archive>
void I3CLSimRandomValueWlenCherenkovNoDispersion::serialize(Archive &ar, unsigned version)
{
    if (version>i3clsimrandomvaluewlencherenkovnodispersion_version_)
        log_fatal("Attempting to read version %u from file but running version %u of I3CLSimRandomValueWlenCherenkovNoDispersion class.",
                  version,
                  i3clsimrandomvaluewlencherenkovnodispersion_version_);

    ar & make_nvp("I3CLSimRandomValue", base_object<I3CLSimRandomValue>(*this));
    ar & make_nvp("fromWlen", fromWlen_);
    ar & make_nvp("toWlen", toWlen_);
}

I3_SERIALIZABLE(I3CLSimRandomValueWlenCherenkovNoDispersion);


#include <boost/numeric/odeint/iterator/integrate/integrate.hpp>


#include "Surface_mesh_item_classification.h"
#include "Color_ramp.h"

#include <CGAL/Timer.h>
#include <CGAL/Memory_sizer.h>

#include <CGAL/Three/Viewer_interface.h>

#include <QLineEdit>

#include <set>
#include <stack>
#include <algorithm>
#include <boost/array.hpp>

Surface_mesh_item_classification::Surface_mesh_item_classification(Scene_surface_mesh_item* mesh)
  : m_mesh (mesh),
    m_selection (nullptr),
    m_generator (nullptr)
{
  m_index_color = 1;

  backup_existing_colors_and_add_new();
  m_training = m_mesh->polyhedron()->add_property_map<face_descriptor, std::size_t>("f:training", std::size_t(-1)).first;
  m_classif = m_mesh->polyhedron()->add_property_map<face_descriptor, std::size_t>("f:label", std::size_t(-1)).first;

  m_labels.add("ground");
  m_labels.add("vegetation");
  m_labels.add("roof");
  m_labels.add("facade");

  m_sowf = new Sum_of_weighted_features (m_labels, m_features);
  m_ethz = nullptr;
#ifdef CGAL_LINKED_WITH_OPENCV
  m_random_forest = nullptr;
#endif
}

Surface_mesh_item_classification::~Surface_mesh_item_classification()
{
  if (m_sowf != nullptr)
    delete m_sowf;
  if (m_ethz != nullptr)
    delete m_ethz;
#ifdef CGAL_LINKED_WITH_OPENCV
  if (m_random_forest != nullptr)
    delete m_random_forest;
#endif
#ifdef CGAL_LINKED_WITH_TENSORFLOW
  if (m_neural_network != nullptr)
    delete m_neural_network;
#endif
  if (m_generator != nullptr)
    delete m_generator;
}

void Surface_mesh_item_classification::backup_existing_colors_and_add_new()
{
  bool has_colors = false;
  boost::tie (m_color, has_colors) = m_mesh->polyhedron()->property_map<face_descriptor, CGAL::Color>("f:color");
  if (has_colors)
  {
    m_real_color
      = m_mesh->polyhedron()->add_property_map<face_descriptor, CGAL::Color>("f:real_color").first;
    for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
    {
      m_real_color[fd] = m_color[fd];
      m_color[fd] = CGAL::Color(128, 128, 128);
    }
  }
  else
    m_color =
      m_mesh->polyhedron()->add_property_map<face_descriptor, CGAL::Color>("f:color", CGAL::Color(128,128,128)).first;
}

void Surface_mesh_item_classification::change_color (int index, float* vmin, float* vmax)
{
  m_index_color = index;
  int index_color = index;
  if (index == 0 && m_real_color == Mesh::Property_map<face_descriptor, CGAL::Color>())
    index_color = -1;

  static Color_ramp ramp;
  ramp.build_rainbow();

  if (index_color == -1) // item color
  {
    for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
      m_color[fd] = CGAL::Color(128,128,128);
  }
  else if (index_color == 0) // real colors
  {
    for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
      m_color[fd] = m_real_color[fd];
  }
  else if (index_color == 1) // classif
  {
    for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
    {
      QColor color (128, 128, 128);
      std::size_t c = m_classif[fd];

      if (c != std::size_t(-1))
        color = label_qcolor (m_labels[c]);

      m_color[fd] = CGAL::Color(color.red(), color.green(), color.blue());
    }
  }
  else if (index_color == 2) // training
  {
    for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
    {
      QColor color (128, 128, 128);
      std::size_t c = m_training[fd];
      std::size_t c2 = m_classif[fd];

      if (c != std::size_t(-1))
        color = label_qcolor(m_labels[c]);

      float div = 1;
      if (c != c2)
        div = 2;
      m_color[fd] = CGAL::Color(color.red() / div,
                                color.green() / div,
                                color.blue() / div);
    }
  }
  else
  {
    std::size_t corrected_index = index_color - 3;
    if (corrected_index < m_labels.size()) // Display label probabilities
    {
      if (m_label_probabilities.size() <= corrected_index ||
          m_label_probabilities[corrected_index].size() != num_faces(*(m_mesh->polyhedron())))
      {
        for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
        {
          m_color[fd] = CGAL::Color((unsigned char)(128),
                                    (unsigned char)(128),
                                    (unsigned char)(128));
        }
      }
      else
      {
        for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
        {
          float v = (std::max) (0.f, (std::min)(1.f, m_label_probabilities[corrected_index][fd]));
          m_color[fd] = CGAL::Color((unsigned char)(ramp.r(v) * 255),
                                    (unsigned char)(ramp.g(v) * 255),
                                    (unsigned char)(ramp.b(v) * 255));
        }
      }
    }
    else
    {
      corrected_index -= m_labels.size();
      if (corrected_index >= m_features.size())
      {
        std::cerr << "Error: trying to access feature " << corrected_index << " out of " << m_features.size() << std::endl;
        return;
      }

      Feature_handle feature = m_features[corrected_index];

      float min = (std::numeric_limits<float>::max)();
      float max = -(std::numeric_limits<float>::max)();

      if (vmin != nullptr && vmax != nullptr
          && *vmin != std::numeric_limits<float>::infinity()
          && *vmax != std::numeric_limits<float>::infinity())
      {
        min = *vmin;
        max = *vmax;
      }
      else
      {
        for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
        {
          if (feature->value(fd) > max)
            max = feature->value(fd);
          if (feature->value(fd) < min)
            min = feature->value(fd);
        }
      }

      for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
      {
        float v = (feature->value(fd) - min) / (max - min);
        if (v < 0.f) v = 0.f;
        if (v > 1.f) v = 1.f;

        m_color[fd] = CGAL::Color((unsigned char)(ramp.r(v) * 255),
                                  (unsigned char)(ramp.g(v) * 255),
                                  (unsigned char)(ramp.b(v) * 255));
      }

      if (vmin != nullptr && vmax != nullptr)
      {
        *vmin = min;
        *vmax = max;
      }
    }
  }
}

void Surface_mesh_item_classification::compute_features (std::size_t nb_scales, float voxel_size)
{
  std::cerr << "Computing features with " << nb_scales << " scale(s) and ";
  if (voxel_size == -1)
    std::cerr << "automatic voxel size" << std::endl;
  else
    std::cerr << "voxel size = " << voxel_size << std::endl;

  m_features.clear();

  if (m_generator != nullptr)
    delete m_generator;

  Face_center_map fc_map (m_mesh->polyhedron());

  m_generator = new Generator (*(m_mesh->polyhedron()), fc_map, nb_scales, voxel_size);

#ifdef CGAL_LINKED_WITH_TBB
  m_features.begin_parallel_additions();
#endif

  m_generator->generate_point_based_features(m_features);
  m_generator->generate_face_based_features(m_features);

#ifdef CGAL_LINKED_WITH_TBB
  m_features.end_parallel_additions();
#endif

  delete m_sowf;
  m_sowf = new Sum_of_weighted_features (m_labels, m_features);
  if (m_ethz != nullptr)
  {
    delete m_ethz;
    m_ethz = nullptr;
  }
#ifdef CGAL_LINKED_WITH_OPENCV
  if (m_random_forest != nullptr)
  {
    delete m_random_forest;
    m_random_forest = nullptr;
  }
#endif
  std::cerr << "Features = " << m_features.size() << std::endl;
}

void Surface_mesh_item_classification::train (int classifier, const QMultipleInputDialog& dialog)
{
  if (m_features.size() == 0)
  {
    std::cerr << "Error: features not computed" << std::endl;
    return;
  }

  m_label_probabilities.clear();
  m_label_probabilities.resize (m_labels.size());
  for (std::size_t i = 0; i < m_label_probabilities.size(); ++ i)
    m_label_probabilities[i].resize (num_faces(*(m_mesh->polyhedron())));

  std::vector<std::size_t> training (num_faces(*(m_mesh->polyhedron())), std::size_t(-1));
  std::vector<std::size_t> indices (num_faces(*(m_mesh->polyhedron())), std::size_t(-1));

  std::vector<std::size_t> nb_label (m_labels.size(), 0);
  std::size_t nb_total = 0;

  for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
  {
    training[fd] = m_training[fd];
    if (training[fd] != std::size_t(-1))
    {
      nb_label[training[fd]] ++;
      ++ nb_total;
    }
  }

  std::cerr << nb_total << " face(s) used for training ("
            << 100. * (nb_total / double(m_mesh->polyhedron()->faces().size())) << "% of the total):" << std::endl;
  for (std::size_t i = 0; i < m_labels.size(); ++ i)
    std::cerr << " * " << m_labels[i]->name() << ": " << nb_label[i] << " face(s)" << std::endl;

  if (classifier == CGAL_CLASSIFICATION_SOWF_NUMBER)
  {
    m_sowf->train<Concurrency_tag>(training, dialog.get<QSpinBox>("trials")->value());
    CGAL::Classification::classify<Concurrency_tag> (m_mesh->polyhedron()->faces(),
                                                     m_labels, *m_sowf,
                                                     indices, m_label_probabilities);
  }
  else if (classifier == CGAL_CLASSIFICATION_ETHZ_NUMBER)
  {
    if (m_ethz != nullptr)
      delete m_ethz;
    m_ethz = new ETHZ_random_forest (m_labels, m_features);
    m_ethz->train<Concurrency_tag>(training, true,
                                   dialog.get<QSpinBox>("num_trees")->value(),
                                   dialog.get<QSpinBox>("max_depth")->value());
    CGAL::Classification::classify<Concurrency_tag> (m_mesh->polyhedron()->faces(),
                                                     m_labels, *m_ethz,
                                                     indices, m_label_probabilities);
  }
  else if (classifier == CGAL_CLASSIFICATION_OPENCV_NUMBER)
  {
#ifdef CGAL_LINKED_WITH_OPENCV
    if (m_random_forest != nullptr)
      delete m_random_forest;
    m_random_forest = new Random_forest (m_labels, m_features,
                                         dialog.get<QSpinBox>("max_depth")->value(), 5, 15,
                                         dialog.get<QSpinBox>("num_trees")->value());
    m_random_forest->train (training);

    CGAL::Classification::classify<Concurrency_tag> (m_mesh->polyhedron()->faces(),
                                                     m_labels, *m_random_forest,
                                                     indices, m_label_probabilities);
#endif
  }

  for(face_descriptor fd : faces(*(m_mesh->polyhedron())))
    m_classif[fd] = indices[fd];

  if (m_index_color == 1 || m_index_color == 2)
     change_color (m_index_color);
}

bool Surface_mesh_item_classification::run (int method, int classifier,
                                            std::size_t subdivisions, double smoothing)
{
  if (m_features.size() == 0)
  {
    std::cerr << "Error: features not computed" << std::endl;
    return false;
  }

  if (classifier == CGAL_CLASSIFICATION_SOWF_NUMBER)
    run (method, *m_sowf, subdivisions, smoothing);
  else if (classifier == CGAL_CLASSIFICATION_ETHZ_NUMBER)
  {
    if (m_ethz == nullptr)
    {
      std::cerr << "Error: ETHZ Random Forest must be trained or have a configuration loaded first" << std::endl;
      return false;
    }
    run (method, *m_ethz, subdivisions, smoothing);
  }
  else if (classifier == CGAL_CLASSIFICATION_OPENCV_NUMBER)
  {
#ifdef CGAL_LINKED_WITH_OPENCV
    if (m_random_forest == nullptr)
    {
      std::cerr << "Error: OpenCV Random Forest must be trained or have a configuration loaded first" << std::endl;
      return false;
    }
    run (method, *m_random_forest, subdivisions, smoothing);
#endif
  }

  return true;
}


#pragma once

#include <Eigen/Dense>

namespace Discregrid {

#ifdef DISCREGRID_USE_DOUBLE
using real = double;
#else
using real = float;
#endif

using Vector3r = Eigen::Matrix<real, 3, 1>;
using Matrix3r = Eigen::Matrix<real, 3, 3>;
using AlignedBox3r = Eigen::AlignedBox<real, 3>;

}


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include "NeonWorkloadFactoryHelper.hpp"

#include <test/TensorHelpers.hpp>
#include <test/UnitTests.hpp>

#include <backendsCommon/CpuTensorHandle.hpp>
#include <neon/NeonLayerSupport.hpp>
#include <neon/NeonWorkloadFactory.hpp>
#include <reference/RefWorkloadFactory.hpp>
#include <backendsCommon/test/ActivationFixture.hpp>
#include <backendsCommon/test/LayerTests.hpp>
#include <backendsCommon/test/TensorCopyUtils.hpp>
#include <backendsCommon/test/WorkloadTestUtils.hpp>

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(Compute_ArmComputeNeon)
using FactoryType = armnn::NeonWorkloadFactory;

// ============================================================================
// UNIT tests

// Convolution
ARMNN_AUTO_TEST_CASE(SimpleConvolution1d, Convolution1dTest, true)

ARMNN_AUTO_TEST_CASE(SimpleConvolution2d, SimpleConvolution2d3x5Test, true, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleConvolution2dNhwc, SimpleConvolution2d3x5Test, true, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(SimpleConvolution2d3x3Uint8, SimpleConvolution2d3x3Uint8Test, true, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleConvolution2d3x3Uint8Nhwc, SimpleConvolution2d3x3Uint8Test, true, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UnbiasedConvolution2d, SimpleConvolution2d3x5Test, false, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(UnbiasedConvolution2dNhwc, SimpleConvolution2d3x5Test, false, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UnbiasedConvolution2dStride2x2Nhwc,
                     SimpleConvolution2d3x3Stride2x2Test, false, armnn::DataLayout::NHWC)

ARMNN_AUTO_TEST_CASE(UnbiasedConvolution2dSquare, SimpleConvolution2d3x3Test, false, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleConvolution2dAsymmetricPadding, Convolution2dAsymmetricPaddingTest, armnn::DataLayout::NCHW)

ARMNN_AUTO_TEST_CASE(UnbiasedConvolution2dSquareNhwc, SimpleConvolution2d3x3Test, false, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(SimpleConvolution2dAsymmetricPaddingNhwc,
                     Convolution2dAsymmetricPaddingTest,
                     armnn::DataLayout::NHWC)

ARMNN_AUTO_TEST_CASE(SimpleConvolution2dSquareNhwc, SimpleConvolution2d3x3NhwcTest, false)

// Depthwise Convolution
ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dDepthMul1,
                     DepthwiseConvolution2dDepthMul1Test, true, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dDepthMul1,
                     DepthwiseConvolution2dDepthMul1Test, false, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dDepthMul1Uint8,
                     DepthwiseConvolution2dDepthMul1Uint8Test, true, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dDepthMul1Uint8,
                     DepthwiseConvolution2dDepthMul1Uint8Test, false, armnn::DataLayout::NCHW)

// NHWC Depthwise Convolution
ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dDepthMul1NHhwc,
                     DepthwiseConvolution2dDepthMul1Test, true, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dDepthMul1Nhwc,
                     DepthwiseConvolution2dDepthMul1Test, false, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dDepthMul1Uint8Nhwc,
                     DepthwiseConvolution2dDepthMul1Uint8Test, true, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dDepthMul1Uint8Nhwc,
                     DepthwiseConvolution2dDepthMul1Uint8Test, false, armnn::DataLayout::NHWC)

ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dDepthNhwc, DepthwiseConvolution2dDepthNhwcTest, false)
ARMNN_AUTO_TEST_CASE(SimpleDepthwiseConvolution2d3x3Dilation3x3Nhwc,
                     SimpleDepthwiseConvolution2d3x3Dilation3x3NhwcTest)

ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dAsymmetric,
                     DepthwiseConvolution2dAsymmetricTest, true, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dAsymmetric,
                     DepthwiseConvolution2dAsymmetricTest, false, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(DepthwiseConvolution2dAsymmetricNhwc,
                     DepthwiseConvolution2dAsymmetricTest, true, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UnbiasedDepthwiseConvolution2dAsymmetricNhwc,
                     DepthwiseConvolution2dAsymmetricTest, false, armnn::DataLayout::NHWC)

namespace
{

armnn::DepthwiseConvolution2dDescriptor MakeDepthwiseConv2dDesc(uint32_t strideX, uint32_t strideY,
    uint32_t depthMultiplier = 1, uint32_t padLeft = 0, uint32_t padRight = 0,
    uint32_t padTop = 0, uint32_t padBottom = 0)
{
    boost::ignore_unused(depthMultiplier);

    armnn::DepthwiseConvolution2dDescriptor desc;

    desc.m_PadLeft = padLeft;
    desc.m_PadRight = padRight;

    desc.m_PadTop = padTop;
    desc.m_PadBottom = padBottom;
    desc.m_StrideX = strideX;
    desc.m_StrideY = strideY;
    desc.m_BiasEnabled = false;

    return desc;
}

armnn::TensorInfo CreateOutputTensorInfo(const armnn::TensorInfo& inputInfo,
                                         const armnn::TensorInfo& weightsInfo,
                                         const armnn::DepthwiseConvolution2dDescriptor& descriptor,
                                         armnn::DataType dataType)
{
    const armnn::TensorShape& inputShape  = inputInfo.GetShape();
    const armnn::TensorShape& filterShape = weightsInfo.GetShape();

    unsigned int inWidth = inputShape[3];
    unsigned int inHeight = inputShape[2];
    unsigned int inBatchSize = inputShape[0];

    unsigned int filterWidth = filterShape[3];
    unsigned int readWidth = (inWidth + descriptor.m_PadLeft + descriptor.m_PadRight) - (filterWidth);
    unsigned int outWidth =  1u + (readWidth / descriptor.m_StrideX);

    unsigned int filterHeight = filterShape[2];
    unsigned int readHeight = (inHeight + descriptor.m_PadTop + descriptor.m_PadBottom) - (filterHeight);
    unsigned int outHeight = 1u + (readHeight / descriptor.m_StrideY);
    unsigned int depthMultiplier = filterShape[0];

    unsigned int outChannels = filterShape[1] * depthMultiplier;
    unsigned int outBatchSize = inBatchSize;

    armnn::TensorShape outputShape({outBatchSize, outChannels, outHeight, outWidth});
    return armnn::TensorInfo(outputShape, dataType);
}
}

BOOST_AUTO_TEST_CASE(DepthwiseConv2dUtils)
{
    const armnn::DataType dataType = armnn::DataType::Float32;

    armnn::TensorInfo inputInfo({1, 1, 10, 10 }, dataType);
    armnn::TensorInfo outputInfo;
    armnn::TensorInfo weightsInfo3x3({ 1, 1, 3, 3 }, dataType);
    armnn::TensorInfo biasesInfo;

    armnn::DepthwiseConvolution2dDescriptor descriptor;
    armnn::NeonLayerSupport layerSupport;

    // Strides supported: 1,2,3
    descriptor = MakeDepthwiseConv2dDesc(1, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(1, 2);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(1, 3);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(2, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(2, 2);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(2, 3);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(3, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(3, 2);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    descriptor = MakeDepthwiseConv2dDesc(3, 3);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    // Supported stride 4
    descriptor = MakeDepthwiseConv2dDesc(4, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));

    // Supported weights shape 1x1
    armnn::TensorInfo weightsInfo1x1({ 1, 1, 1, 1 }, armnn::DataType::Float32);
    descriptor = MakeDepthwiseConv2dDesc(1, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo1x1, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo1x1, biasesInfo));

    // Supported shape 2x2
    armnn::TensorInfo weightsInfo2x2({ 1, 1, 2, 2 }, armnn::DataType::Float32);
    descriptor = MakeDepthwiseConv2dDesc(1, 1);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo2x2, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo2x2, biasesInfo));

    // Asymmetric padding
    descriptor = MakeDepthwiseConv2dDesc(1, 1, 1, 1, 2, 1, 2);
    outputInfo = CreateOutputTensorInfo(inputInfo, weightsInfo3x3, descriptor, dataType);
    BOOST_TEST(layerSupport.IsDepthwiseConvolutionSupported(inputInfo, outputInfo, descriptor,
                                                            weightsInfo3x3, biasesInfo));
}

// Pooling
ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2dSize3x3Stride2x4, SimpleMaxPooling2dSize3x3Stride2x4Test, true)
ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2dSize3x3Stride2x4Uint8, SimpleMaxPooling2dSize3x3Stride2x4Uint8Test, true)

ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2d, SimpleMaxPooling2dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2dNhwc, SimpleMaxPooling2dTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2dUint8, SimpleMaxPooling2dUint8Test, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleMaxPooling2dUint8Nhwc, SimpleMaxPooling2dUint8Test, armnn::DataLayout::NHWC)

ARMNN_AUTO_TEST_CASE(SimpleAveragePooling2d, SimpleAveragePooling2dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleAveragePooling2dNhwc, SimpleAveragePooling2dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleAveragePooling2dUint8, SimpleAveragePooling2dUint8Test, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleAveragePooling2dUint8Nhwc, SimpleAveragePooling2dUint8Test, armnn::DataLayout::NHWC)

ARMNN_AUTO_TEST_CASE(LargeTensorsAveragePooling2d, LargeTensorsAveragePooling2dTest)
ARMNN_AUTO_TEST_CASE(LargeTensorsAveragePooling2dUint8, LargeTensorsAveragePooling2dUint8Test)

ARMNN_AUTO_TEST_CASE(SimpleL2Pooling2d, SimpleL2Pooling2dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleL2Pooling2dNeon, SimpleL2Pooling2dTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_SimpleL2Pooling2dUint8, SimpleL2Pooling2dUint8Test, armnn::DataLayout::NCHW)

ARMNN_AUTO_TEST_CASE(L2Pooling2dSize3Stride1, L2Pooling2dSize3Stride1Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_L2Pooling2dSize3Stride1Uint8, L2Pooling2dSize3Stride1Uint8Test)
ARMNN_AUTO_TEST_CASE(L2Pooling2dSize3Stride3, L2Pooling2dSize3Stride3Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_L2Pooling2dSize3Stride3Uint8, L2Pooling2dSize3Stride3Uint8Test)
ARMNN_AUTO_TEST_CASE(L2Pooling2dSize3Stride4, L2Pooling2dSize3Stride4Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_L2Pooling2dSize3Stride4Uint8, L2Pooling2dSize3Stride4Uint8Test)
ARMNN_AUTO_TEST_CASE(L2Pooling2dSize7, L2Pooling2dSize7Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_L2Pooling2dSize7Uint8, L2Pooling2dSize7Uint8Test)
ARMNN_AUTO_TEST_CASE(L2Pooling2dSize9, L2Pooling2dSize9Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_L2Pooling2dSize9Uint8, L2Pooling2dSize9Uint8Test)

// Ignore padding values for pooling but count padding fields into the divisor
ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleMaxPooling2d, IgnorePaddingSimpleMaxPooling2dTest)
ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleMaxPooling2dUint8, IgnorePaddingSimpleMaxPooling2dUint8Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingMaxPooling2dSize3, IgnorePaddingMaxPooling2dSize3Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingMaxPooling2dSize3Uint8, IgnorePaddingMaxPooling2dSize3Uint8Test)

ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleAveragePooling2d, IgnorePaddingSimpleAveragePooling2dTest)
ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleAveragePooling2dUint8, IgnorePaddingSimpleAveragePooling2dUint8Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleAveragePooling2dNoPadding, IgnorePaddingSimpleAveragePooling2dNoPaddingTest)
ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleAveragePooling2dNoPaddingUint8,
    IgnorePaddingSimpleAveragePooling2dNoPaddingUint8Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingAveragePooling2dSize3, IgnorePaddingAveragePooling2dSize3Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingAveragePooling2dSize3Uint8, IgnorePaddingAveragePooling2dSize3Uint8Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingAveragePooling2dSize3x2Stride2x2,
                             IgnorePaddingAveragePooling2dSize3x2Stride2x2Test, false)
ARMNN_AUTO_TEST_CASE(IgnorePaddingAveragePooling2dSize3x2Stride2x2NoPadding,
                             IgnorePaddingAveragePooling2dSize3x2Stride2x2Test,
                                          true)

ARMNN_AUTO_TEST_CASE(IgnorePaddingSimpleL2Pooling2d, IgnorePaddingSimpleL2Pooling2dTest)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_IgnorePaddingSimpleL2Pooling2dUint8, IgnorePaddingSimpleL2Pooling2dUint8Test)
ARMNN_AUTO_TEST_CASE(IgnorePaddingL2Pooling2dSize3, IgnorePaddingL2Pooling2dSize3Test)
ARMNN_AUTO_TEST_CASE(UNSUPPORTED_IgnorePaddingL2Pooling2dSize3Uint8, IgnorePaddingL2Pooling2dSize3Uint8Test)

// Activation
ARMNN_AUTO_TEST_CASE(ConstantLinearActivation, ConstantLinearActivationTest)

// ReLu
ARMNN_AUTO_TEST_CASE(ReLu1Uint8, BoundedReLuUint8UpperAndLowerBoundTest)
ARMNN_AUTO_TEST_CASE(ReLu6Uint8, BoundedReLuUint8UpperBoundOnlyTest)

// Sigmoid
ARMNN_AUTO_TEST_CASE(SimpleSigmoid, SimpleSigmoidTest)
ARMNN_AUTO_TEST_CASE(SimpleSigmoidUint8, SimpleSigmoidUint8Test)

// Softmax
ARMNN_AUTO_TEST_CASE(SimpleSoftmaxBeta1, SimpleSoftmaxTest, 1.0f)
ARMNN_AUTO_TEST_CASE(SimpleSoftmaxBeta2, SimpleSoftmaxTest, 2.0f)
ARMNN_AUTO_TEST_CASE(SimpleSoftmaxBeta1Uint8, SimpleSoftmaxUint8Test, 1.0f)
ARMNN_AUTO_TEST_CASE(SimpleSoftmaxBeta2Uint8, SimpleSoftmaxUint8Test, 2.0f)

ARMNN_AUTO_TEST_CASE(Simple3dSoftmaxBeta1, Simple3dSoftmaxTest, 1.0f)
ARMNN_AUTO_TEST_CASE(Simple3dSoftmaxBeta1Uint8, Simple3dSoftmaxUint8Test, 1.0f)

ARMNN_AUTO_TEST_CASE(Simple4dSoftmaxBeta1, Simple4dSoftmaxTest, 1.0f)
ARMNN_AUTO_TEST_CASE(Simple4dSoftmaxBeta1Uint8, Simple4dSoftmaxUint8Test, 1.0f)

// Splitter
ARMNN_AUTO_TEST_CASE(SimpleSplitter, SplitterTest)
ARMNN_AUTO_TEST_CASE(SimpleSplitterUint8, SplitterUint8Test)

ARMNN_AUTO_TEST_CASE(CopyViaSplitter, CopyViaSplitterTest)
ARMNN_AUTO_TEST_CASE(CopyViaSplitterUint8, CopyViaSplitterUint8Test)

// Concat
ARMNN_AUTO_TEST_CASE(SimpleConcat, ConcatTest)
ARMNN_AUTO_TEST_CASE(ConcatUint8, ConcatUint8Test)

// Fully Connected
ARMNN_AUTO_TEST_CASE(SimpleFullyConnected, FullyConnectedFloat32Test, false, false)
ARMNN_AUTO_TEST_CASE(SimpleFullyConnectedWithBias, FullyConnectedFloat32Test, true, false)
ARMNN_AUTO_TEST_CASE(SimpleFullyConnectedWithTranspose, FullyConnectedFloat32Test, false, true)
ARMNN_AUTO_TEST_CASE(FullyConnectedLarge, FullyConnectedLargeTest, false)
ARMNN_AUTO_TEST_CASE(FullyConnectedLargeTransposed, FullyConnectedLargeTest, true)
ARMNN_AUTO_TEST_CASE(FullyConnectedUint8, FullyConnectedUint8Test, false)
ARMNN_AUTO_TEST_CASE(FullyConnectedBiasedUint8, FullyConnectedUint8Test, true)

// Add
ARMNN_AUTO_TEST_CASE(SimpleAdd, AdditionTest)
ARMNN_AUTO_TEST_CASE(AddBroadcast, AdditionBroadcastTest)
ARMNN_AUTO_TEST_CASE(AddBroadcast1Element, AdditionBroadcast1ElementTest)

// Sub
ARMNN_AUTO_TEST_CASE(SimpleSub, SubtractionTest)
ARMNN_AUTO_TEST_CASE(SubBroadcast1Element, SubtractionBroadcast1ElementTest)
ARMNN_AUTO_TEST_CASE(SubBroadcast, SubtractionBroadcastTest)
ARMNN_AUTO_TEST_CASE(SubtractionUint8, SubtractionUint8Test)
ARMNN_AUTO_TEST_CASE(SubBroadcastUint8, SubtractionBroadcastUint8Test)
ARMNN_AUTO_TEST_CASE(SubBroadcast1ElementUint8, SubtractionBroadcast1ElementUint8Test)

// Mul
ARMNN_AUTO_TEST_CASE(SimpleMultiplication, MultiplicationTest)
ARMNN_AUTO_TEST_CASE(MultiplicationBroadcast1Element, MultiplicationBroadcast1ElementTest)
ARMNN_AUTO_TEST_CASE(MultiplicationBroadcast1DVector, MultiplicationBroadcast1DVectorTest)
ARMNN_AUTO_TEST_CASE(MultiplicationUint8, MultiplicationUint8Test)
ARMNN_AUTO_TEST_CASE(MultiplicationBroadcast1ElementUint8, MultiplicationBroadcast1ElementUint8Test)
ARMNN_AUTO_TEST_CASE(MultiplicationBroadcast1DVectorUint8, MultiplicationBroadcast1DVectorUint8Test)

// Batch Norm
ARMNN_AUTO_TEST_CASE(BatchNorm, BatchNormTest)
ARMNN_AUTO_TEST_CASE(BatchNormNhwc, BatchNormNhwcTest)

// Constant
ARMNN_AUTO_TEST_CASE(Constant, ConstantTest)
ARMNN_AUTO_TEST_CASE(ConstantUint8, ConstantUint8SimpleQuantizationScaleNoOffsetTest)

// Concatenation
ARMNN_AUTO_TEST_CASE(Concatenation1d, Concatenation1dTest)
ARMNN_AUTO_TEST_CASE(Concatenation1dUint8, Concatenation1dUint8Test)

ARMNN_AUTO_TEST_CASE(Concatenation2dDim0, Concatenation2dDim0Test)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim0Uint8, Concatenation2dDim0Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim1, Concatenation2dDim1Test)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim1Uint8, Concatenation2dDim1Uint8Test)

ARMNN_AUTO_TEST_CASE(Concatenation2dDim0DiffInputDims, Concatenation2dDim0DiffInputDimsTest)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim0DiffInputDimsUint8, Concatenation2dDim0DiffInputDimsUint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim1DiffInputDims, Concatenation2dDim1DiffInputDimsTest)
ARMNN_AUTO_TEST_CASE(Concatenation2dDim1DiffInputDimsUint8, Concatenation2dDim1DiffInputDimsUint8Test)

ARMNN_AUTO_TEST_CASE(Concatenation3dDim0, Concatenation3dDim0Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim0Uint8, Concatenation3dDim0Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim1, Concatenation3dDim1Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim1Uint8, Concatenation3dDim1Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim2, Concatenation3dDim2Test, false)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim2Uint8, Concatenation3dDim2Uint8Test, false)

ARMNN_AUTO_TEST_CASE(Concatenation3dDim0DiffInputDims, Concatenation3dDim0DiffInputDimsTest)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim0DiffInputDimsUint8, Concatenation3dDim0DiffInputDimsUint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim1DiffInputDims, Concatenation3dDim1DiffInputDimsTest)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim1DiffInputDimsUint8, Concatenation3dDim1DiffInputDimsUint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim2DiffInputDims, Concatenation3dDim2DiffInputDimsTest, false)
ARMNN_AUTO_TEST_CASE(Concatenation3dDim2DiffInputDimsUint8, Concatenation3dDim2DiffInputDimsUint8Test, false)

ARMNN_AUTO_TEST_CASE(Concatenation4dDim0, Concatenation4dDim0Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDim1, Concatenation4dDim1Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDim3, Concatenation4dDim3Test, false)
ARMNN_AUTO_TEST_CASE(Concatenation4dDim0Uint8, Concatenation4dDim0Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDim1Uint8, Concatenation4dDim1Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDim3Uint8, Concatenation4dDim3Uint8Test, false)

ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim0, Concatenation4dDiffShapeDim0Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim1, Concatenation4dDiffShapeDim1Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim3, Concatenation4dDiffShapeDim3Test, false)
ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim0Uint8, Concatenation4dDiffShapeDim0Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim1Uint8, Concatenation4dDiffShapeDim1Uint8Test)
ARMNN_AUTO_TEST_CASE(Concatenation4dDiffShapeDim3Uint8, Concatenation4dDiffShapeDim3Uint8Test, false)
// L2 Normalization
ARMNN_AUTO_TEST_CASE(L2Normalization1d, L2Normalization1dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(L2Normalization2d, L2Normalization2dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(L2Normalization3d, L2Normalization3dTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(L2Normalization4d, L2Normalization4dTest, armnn::DataLayout::NCHW)

ARMNN_AUTO_TEST_CASE(L2Normalization1dNhwc, L2Normalization1dTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(L2Normalization2dNhwc, L2Normalization2dTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(L2Normalization3dNhwc, L2Normalization3dTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(L2Normalization4dNhwc, L2Normalization4dTest, armnn::DataLayout::NHWC)

// Floor
ARMNN_AUTO_TEST_CASE(SimpleFloor, SimpleFloorTest)

// Greater
ARMNN_AUTO_TEST_CASE(SimpleGreater, GreaterSimpleTest)
ARMNN_AUTO_TEST_CASE(GreaterBroadcast1Element, GreaterBroadcast1ElementTest)
ARMNN_AUTO_TEST_CASE(GreaterBroadcast1DVector, GreaterBroadcast1DVectorTest)
ARMNN_AUTO_TEST_CASE(GreaterUint8, GreaterUint8Test)
ARMNN_AUTO_TEST_CASE(GreaterBroadcast1ElementUint8, GreaterBroadcast1ElementUint8Test)
ARMNN_AUTO_TEST_CASE(GreaterBroadcast1DVectorUint8, GreaterBroadcast1DVectorUint8Test)

// Reshape
ARMNN_AUTO_TEST_CASE(SimpleReshapeFloat32, SimpleReshapeFloat32Test)
ARMNN_AUTO_TEST_CASE(SimpleReshapeUint8, SimpleReshapeUint8Test)

// Pad
ARMNN_AUTO_TEST_CASE(PadFloat322d, PadFloat322dTest)
ARMNN_AUTO_TEST_CASE(PadFloat323d, PadFloat323dTest)
ARMNN_AUTO_TEST_CASE(PadFloat324d, PadFloat324dTest)

// Permute
ARMNN_AUTO_TEST_CASE(SimplePermuteFloat32, SimplePermuteFloat32Test)
ARMNN_AUTO_TEST_CASE(SimplePermuteUint8, SimplePermuteUint8Test)
ARMNN_AUTO_TEST_CASE(PermuteFloat32ValueSet1, PermuteFloat32ValueSet1Test)
ARMNN_AUTO_TEST_CASE(PermuteFloat32ValueSet2, PermuteFloat32ValueSet2Test)
ARMNN_AUTO_TEST_CASE(PermuteFloat32ValueSet3, PermuteFloat32ValueSet3Test)

// Lstm
ARMNN_AUTO_TEST_CASE(LstmLayerFloat32WithCifgWithPeepholeNoProjection,
                     LstmLayerFloat32WithCifgWithPeepholeNoProjectionTest)
ARMNN_AUTO_TEST_CASE(LstmLayerFloat32NoCifgNoPeepholeNoProjection,
                     LstmLayerFloat32NoCifgNoPeepholeNoProjectionTest)
ARMNN_AUTO_TEST_CASE(LstmLayerFloat32NoCifgWithPeepholeWithProjection,
                     LstmLayerFloat32NoCifgWithPeepholeWithProjectionTest)

// Mean
ARMNN_AUTO_TEST_CASE(MeanUint8Simple, MeanUint8SimpleTest)
ARMNN_AUTO_TEST_CASE(MeanUint8SimpleAxis, MeanUint8SimpleAxisTest)
ARMNN_AUTO_TEST_CASE(MeanUint8KeepDims, MeanUint8KeepDimsTest)
ARMNN_AUTO_TEST_CASE(MeanUint8MultipleDims, MeanUint8MultipleDimsTest)
ARMNN_AUTO_TEST_CASE(MeanVtsUint8, MeanVtsUint8Test)

ARMNN_AUTO_TEST_CASE(MeanFloatSimple, MeanFloatSimpleTest)
ARMNN_AUTO_TEST_CASE(MeanFloatSimpleAxis, MeanFloatSimpleAxisTest)
ARMNN_AUTO_TEST_CASE(MeanFloatKeepDims, MeanFloatKeepDimsTest)
ARMNN_AUTO_TEST_CASE(MeanFloatMultipleDims, MeanFloatMultipleDimsTest)
ARMNN_AUTO_TEST_CASE(MeanVtsFloat1, MeanVtsFloat1Test)
ARMNN_AUTO_TEST_CASE(MeanVtsFloat2, MeanVtsFloat2Test)
ARMNN_AUTO_TEST_CASE(MeanVtsFloat3, MeanVtsFloat3Test)

// Max
ARMNN_AUTO_TEST_CASE(SimpleMaximum, MaximumSimpleTest)
ARMNN_AUTO_TEST_CASE(MaximumBroadcast1Element, MaximumBroadcast1ElementTest)
ARMNN_AUTO_TEST_CASE(MaximumBroadcast1DVector, MaximumBroadcast1DVectorTest)
ARMNN_AUTO_TEST_CASE(MaximumUint8, MaximumUint8Test)
ARMNN_AUTO_TEST_CASE(MaximumBroadcast1ElementUint8, MaximumBroadcast1ElementUint8Test)
ARMNN_AUTO_TEST_CASE(MaximumBroadcast1DVectorUint8, MaximumBroadcast1DVectorUint8Test)

// Min
ARMNN_AUTO_TEST_CASE(SimpleMinimum1, MinimumBroadcast1ElementTest1)
ARMNN_AUTO_TEST_CASE(SimpleMinimum2, MinimumBroadcast1ElementTest2)
ARMNN_AUTO_TEST_CASE(Minimum1DVectorUint8, MinimumBroadcast1DVectorUint8Test)

// Normalization
ARMNN_AUTO_TEST_CASE(SimpleNormalizationAcross, SimpleNormalizationAcrossTest)
ARMNN_AUTO_TEST_CASE(SimpleNormalizationWithin, SimpleNormalizationWithinTest)
ARMNN_AUTO_TEST_CASE(SimpleNormalizationAcrossNhwc, SimpleNormalizationAcrossNhwcTest)

// Resize Bilinear - NCHW data layout
ARMNN_AUTO_TEST_CASE(ResizeBilinearNop, ResizeBilinearNopTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(SimpleResizeBilinear, SimpleResizeBilinearTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(ResizeBilinearSqMin, ResizeBilinearSqMinTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMin, ResizeBilinearMinTest, armnn::DataLayout::NCHW)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMag, ResizeBilinearMagTest, armnn::DataLayout::NCHW)

ARMNN_AUTO_TEST_CASE(ResizeBilinearNopUint8, ResizeBilinearNopUint8Test)
ARMNN_AUTO_TEST_CASE(SimpleResizeBilinearUint8, SimpleResizeBilinearUint8Test)
ARMNN_AUTO_TEST_CASE(ResizeBilinearSqMinUint8, ResizeBilinearSqMinUint8Test)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMinUint8, ResizeBilinearMinUint8Test)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMagUint8, ResizeBilinearMagUint8Test)

// Resize Bilinear - NHWC data layout
ARMNN_AUTO_TEST_CASE(ResizeBilinearNopNhwc, ResizeBilinearNopTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(SimpleResizeBilinearNhwc, SimpleResizeBilinearTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(ResizeBilinearSqMinNhwc, ResizeBilinearSqMinTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMinNhwc, ResizeBilinearMinTest, armnn::DataLayout::NHWC)
ARMNN_AUTO_TEST_CASE(ResizeBilinearMagNhwc, ResizeBilinearMagTest, armnn::DataLayout::NHWC)

// ============================================================================
// COMPARE tests

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareConv2dWithReference, CompareConvolution2dTest)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareDepthwiseConv2dWithReferenceFloat32,
                                 CompareDepthwiseConvolution2dFloatTest,
                                 armnn::DataLayout::NCHW)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareDepthwiseConv2dWithReferenceUint8,
                                 CompareDepthwiseConvolution2dUint8Test,
                                 armnn::DataLayout::NCHW)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareDepthwiseConv2dWithReferenceFloat32Nhwc,
                                 CompareDepthwiseConvolution2dFloatTest,
                                 armnn::DataLayout::NHWC)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareDepthwiseConv2dWithReferenceUint8Nhwc,
                                 CompareDepthwiseConvolution2dUint8Test,
                                 armnn::DataLayout::NHWC)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareNormalizationWithinWithReference, CompareNormalizationTest,
                                 armnn::NormalizationAlgorithmChannel::Within,
                                 armnn::NormalizationAlgorithmMethod::LocalBrightness)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareNormalizationAcrossWithReference, CompareNormalizationTest,
                                 armnn::NormalizationAlgorithmChannel::Across,
                                 armnn::NormalizationAlgorithmMethod::LocalBrightness)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareMaxPooling2dWithReference, ComparePooling2dTest, armnn::PoolingAlgorithm::Max)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareMaxPooling2dWithReferenceUint8, ComparePooling2dUint8Test,
                                 armnn::PoolingAlgorithm::Max)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareAveragePooling2dWithReference, ComparePooling2dTest,
                                 armnn::PoolingAlgorithm::Average)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareAveragePooling2dWithReferenceUint8, ComparePooling2dUint8Test,
                                 armnn::PoolingAlgorithm::Average)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareL2Pooling2dWithReference, ComparePooling2dTest, armnn::PoolingAlgorithm::L2)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(UNSUPPORTED_CompareL2Pooling2dWithReferenceUint8, ComparePooling2dUint8Test,
                                 armnn::PoolingAlgorithm::L2)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareSoftmaxBeta1WithReference, CompareSoftmaxTest, 1.0f)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareSoftmaxBeta2WithReference, CompareSoftmaxTest, 2.0f)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareSoftmaxUint8Beta1WithReference, CompareSoftmaxUint8Test, 1.0f)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareSoftmaxUint8Beta2WithReference, CompareSoftmaxUint8Test, 2.0f)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareAddition, CompareAdditionTest)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareMultiplicationWithReference, CompareMultiplicationTest)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(CompareBatchNorm, CompareBatchNormTest)

ARMNN_COMPARE_REF_AUTO_TEST_CASE(ReLu1, CompareBoundedReLuTest, 1.0f, -1.0f)
ARMNN_COMPARE_REF_AUTO_TEST_CASE(ReLu6, CompareBoundedReLuTest, 6.0f, 0.0f)

// ============================================================================
// FIXTURE tests

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareSigmoidActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::Sigmoid, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareTanhActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::TanH, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareLinearActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::Linear, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareReLuActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::ReLu, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareBoundedReLuActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::BoundedReLu, 5u)
ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareBoundedReLuActivationWithReferenceUint8, ActivationFixture,
                                    CompareActivationUint8Test, armnn::ActivationFunction::BoundedReLu)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareSoftReLuActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::SoftReLu, 1u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareLeakyReLuActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::LeakyReLu, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareAbsActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::Abs, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareSqrtActivationWithReference, PositiveActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::Sqrt, 5u)

ARMNN_COMPARE_REF_FIXTURE_TEST_CASE(CompareSquareActivationWithReference, ActivationFixture,
                                    CompareActivationTest, armnn::ActivationFunction::Square, 5u)
BOOST_AUTO_TEST_SUITE_END()