// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include "Bow\BaseBow.h"
#include "Tracking\TrackLocalMap.h"
#include "Image\AnalyzedImage.h"

#include <gsl\gsl>

#include <memory>
#include <vector>

namespace mage
{
    // VI-C : LOCAL MAPPING - NEW MAP POINT CREATION
    // create new map points for keyframe by triangulate ORB from connected keyframes KC in covisibility graph
    void NewMapPointsCreation(
        const BaseBow& bagOfWords,
        const KeyframeProxy& Ki,
        const PerCameraSettings& cameraSettings,
        const NewMapPointsCreationSettings& newMapPointSettings,
        const float scale,
        thread_memory memory,
        gsl::span<MappingKeyframe> Kcs,
        std::vector<MapPointKeyframeAssociations>& newMapPoints);

    // Carries the state, arguments, and cached computations through the NewMapPointsCreation operation.
    // Because this struct carries the state, operations that use its contents may depend on prior
    // operations to populate that content; in other words, any method that takes a NewMapPointsCreationState
    // is expected to modify the state in such a way that it is ready for the next call.
    // used internally by new map points creation
    struct NewMapPointsCreationState
    {
        const BaseBow& BagOfWords;
        const KeyframeProxy& Ki;                        // new keyframe
        const MappingKeyframe* Kc;                      // connected keyframe
        size_t KcKeypointDescriptorIdx;                 // index into the Kc analyzed image's keypoints/descriptors
        size_t KiKeypointDescriptorIdx;                 // index into the Ki analyzed image's keypoints/descriptors
        cv::Matx33f FundamentalMatrixKcToKi;            // fundamental matrix Kc to Ki
        cv::Matx33f FundamentalMatrixKiToKc;            // fundamental matrix Ki to Kc
        cv::Matx34f KiViewMatrix;                       // new keyframe matrix
        cv::Matx33f KiCameraMatrix;                     // Camera Calibration for Ki
        cv::Matx44f KiInverseViewMatrix;                // new keyframe world matrix
        cv::Matx34f KcViewMatrix;                       // connected keyframe matrix
        cv::Matx33f KcCameraMatrix;                     // Camera Calibration for Kc
        cv::Matx44f KcInverseViewMatrix;                // connected keyframe world matrix
        std::set<size_t> KiAssociatedDescriptors;       // new keyframe associated descriptors
        const float KiPyramidScale;                           // Pyramid Scale used when features were extracted of Ki
        const float KiImageBorder;                            // Image Border for which no keypoints could have been extracted
        const size_t KiNumLevels;                             // Number of levels used to extract features in Ki

        const PerCameraSettings& CameraSettings;
        const NewMapPointsCreationSettings& NewMapPointsCreationSettings;
        const float CosMinParallax;
        const float MapScale;

        NewMapPointsCreationState(const BaseBow& bagofWords, const KeyframeProxy& keyFrameId, const PerCameraSettings& cameraSettings, const mage::NewMapPointsCreationSettings& newMapPointsCreationSettings, const float scale);

        void SetKc(const MappingKeyframe& kc)
        {
            Kc = &kc;
            KcInverseViewMatrix = Kc->GetPose().GetInverseViewMatrix();
            KcViewMatrix = Kc->GetPose().GetViewMatrix();
            KcCameraMatrix = Kc->GetAnalyzedImage()->GetUndistortedCalibration().GetCameraMatrix();
        }
    };

    // tests whether a match should be turned into a mappoint, if so creates the new map point
    // used internally by new map points creation
    bool TryCreateNewAssociatedMapPointForMatch(
        NewMapPointsCreationState& state,
        std::vector<MapPointKeyframeAssociations>& keyframeAssociations);

    // For each new map point created in CreateInitialAssociations()
    //     For each covisible keyframe that isn't already associated with this map point, attempt to
    //     match one of the unassociated descriptors of the keyframe with the map point.  If you succeed,
    //     Create the association.
    //
    // This method modifies NewMapPointsCreationState and keyframeAssociations.
    void LocallyAssociateNewAssociations(
        NewMapPointsCreationState& state,
        gsl::span<MappingKeyframe> covisibleKeyframes,
        thread_memory memory,
        std::vector<MapPointKeyframeAssociations>& keyframeAssociations);

    // For each covisible keyframe, match all the unassociated descriptors in that keyframe
    // to the unassociated descriptors in the current keyframe.
    //     For each resulting match (after confirming that the new keyframe descriptor involved
    //     has not been associated by a previous iteration), try to create a map point for the
    //     match.
    //
    // This method modifies the NewMapPointsCreationState and should leave it ready to be passed
    // into LocallyAssociateNewAssociations().  It also modifies newMapPoints and
    // keyframeAssociations.
    void CreateInitialAssociations(
        NewMapPointsCreationState& state,
        gsl::span<MappingKeyframe> covisibleKeyframes,
        std::vector<MapPointKeyframeAssociations>& newMapPoints,
        int maxHammingDist,
        int minHammingDifference,
        ptrdiff_t maxFrameToSearch,
        thread_memory memory);

    bool VerifyProjectInFront(
        const cv::Matx34f& viewMatrix,
        const cv::Matx33f& cameraCalibration,
        const cv::Point3f& triangulatedPoint);
}


#if !defined(lowMachFlow_h)
#define lowMachFlow_h
#include <petsc.h>

// Define the test field number
typedef enum { VTEST, QTEST, WTEST } LowMachFlowTestFields;

typedef enum { STROUHAL, REYNOLDS, FROUDE, PECLET, HEATRELEASE, GAMMA, PTH, MU, K, CP, BETA, GRAVITY_DIRECTION, TOTAL_LOW_MACH_FLOW_PARAMETERS } LowMachFlowParametersTypes;
PETSC_EXTERN const char* lowMachFlowParametersTypeNames[TOTAL_LOW_MACH_FLOW_PARAMETERS + 1];

typedef enum {VEL, PRES, TEMP, TOTAL_LOW_MACH_FLOW_FIELDS} LowMachFlowFields;

typedef enum {MOM, MASS, ENERGY, TOTAL_LOW_MACH_SOURCE_FIELDS} LowMachSourceFields;

// Low Mach Kernels
PETSC_EXTERN void LowMachFlow_vIntegrandTestFunction(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, const PetscReal X[],
    PetscInt numConstants, const PetscScalar constants[], PetscScalar f0[]);

PETSC_EXTERN void LowMachFlow_vIntegrandTestGradientFunction(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[],
                                                             const PetscScalar u_x[], const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[],
                                                             PetscReal t, const PetscReal X[], PetscInt numConstants, const PetscScalar constants[], PetscScalar f1[]);

PETSC_EXTERN void LowMachFlow_wIntegrandTestFunction(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                                     const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, const PetscReal X[],
                                                     PetscInt numConstants, const PetscScalar constants[], PetscScalar f0[]);

PETSC_EXTERN void LowMachFlow_wIntegrandTestGradientFunction(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[],
                                                             const PetscScalar u_x[], const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[],
                                                             PetscReal t, const PetscReal X[], PetscInt numConstants, const PetscScalar constants[], PetscScalar f1[]);

PETSC_EXTERN void LowMachFlow_qIntegrandTestFunction(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                                     const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, const PetscReal X[],
                                                     PetscInt numConstants, const PetscScalar constants[], PetscScalar f0[]);

PETSC_EXTERN void LowMachFlow_g0_qu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                  const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                  PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g1_qu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_qT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_qT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g1_qT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_vT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_vu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g1_vu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g3_vu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g2_vp(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_wu(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g0_wT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g1_wT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

PETSC_EXTERN void LowMachFlow_g3_wT(PetscInt dim, PetscInt Nf, PetscInt NfAux, const PetscInt uOff[], const PetscInt uOff_x[], const PetscScalar u[], const PetscScalar u_t[], const PetscScalar u_x[],
                                    const PetscInt aOff[], const PetscInt aOff_x[], const PetscScalar a[], const PetscScalar a_t[], const PetscScalar a_x[], PetscReal t, PetscReal u_tShift, const PetscReal x[],
                                    PetscInt numConstants, const PetscScalar constants[], PetscScalar g0[]);

#endif


// Copyright @ 2012 Zhi Qiu and Chun Shen
// Ver 2.1
// Note the version used in iSS is slightly slower than the one
// used in iS, do not mix them unless necessary.

#ifndef SRC_EMISSIONFUNCTION_H_
#define SRC_EMISSIONFUNCTION_H_

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

#include <string>
#include <vector>
#include <array>
#include <memory>

#include "Table.h"
#include "TableFunction.h"
#include "ParameterReader.h"
#include "particle_decay.h"
#include "data_struct.h"
#include "data_struct.h"
#include "Random.h"
#include "pretty_ostream.h"

class EmissionFunctionArray {
 private:
    int hydro_mode;   // switch for (2+1)-d or (3+1)-d hypersurface
    int flag_PCE_;
    int flag_restrict_deltaf;
    double deltaf_max_ratio;

    pretty_ostream messager;

    const std::string path_;
    const std::string table_path_;
    const AfterburnerType afterburner_type_;

    std::shared_ptr<RandomUtil::Random> ran_gen_ptr;

    int MC_sampling;
    int number_of_repeated_sampling;
    int local_charge_conservation;

    Table *pT_tab, *phi_tab, *y_minus_eta_tab;
    int pT_tab_length, phi_tab_length;

    // the y_minus_eta_min_index holds the index
    // to the smallest positive y-eta_s value
    int y_minus_eta_tab_length, y_minus_eta_min_index;
    // true if y_minus_eta_tab has only positive part
    bool positive_y_minus_eta_table_only;

    ParameterReader *paraRdr; // used to pass-in parameters
    int USE_OSCAR_FORMAT;
    int USE_GZIP_FORMAT;
    int INCLUDE_DELTAF, INCLUDE_BULK_DELTAF, INCLUDE_DIFFUSION_DELTAF;
    int bulk_deltaf_kind;

    int turn_on_rhob;

    Table *dN_pTdpTdphidy;  // dN / (pt dpt dphi dy)
    // store the largest element when summing over xt and eta
    // to get dN / (pt dpt dphi dy); used during sampling.
    Table *dN_pTdpTdphidy_max;

    double **dN_dxtdetady;  // dN / (d^2x_t deta dy) (correction: and dtau)
    // dN / (d^2x_t deta dy) is the (weighted) sum of all elements of
    // the [pT_tab_length][phi_tab_length]-sized dN/dall matrix;
    // this array records the maximum value of this dN/dall matrix,
    // for each eta and FO-cell.
    double **dN_dxtdetady_pT_max;
    double **dN_dxtdy_4all;  // dN / (dxt dy) for all particles

    // dN/(dxt dy) for one particle species
    std::vector<double> dN_dxtdy_for_one_particle_species;

    int number_of_chosen_particles;
    // used in spectra and flow calculations;
    // it has length Nparticle, 0 means miss, 1 means include
    std::vector<int> chosen_particles_01_table;

    // 0/1: Particles with similar mass and chemical potentials
    // will be sampled using the same dN/(dxt deta dy) matrix
    int grouping_particles;

    // Usable only when grouping_particles is 1.
    // If two particles have mass and chemical potentials
    // close within this relative tolerance, they are considered to be
    // identical and will be sampled successively without regenerating
    // the dN / (dxt deta dy) matrix for efficiency.
    double mc_grouping_tolerance;

    // store particle index;
    // the sampling process follows the order specified by this table
    std::vector<int> chosen_particles_sampling_table;

    // store chosen particle monte carlo number that are not found in pdg.dat
    std::vector<int> unidentifiedPid_table;

    // list for information for all particles
    int Nparticles;
    std::vector<particle_info> particles;

    // list for information for all fluid cells
    long FO_length;
    const std::vector<FO_surf> &FOsurf_ptr;

    // store the last particle index being used by calculate_dNArrays function
    int last_particle_idx;

    double **trig_phi_table, **hypertrig_y_minus_eta_table;
    inline long determine_number_to_sample(
                    double dN, int model=1, double para1=0);

    const gsl_rng_type *gsl_type_random_number;
    gsl_rng *gsl_random_r;

    bool particles_are_the_same(int, int);
    //long *sorted_FZ;

    //array for bulk delta f coefficients
    Table *bulkdf_coeff;

    // table parameter for diffusion deltaf coefficient
    int deltaf_qmu_coeff_table_length_T;
    int deltaf_qmu_coeff_table_length_mu;
    double delta_qmu_coeff_table_T0, delta_qmu_coeff_table_mu0;
    double delta_qmu_coeff_table_dT, delta_qmu_coeff_table_dmu;
    double **deltaf_qmu_coeff_tb;       // kappa_B [1/fm^3]

    // arrays to speed up computing particle yield
    double sf_dx, sf_x_min, sf_x_max;
    int sf_tb_length;
    int sf_expint_truncate_order;
    double** sf_bessel_Kn;
    double** sf_expint_En;

    double lambert_x_min, lambert_x_max, lambert_dx;
    std::vector<double> lambert_W;

    int flag_output_samples_into_files;
    int flag_store_samples_in_memory;

    //! particle decay
    int flag_perform_decays;
    particle_decay *decayer_ptr;

 public:
    EmissionFunctionArray(std::shared_ptr<RandomUtil::Random> ran_gen,
                          Table* chosen_particle, Table* pt_tab_in,
                          Table* phi_tab_in, Table* eta_tab_in,
                          std::vector<particle_info> particles_in,
                          const std::vector<FO_surf> &FOsurf_ptr_in,
                          int flag_PCE_in, ParameterReader* paraRdr_in,
                          std::string path_in, std::string table_path,
                          AfterburnerType afterburner_type);
    ~EmissionFunctionArray();

    std::vector< std::vector<iSS_Hadron>* >* Hadron_list;

    void initialize_special_function_arrays();
    double get_special_function_K1(double arg);
    double get_special_function_K2(double arg);
    void get_special_function_En(double arg, std::vector<double> &results);
    double get_special_function_lambertW(double arg);

    void calculate_dNArrays(int);
    void calculate_dN_dxtdetady(int);
    void calculate_dN_pTdpTdphidy(int);
    void write_dN_pTdpTdphidy_toFile();
    std::string dN_pTdpTdphidy_filename;  // where to save
    void write_dN_dxtdetady_toFile();
    std::string dN_dxtdetady_filename;

    void calculate_flows(int to_order, std::string flow_diff_filename,
                         std::string flow_inte_filename);
    std::string flow_differential_filename_old, flow_integrated_filename_old;
    std::string flow_differential_filename, flow_integrated_filename;

    void calculate_dN_pTdpTdphidy_and_flows_4all_old_output(
                                                    int perform_sampling = 0);
    void calculate_dN_pTdpTdphidy_and_flows_4all(int perform_sampling = 0);
    void calculate_dN_dxtdetady_and_sample_4all();

    void shell();  // it all starts here...

    void combine_samples_to_OSCAR();
    std::string OSCAR_header_filename, OSCAR_output_filename;
    void combine_samples_to_gzip_file();

    // Sample files
    // where samples, its control informations, and its "format file"
    // are stored;
    // the first two can contain a "%d" string to generate multiple files
    std::string samples_format_filename;

    // First sampling method
    void sample_using_dN_dxtdetady_smooth_pT_phi();
    void calculate_dN_dtau_using_dN_dxtdetady(
                    double tau0 = 0, double dtau = 0.5, double tau_max = 17);
    void calculate_dN_dphi_using_dN_pTdpTdphidy();
    void calculate_dN_deta_using_dN_dxtdetady();
    void calculate_dN_dxt_using_dN_dxtdetady();
    void calculate_dN_dx_using_dN_dxtdetady(
                    double x_min, double x_max, double dx);

    // Second sampling method
    void calculate_dN_analytic(const particle_info* particle, double mu,
                               double Temperature,
                               std::array<double, 5> &results);

    // the following variables need to be set first in order to
    // call this function
    void calculate_dN_dxtdy_4all_particles();

    void calculate_dN_dxtdy_for_one_particle_species(const int particle_idx);

    double calculate_total_FZ_energy_flux();
    // to be used after calculate_dN_dxtdy_4all_particles
    void sample_using_dN_dxtdy_4all_particles_conventional();

    // Third sampling method
    void sample_using_dN_pTdpTdphidy();
    Table pT_tab4Sampling, phi_tab4Sampling;
    int pT_tab4Sampling_length, phi_tab4Sampling_length;
    double** trig_phi_tab4Sampling;

    void getbulkvisCoefficients(double Tdec,
                                std::array<double, 3> &bulkvisCoefficients);
    void load_deltaf_qmu_coeff_table(std::string filename);
    double get_deltaf_qmu_coeff(double T, double muB);

    void check_samples_in_memory();
    int get_number_of_sampled_events() {return(Hadron_list->size());};
    int get_number_of_particles(int iev) {
        return((*Hadron_list)[iev]->size());
    };

    iSS_Hadron get_hadron(int iev, int ipart) {
        return((*(*Hadron_list)[iev])[ipart]);
    };

    std::vector<iSS_Hadron>* get_hadron_list_iev(const int iev) {
        return(Hadron_list->at(iev));
    }

    void perform_resonance_feed_down(
                std::vector< std::vector<iSS_Hadron>* >* input_particle_list);
    int compute_number_of_sampling_needed(int number_of_particles_needed);
    double estimate_ideal_maximum(
        int sign, double mass, double Tdec, double mu, double f0_mass,
        TableFunction &z_exp_m_z);
    double estimate_shear_viscous_maximum(
        int sign, double mass, double Tdec, double mu, double f0_mass,
        TableFunction &z_exp_m_z, double pi_size);
    double estimate_diffusion_maximum(
        int sign, int baryon, double mass, double Tdec, double mu,
        double f0_mass, TableFunction &z_exp_m_z,
        double prefactor_qmu, double guess_ideal, double q_size);
    double get_deltaf_bulk(
        double mass, double pdotu, double bulkPi, double Tdec, int sign,
        double f0, const std::array<double, 3> bulkvisCoefficients);
    int sample_momemtum_from_a_fluid_cell(
        const double mass, const double degen, const int sign,
        const int baryon, const int strange, const int charge,
        const double pT_to, const double y_minus_eta_s_range,
        const double maximum_guess, const FO_surf *surf,
        const std::array<double, 3> bulkvisCoefficients,
        const double deltaf_qmu_coeff,
        double &pT, double &phi, double &y_minus_eta_s);
    double estimate_maximum(
        const FO_surf *surf, const int real_particle_idx, const double mass,
        const double sign, const double degen,
        const int baryon, const int strange, const int charge,
        TableFunction &z_exp_m_z,
        const std::array<double, 3> bulkvisCoefficients,
        const double deltaf_qmu_coeff);
    std::string add_one_sampled_particle(
        const int repeated_sampling_idx,
        const unsigned long FO_idx, const FO_surf *surf,
        const int particle_monval, const double mass,
        const double pT, const double phi,
        const double y_minus_eta_s, const double eta_s);
};

#endif  // SRC_EMISSIONFUNCTION_H_

/*----------------------------------------------------------------------
Change log: See changelog.txt.
----------------------------------------------------------------------*/


/* multifit_nlinear/lm.c
 *
 * Copyright (C) 2014, 2015, 2016 Patrick Alken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_multifit_nlinear.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_permutation.h>

/*
 * This module contains an implementation of the Levenberg-Marquardt
 * algorithm for nonlinear optimization problems. This implementation
 * closely follows the following works:
 *
 * [1] H. B. Nielsen, K. Madsen, Introduction to Optimization and
 *     Data Fitting, Informatics and Mathematical Modeling,
 *     Technical University of Denmark (DTU), 2010.
 *
 * [2] J. J. More, The Levenberg-Marquardt Algorithm: Implementation
 *     and Theory, Lecture Notes in Mathematics, v630, 1978.
 */

typedef struct
{
  size_t n;           /* number of observations */
  size_t p;           /* number of parameters */
  gsl_vector *fvv;    /* D_v^2 f(x), size n */
  gsl_vector *vel;    /* geodesic velocity (standard LM step), size p */
  gsl_vector *acc;    /* geodesic acceleration, size p */
  gsl_vector *workp;  /* workspace, length p */
  gsl_vector *workn;  /* workspace, length n */

  int accel;          /* use geodesic acceleration? */

  /* tunable parameters */
  gsl_multifit_nlinear_parameters params;
} lm_state_t;

#include "common.c"

static void *lm_alloc (const int accel, const void * params, const size_t n, const size_t p);
static void *lm_alloc_noaccel (const void * params, const size_t n, const size_t p);
static void *lm_alloc_accel (const void * params, const size_t n, const size_t p);
static void lm_free(void *vstate);
static int lm_init(const void *vtrust_state, void *vstate);
static int lm_preloop(const void * vtrust_state, void * vstate);
static int lm_step(const void * vtrust_state, const double delta,
                   gsl_vector * dx, void * vstate);
static int lm_preduction(const void * vtrust_state, const gsl_vector * dx,
                         double * pred, void * vstate);

static void *
lm_alloc (const int accel, const void * params, const size_t n, const size_t p)
{
  const gsl_multifit_nlinear_parameters *mparams = (const gsl_multifit_nlinear_parameters *) params;
  lm_state_t *state;

  state = calloc(1, sizeof(lm_state_t));
  if (state == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate lm state", GSL_ENOMEM);
    }

  state->workp = gsl_vector_alloc(p);
  if (state->workp == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate space for workp", GSL_ENOMEM);
    }

  state->workn = gsl_vector_alloc(n);
  if (state->workn == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate space for workn", GSL_ENOMEM);
    }

  state->fvv = gsl_vector_alloc(n);
  if (state->fvv == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate space for fvv", GSL_ENOMEM);
    }

  state->vel = gsl_vector_alloc(p);
  if (state->vel == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate space for vel", GSL_ENOMEM);
    }

  state->acc = gsl_vector_alloc(p);
  if (state->acc == NULL)
    {
      GSL_ERROR_NULL ("failed to allocate space for acc", GSL_ENOMEM);
    }

  state->n = n;
  state->p = p;
  state->params = *mparams;
  state->accel = accel;

  return state;
}

static void *
lm_alloc_noaccel (const void * params, const size_t n, const size_t p)
{
  return lm_alloc(0, params, n, p);
}

static void *
lm_alloc_accel (const void * params, const size_t n, const size_t p)
{
  return lm_alloc(1, params, n, p);
}

static void
lm_free(void *vstate)
{
  lm_state_t *state = (lm_state_t *) vstate;

  if (state->workp)
    gsl_vector_free(state->workp);

  if (state->workn)
    gsl_vector_free(state->workn);

  if (state->fvv)
    gsl_vector_free(state->fvv);

  if (state->vel)
    gsl_vector_free(state->vel);

  if (state->acc)
    gsl_vector_free(state->acc);

  free(state);
}

/*
lm_init()
  Initialize LM solver

Inputs: vtrust_state - trust state
        vstate       - workspace

Return: success/error
*/

static int
lm_init(const void *vtrust_state, void *vstate)
{
  const gsl_multifit_nlinear_trust_state *trust_state =
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
  lm_state_t *state = (lm_state_t *) vstate;

  gsl_vector_set_zero(state->vel);
  gsl_vector_set_zero(state->acc);

  *(trust_state->avratio) = 0.0;

  return GSL_SUCCESS;
}

/*
lm_preloop()
  Initialize LM method for new Jacobian matrix
*/

static int
lm_preloop(const void * vtrust_state, void * vstate)
{
  int status;
  const gsl_multifit_nlinear_trust_state *trust_state =
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
  const gsl_multifit_nlinear_parameters *params = trust_state->params;

  (void)vstate;

  /* initialize linear least squares solver */
  status = (params->solver->init)(trust_state, trust_state->solver_state);
  if (status)
    return status;

  return GSL_SUCCESS;
}

/*
lm_step()
  Calculate a new step vector by solving the linear
least squares system:

[      J     ] v = - [ f ]
[ sqrt(mu) D ]       [ 0 ]
*/

static int
lm_step(const void * vtrust_state, const double delta,
        gsl_vector * dx, void * vstate)
{
  int status;
  const gsl_multifit_nlinear_trust_state *trust_state =
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
  lm_state_t *state = (lm_state_t *) vstate;
  const gsl_multifit_nlinear_parameters *params = trust_state->params;
  const double mu = *(trust_state->mu);

  (void)delta;

  /* prepare the linear solver with current LM parameter mu */
  status = (params->solver->presolve)(mu, trust_state, trust_state->solver_state);
  if (status)
    return status;

  /*
   * solve: [     J      ] v = - [ f ]
   *        [ sqrt(mu)*D ]       [ 0 ]
   */
  status = (params->solver->solve)(trust_state->f,
                                   state->vel,
                                   trust_state,
                                   trust_state->solver_state);
  if (status)
    return status;

  if (state->accel)
    {
      double anorm, vnorm;

      /* compute geodesic acceleration */
      status = gsl_multifit_nlinear_eval_fvv(params->h_fvv,
                                             trust_state->x,
                                             state->vel,
                                             trust_state->f,
                                             trust_state->J,
                                             trust_state->sqrt_wts,
                                             trust_state->fdf,
                                             state->fvv,
                                             state->workp);
      if (status)
        return status;

      /*
       * solve: [     J      ] a = - [ fvv ]
       *        [ sqrt(mu)*D ]       [  0  ]
       */
      status = (params->solver->solve)(state->fvv,
                                       state->acc,
                                       trust_state,
                                       trust_state->solver_state);
      if (status)
        return status;

      anorm = gsl_blas_dnrm2(state->acc);
      vnorm = gsl_blas_dnrm2(state->vel);

      /* store |a| / |v| */
      *(trust_state->avratio) = anorm / vnorm;
    }

  /* compute step dx = v + 1/2 a */
  scaled_addition(1.0, state->vel, 0.5, state->acc, dx);

  return GSL_SUCCESS;
}

/*
lm_preduction()
  Compute predicted reduction using Eq 4.4 of More 1978
*/

static int
lm_preduction(const void * vtrust_state, const gsl_vector * dx,
              double * pred, void * vstate)
{
  const gsl_multifit_nlinear_trust_state *trust_state =
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
  lm_state_t *state = (lm_state_t *) vstate;
  const gsl_vector *diag = trust_state->diag;
  const gsl_vector *p = state->vel;
  const double norm_Dp = scaled_enorm(diag, p);
  const double normf = gsl_blas_dnrm2(trust_state->f);
  const double mu = *(trust_state->mu);
  double norm_Jp;
  double u, v;

  (void)dx;

  /* compute work = J*p */
  gsl_blas_dgemv(CblasNoTrans, 1.0, trust_state->J, p, 0.0, state->workn);

  /* compute ||J*p|| */
  norm_Jp = gsl_blas_dnrm2(state->workn);

  u = norm_Jp / normf;
  v = norm_Dp / normf;

  *pred = u * u + 2.0 * mu * v * v;

  return GSL_SUCCESS;
}

static const gsl_multifit_nlinear_trs lm_type =
{
  "levenberg-marquardt",
  lm_alloc_noaccel,
  lm_init,
  lm_preloop,
  lm_step,
  lm_preduction,
  lm_free
};

const gsl_multifit_nlinear_trs *gsl_multifit_nlinear_trs_lm = &lm_type;

static const gsl_multifit_nlinear_trs lmaccel_type =
{
  "levenberg-marquardt+accel",
  lm_alloc_accel,
  lm_init,
  lm_preloop,
  lm_step,
  lm_preduction,
  lm_free
};

const gsl_multifit_nlinear_trs *gsl_multifit_nlinear_trs_lmaccel = &lmaccel_type;


/* movstat/apply.c
 *
 * Copyright (C) 2018 Patrick Alken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_movstat.h>

/*
gsl_movstat_apply_accum()
  Apply moving window statistic to input vector. This is a generalized
routine to handle window endpoints and apply a given accumulator to
the input vector.

Inputs: endtype      - end point handling criteria
        x            - input vector, size n
        accum        - accumulator to apply moving window statistic
        accum_params - parameters to pass to accumulator
        y            - output vector, size n
        z            - second output vector (i.e. minmax), size n; can be NULL
        w            - workspace

Notes:
1) It is allowed to have x = y for in-place moving statistics
*/

int
gsl_movstat_apply_accum(const gsl_movstat_end_t endtype,
                        const gsl_vector * x,
                        const gsl_movstat_accum * accum,
                        void * accum_params,
                        gsl_vector * y,
                        gsl_vector * z,
                        gsl_movstat_workspace * w)
{
  if (x->size != y->size)
    {
      GSL_ERROR("input and output vectors must have same length", GSL_EBADLEN);
    }
  else if (z != NULL && x->size != z->size)
    {
      GSL_ERROR("input and output vectors must have same length", GSL_EBADLEN);
    }
  else
    {
      const int n = (int) x->size;
      const int H = w->H; /* number of samples to left of current sample */
      const int J = w->J; /* number of samples to right of current sample */
      int i;
      double x1 = 0.0;    /* pad values for data edges */
      double xN = 0.0;
      double result[2];

      /* initialize accumulator */
      (accum->init)(w->K, w->state);

      /* pad initial window if necessary */
      if (endtype != GSL_MOVSTAT_END_TRUNCATE)
        {
          if (endtype == GSL_MOVSTAT_END_PADZERO)
            {
              x1 = 0.0;
              xN = 0.0;
            }
          else if (endtype == GSL_MOVSTAT_END_PADVALUE)
            {
              x1 = gsl_vector_get(x, 0);
              xN = gsl_vector_get(x, n - 1);
            }

          /* pad initial windows with H values */
          for (i = 0; i < H; ++i)
            (accum->insert)(x1, w->state);
        }
      else if (accum->delete_oldest == NULL) /* FIXME XXX */
        {
          /* save last K - 1 samples of x for later (needed for in-place input/output) */
          int idx1 = GSL_MAX(n - J - H, 0);
          int idx2 = n - 1;

          for (i = idx1; i <= idx2; ++i)
            w->work[i - idx1] = gsl_vector_get(x, i);
        }

      /* process input vector and fill y(0:n - J - 1) */
      for (i = 0; i < n; ++i)
        {
          double xi = gsl_vector_get(x, i);
          int idx = i - J;

          (accum->insert)(xi, w->state);

          if (idx >= 0)
            {
              (accum->get)(accum_params, result, w->state);
              gsl_vector_set(y, idx, result[0]);

              if (z != NULL)
                gsl_vector_set(z, idx, result[1]);
            }
        }

      if (endtype == GSL_MOVSTAT_END_TRUNCATE)
        {
          /* need to fill y(n-J:n-1) using shrinking windows */
          int idx1 = GSL_MAX(n - J, 0);
          int idx2 = n - 1;

          if (accum->delete_oldest == NULL)
            {
              int wsize = n - GSL_MAX(n - J - H, 0); /* size of work array */

              for (i = idx1; i <= idx2; ++i)
                {
                  int nsamp = n - GSL_MAX(i - H, 0); /* number of samples in this window */
                  int j;

                  (accum->init)(w->K, w->state);

                  for (j = wsize - nsamp; j < wsize; ++j)
                    (accum->insert)(w->work[j], w->state);

                  /* yi = acc_get [ work(i:K-2) ] */
                  (accum->get)(accum_params, result, w->state);
                  gsl_vector_set(y, i, result[0]);

                  if (z != NULL)
                    gsl_vector_set(z, i, result[1]);
                }
            }
          else
            {
              for (i = idx1; i <= idx2; ++i)
                {
                  if (i - H > 0)
                    {
                      /* delete oldest window sample as we move closer to edge */
                      (accum->delete_oldest)(w->state);
                    }

                  /* yi = acc_get [ work(i:K-2) ] */
                  (accum->get)(accum_params, result, w->state);
                  gsl_vector_set(y, i, result[0]);

                  if (z != NULL)
                    gsl_vector_set(z, i, result[1]);
                }
            }
        }
      else
        {
          /* pad final windows and fill y(n-J:n-1) */
          for (i = 0; i < J; ++i)
            {
              int idx = n - J + i;

              (accum->insert)(xN, w->state);

              if (idx >= 0)
                {
                  (accum->get)(accum_params, result, w->state);
                  gsl_vector_set(y, idx, result[0]);

                  if (z != NULL)
                    gsl_vector_set(z, idx, result[1]);
                }
            }
        }

      return GSL_SUCCESS;
    }
}

/*
gsl_movstat_apply()
  Apply user-defined moving window function to input vector

Inputs: endtype - end point handling criteria
        F       - user-defined function
        x       - input vector, size n
        y       - output vector, size n
        w       - workspace
*/

int
gsl_movstat_apply(const gsl_movstat_end_t endtype, const gsl_movstat_function * F,
                  const gsl_vector * x, gsl_vector * y, gsl_movstat_workspace * w)
{
  int status = gsl_movstat_apply_accum(endtype, x, gsl_movstat_accum_userfunc, (void *) F, y, NULL, w);
  return status;
}


/*
 * Copyright (c) 2011-2018 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 *
 * @precisions normal z -> s d c
 *
 */
#include <math.h>
#include <stdlib.h>
#include <core_blas.h>
#include <cblas.h>
#include "dplasma.h"

/*
#define DEBUG_BUTTERFLY
*/

/* Forward declaration of kernels for the butterfly transformation */
void BFT_zQTL( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal );

void BFT_zQBL( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal );
void BFT_zQTR_trans( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans );
void BFT_zQTR( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans);
void BFT_zQBR( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal );

void RBMM_zTOP( int mb, int nb, int lda, int i_seg, int lvl, int N, int trans,
          PLASMA_Complex64_t *top, PLASMA_Complex64_t *btm,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_but_vec);
void RBMM_zBTM( int mb, int nb, int lda, int i_seg, int lvl, int N, int trans,
          PLASMA_Complex64_t *top, PLASMA_Complex64_t *btm,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_but_vec);

/* Bodies of kernels for the butterfly transformation */

void BFT_zQTL( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal )
{
    int i, j;

    (void)lvl;
    (void)N;

#if defined(DEBUG_BUTTERFLY)
    printf ("BFT_zQTL(mb:%d, nb:%d, lda:%d, i_seg:%d, j_seg:%d, lvl:%d, N:%d, bl_is_tr_trans:%d, is_diagonal:%d)\n",
            mb, nb, lda, i_seg, j_seg, lvl, N, bl_is_tr_trans, is_diagonal);
#endif

    if( bl_is_tr_trans ){
        for (j=0; j<nb; j++) {
            int start = is_diagonal ? j : 0;
            for (i=start; i<mb; i++) {
                PLASMA_Complex64_t ri = U_before[i_seg+i];
                PLASMA_Complex64_t rj = U_after[j_seg+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("HE A[%d][%d] = U_before[%d]*((tl[%d]+bl[%d]) + (tr[%d]+br[%d])) * U_after[%d]\n", i_seg+i, j_seg+j, i_seg+i, j*lda+i, j*lda+i, i*lda+j, j*lda+i, j_seg+j);
#endif
                C[j*lda+i] = ri * ((tl[j*lda+i]+bl[j*lda+i]) + (tr[i*lda+j]+br[j*lda+i])) * rj;
#if defined(DEBUG_BUTTERFLY)
                printf ("HE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(ri), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[i*lda+j]), creal(br[j*lda+i]), creal(rj));
#endif
            }
        }
    }else{
        for (j=0; j<nb; j++) {
            int start = is_diagonal ? j : 0;
            for (i=start; i<mb; i++) {
                PLASMA_Complex64_t ri = U_before[i_seg+i];
                PLASMA_Complex64_t rj = U_after[j_seg+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("GE A[%d][%d] = U_before[%d]*((tl[%d]+bl[%d]) + (tr[%d]+br[%d])) * U_after[%d]\n", i_seg+i, j_seg+j, i_seg+i, j*lda+i, j*lda+i, j*lda+i, j*lda+i, j_seg+j);
#endif
                C[j*lda+i] = ri * ((tl[j*lda+i]+bl[j*lda+i]) + (tr[j*lda+i]+br[j*lda+i])) * rj;
#if defined(DEBUG_BUTTERFLY)
                printf ("GE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(ri), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[j*lda+i]), creal(br[j*lda+i]), creal(rj));
#endif
            }
        }
    }
    return;
}

void BFT_zQBL( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal )
{
    int i, j;
    int r_to_s = N/(1<<(lvl+1));

#if defined(DEBUG_BUTTERFLY)
    if( is_diagonal )
        printf ("BFT_zQBL(diag)\n");
    else
        printf ("BFT_zQBL(lower)\n");
#endif

    if( bl_is_tr_trans ){
        for (j=0; j<nb; j++) {
            for (i=0; i<mb; i++) {
                PLASMA_Complex64_t si = U_before[i_seg+r_to_s+i];
                PLASMA_Complex64_t rj = U_after[j_seg+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("HE A[%d][%d] = U_before[%d]*((tl[%d]-bl[%d]) + (tr[%d]-br[%d])) * U_after[%d]\n", i_seg+i+r_to_s, j_seg+j, i_seg+r_to_s+i, j*lda+i, j*lda+i, i*lda+j, j*lda+i, j_seg+j);
#endif
                if( is_diagonal && j>i ){ /* If the tile of the TL quarter is on the diagonal, then we need to take the transpose element for the tiles that came from TL and BR */
                    C[j*lda+i] = si * ((tl[i*lda+j]-bl[j*lda+i]) + (tr[i*lda+j]-br[i*lda+j])) * rj;
                } else{
                    C[j*lda+i] = si * ((tl[j*lda+i]-bl[j*lda+i]) + (tr[i*lda+j]-br[j*lda+i])) * rj;
                }
#if defined(DEBUG_BUTTERFLY)
                printf ("HE %lf %lf %lf %lf %lf %lf %lf\n",creal(C[j*lda+i]), creal(si), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[i*lda+j]), creal(br[j*lda+i]), creal(rj));
#endif
            }
        }
    }else{
        for (j=0; j<nb; j++) {
            for (i=0; i<mb; i++) {
                PLASMA_Complex64_t si = U_before[i_seg+r_to_s+i];
                PLASMA_Complex64_t rj = U_after[j_seg+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("GE A[%d][%d] = U_before[%d]*((tl[%d]-bl[%d]) + (tr[%d]-br[%d])) * U_after[%d]\n", i_seg+i+r_to_s, j_seg+j, i_seg+r_to_s+i, j*lda+i, j*lda+i, j*lda+i, j*lda+i, j_seg+j);
#endif
                C[j*lda+i] = si * ((tl[j*lda+i]-bl[j*lda+i]) + (tr[j*lda+i]-br[j*lda+i])) * rj;
#if defined(DEBUG_BUTTERFLY)
                printf ("GE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(si), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[j*lda+i]), creal(br[j*lda+i]), creal(rj));
#endif
            }
        }
    }
    return;
}

/* This function writes into a transposed tile, so C is always transposed. */
void BFT_zQTR_trans( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans )
{
    int i,j;
    int r_to_s = N/(1<<(lvl+1));
#if defined(DEBUG_BUTTERFLY)
    printf ("BFT_zQTR_trans()\n");
#endif
    if( bl_is_tr_trans ){
        for (j=0; j<nb; j++) {
            for (i=0; i<mb; i++) {
                PLASMA_Complex64_t ri = U_before[i_seg+i];
                PLASMA_Complex64_t sj = U_after[j_seg+r_to_s+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("HE A[%d][%d] = U_before[%d]*((tl[%d]+bl[%d]) - (tr[%d]+br[%d])) * U_after[%d]\n", i_seg+j, j_seg+i+r_to_s, i_seg+i, j*lda+i, j*lda+i, i*lda+j, j*lda+i, j_seg+r_to_s+j);
#endif
                C[i*lda+j] = ri * ((tl[j*lda+i]+bl[j*lda+i]) - (tr[i*lda+j]+br[j*lda+i])) * sj;
#if defined(DEBUG_BUTTERFLY)
                printf ("HE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[i*lda+j]), creal(ri), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[i*lda+j]), creal(br[j*lda+i]), creal(sj));
#endif
            }
        }
    }else{
        assert(0); // This should never happen.
    }
    return;
}

void BFT_zQTR( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans )
{
    int i, j;
    int r_to_s = N/(1<<(lvl+1));
#if defined(DEBUG_BUTTERFLY)
    printf ("BFT_zQTR()\n");
#endif
    if( bl_is_tr_trans ){
        for (j=0; j<nb; j++) {
            for (i=0; i<mb; i++) {
                PLASMA_Complex64_t ri = U_before[i_seg+i];
                PLASMA_Complex64_t sj = U_after[j_seg+r_to_s+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("HE A[%d][%d] = U_before[%d]*((tl[%d]+bl[%d]) - (tr[%d]+br[%d])) * U_after[%d]\n", i_seg+i, j_seg+j+r_to_s, i_seg+i, j*lda+i, j*lda+i, i*lda+j, j*lda+i, j_seg+r_to_s+j);
#endif
                C[j*lda+i] = ri * ((tl[j*lda+i]+bl[j*lda+i]) - (tr[i*lda+j]+br[j*lda+i])) * sj;
#if defined(DEBUG_BUTTERFLY)
                printf ("HE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(ri), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[i*lda+j]), creal(br[j*lda+i]), creal(sj));
#endif
            }
        }
    }else{
        for (j=0; j<nb; j++) {
            for (i=0; i<mb; i++) {
                PLASMA_Complex64_t ri = U_before[i_seg+i];
                PLASMA_Complex64_t sj = U_after[j_seg+r_to_s+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("GE A[%d][%d] = U_before[%d]*((tl[%d]+bl[%d]) - (tr[%d]+br[%d])) * U_after[%d]\n", i_seg+i, j_seg+j+r_to_s, i_seg+i, j*lda+i, j*lda+i, j*lda+i, j*lda+i, j_seg+r_to_s+j);
#endif
                C[j*lda+i] = ri * ((tl[j*lda+i]+bl[j*lda+i]) - (tr[j*lda+i]+br[j*lda+i])) * sj;
#if defined(DEBUG_BUTTERFLY)
                printf ("GE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(ri), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[j*lda+i]), creal(br[j*lda+i]), creal(sj));
#endif
            }
        }
    }
    return;
}

void BFT_zQBR( int mb, int nb, int lda, int i_seg, int j_seg, int lvl, int N,
          PLASMA_Complex64_t *tl, PLASMA_Complex64_t *bl,
          PLASMA_Complex64_t *tr, PLASMA_Complex64_t *br,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_before, PLASMA_Complex64_t *U_after, int bl_is_tr_trans, int is_diagonal )
{
    int i, j;
    int r_to_s = N/(1<<(lvl+1));

#if defined(DEBUG_BUTTERFLY)
    if( is_diagonal )
        printf ("BFT_zQBR(diag)\n");
    else
        printf ("BFT_zQBR(lower)\n");
#endif

    if( bl_is_tr_trans ){
        for (j=0; j<nb; j++) {
            int start = is_diagonal ? j : 0;
            for (i=start; i<mb; i++) {
                PLASMA_Complex64_t si = U_before[i_seg+r_to_s+i];
                PLASMA_Complex64_t sj = U_after[j_seg+r_to_s+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("HE A[%d][%d] = U_before[%d]*((tl[%d]-bl[%d]) - (tr[%d]-br[%d])) * U_after[%d]\n", i_seg+i+r_to_s, j_seg+j+r_to_s, i_seg+r_to_s+i, j*lda+i, j*lda+i, i*lda+j, j*lda+i, j_seg+r_to_s+j);
#endif
                C[j*lda+i] = si * ((tl[j*lda+i]-bl[j*lda+i]) - (tr[i*lda+j]-br[j*lda+i])) * sj;
#if defined(DEBUG_BUTTERFLY)
                printf ("HE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(si), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[i*lda+j]), creal(br[j*lda+i]), creal(sj));
#endif
            }
        }
    }else{
        for (j=0; j<nb; j++) {
            int start = is_diagonal ? j : 0;
            for (i=start; i<mb; i++) {
                PLASMA_Complex64_t si = U_before[i_seg+r_to_s+i];
                PLASMA_Complex64_t sj = U_after[j_seg+r_to_s+j];
#if defined(DEBUG_BUTTERFLY)
                printf ("GE A[%d][%d] = U_before[%d]*((tl[%d]-bl[%d]) - (tr[%d]-br[%d])) * U_after[%d]\n", i_seg+i+r_to_s, j_seg+j+r_to_s, i_seg+r_to_s+i, j*lda+i, j*lda+i, j*lda+i, j*lda+i, j_seg+r_to_s+j);
#endif
                C[j*lda+i] = si * ((tl[j*lda+i]-bl[j*lda+i]) - (tr[j*lda+i]-br[j*lda+i])) * sj;
#if defined(DEBUG_BUTTERFLY)
                printf ("GE %lf %lf %lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(si), creal(tl[j*lda+i]), creal(bl[j*lda+i]), creal(tr[j*lda+i]), creal(br[j*lda+i]), creal(sj));
#endif
            }
        }
    }
    return;
}

void RBMM_zTOP( int mb, int nb, int lda, int i_seg, int lvl, int N, int trans,
          PLASMA_Complex64_t *top, PLASMA_Complex64_t *btm,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_but_vec)
{
    int i, j;
    int r_to_s = N/(1<<(lvl+1));
#if defined(DEBUG_BUTTERFLY)
    printf ("RBMM_zTOP()\n");
#endif
    for (j=0; j<nb; j++) {
        for (i=0; i<mb; i++) {
            PLASMA_Complex64_t r = U_but_vec[i_seg+i];
            if( PlasmaConjTrans == trans ){
#if defined(DEBUG_BUTTERFLY)
                printf ("C[%d] = U_but_vec[%d]*(top[%d]+btm[%d])\n", j*lda+i, i_seg+i, j*lda+i, j*lda+i);
#endif
                C[j*lda+i] = r * (top[j*lda+i] + btm[j*lda+i]);
#if defined(DEBUG_BUTTERFLY)
                printf ("%lf %lf %lf %lf\n",creal(C[j*lda+i]), creal(r), creal(top[j*lda+i]), creal(btm[j*lda+i]));
#endif
            }else{
                PLASMA_Complex64_t s = U_but_vec[i_seg+r_to_s+i];
#if defined(DEBUG_BUTTERFLY)
                printf ("C[%d] = U_but_vec[%d]*top[%d] + U_but_vec[%d]*btm[%d]\n", j*lda+i, i_seg+i, j*lda+i, i_seg+r_to_s+i, j*lda+i);
#endif
                C[j*lda+i] =  r*top[j*lda+i] + s*btm[j*lda+i];
#if defined(DEBUG_BUTTERFLY)
                printf ("%lf %lf %lf %lf %lf\n",creal(C[j*lda+i]), creal(r), creal(top[j*lda+i]), creal(s), creal(btm[j*lda+i]));
#endif
            }
        }
    }
    return;
}

void RBMM_zBTM( int mb, int nb, int lda, int i_seg, int lvl, int N, int trans,
          PLASMA_Complex64_t *top, PLASMA_Complex64_t *btm,
          PLASMA_Complex64_t *C, PLASMA_Complex64_t *U_but_vec)
{
    int i, j;
    int r_to_s = N/(1<<(lvl+1));
#if defined(DEBUG_BUTTERFLY)
    printf ("RBMM_zBTM()\n");
#endif
    for (j=0; j<nb; j++) {
        for (i=0; i<mb; i++) {
            PLASMA_Complex64_t s = U_but_vec[i_seg+r_to_s+i];
            if( PlasmaConjTrans == trans ){
#if defined(DEBUG_BUTTERFLY)
                printf ("C[%d] = U_but_vec[%d]*(top[%d]-btm[%d])\n", j*lda+i, i_seg+r_to_s+i, j*lda+i, j*lda+i);
#endif
                C[j*lda+i] = s * (top[j*lda+i] - btm[j*lda+i]);
#if defined(DEBUG_BUTTERFLY)
                printf ("%lf %lf %lf %lf\n",creal(C[j*lda+i]), creal(s), creal(top[j*lda+i]), creal(btm[j*lda+i]));
#endif
            }else{
                PLASMA_Complex64_t r = U_but_vec[i_seg+i];
#if defined(DEBUG_BUTTERFLY)
                printf ("C[%d] = U_but_vec[%d]*top[%d] - U_but_vec[%d]*btm[%d]\n", j*lda+i, i_seg+i, j*lda+i, i_seg+r_to_s+i, j*lda+i);
#endif
                C[j*lda+i] = r*top[j*lda+i] - s*btm[j*lda+i];
#if defined(DEBUG_BUTTERFLY)
                printf ("%lf %lf %lf %lf %lf\n", creal(C[j*lda+i]), creal(r), creal(top[j*lda+i]), creal(s), creal(btm[j*lda+i]));
#endif
            }
        }
    }
    return;
}


/////////////////////////////////////////////////////////////////////
// = NMatrix
//
// A linear algebra library for scientific computation in Ruby.
// NMatrix is part of SciRuby.
//
// NMatrix was originally inspired by and derived from NArray, by
// Masahiro Tanaka: http://narray.rubyforge.org
//
// == Copyright Information
//
// SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
// NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
//
// Please see LICENSE.txt for additional copyright notices.
//
// == Contributing
//
// By contributing source code to SciRuby, you agree to be bound by
// our Contributor Agreement:
//
// * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
//
// == gemv.h
//
// Header file for interface with ATLAS's CBLAS gemv functions and
// native templated version of LAPACK's gemv function.
//

#ifndef GEMV_H
# define GEMV_H

extern "C" { // These need to be in an extern "C" block or you'll get all kinds of undefined symbol errors.
#if defined HAVE_CBLAS_H
  #include <cblas.h>
#elif defined HAVE_ATLAS_CBLAS_H
  #include <atlas/cblas.h>
#endif
}

namespace nm { namespace math {

/*
 * GEneral Matrix-Vector multiplication: based on dgemv.f from Netlib.
 *
 * This is an extremely inefficient algorithm. Recommend using ATLAS' version instead.
 *
 * Template parameters: LT -- long version of type T. Type T is the matrix dtype.
 */
template <typename DType>
inline bool gemv(const enum CBLAS_TRANSPOSE Trans, const int M, const int N, const DType* alpha, const DType* A, const int lda,
          const DType* X, const int incX, const DType* beta, DType* Y, const int incY) {
  int lenX, lenY, i, j;
  int kx, ky, iy, jx, jy, ix;

  typename LongDType<DType>::type temp;

  // Test the input parameters
  if (Trans < 111 || Trans > 113) {
    rb_raise(rb_eArgError, "GEMV: TransA must be CblasNoTrans, CblasTrans, or CblasConjTrans");
    return false;
  } else if (lda < std::max(1, N)) {
    fprintf(stderr, "GEMV: N = %d; got lda=%d", N, lda);
    rb_raise(rb_eArgError, "GEMV: Expected lda >= max(1, N)");
    return false;
  } else if (incX == 0) {
    rb_raise(rb_eArgError, "GEMV: Expected incX != 0\n");
    return false;
  } else if (incY == 0) {
    rb_raise(rb_eArgError, "GEMV: Expected incY != 0\n");
    return false;
  }

  // Quick return if possible
  if (!M or !N or (*alpha == 0 and *beta == 1)) return true;

  if (Trans == CblasNoTrans) {
    lenX = N;
    lenY = M;
  } else {
    lenX = M;
    lenY = N;
  }

  if (incX > 0) kx = 0;
  else          kx = (lenX - 1) * -incX;

  if (incY > 0) ky = 0;
  else          ky =  (lenY - 1) * -incY;

  // Start the operations. In this version, the elements of A are accessed sequentially with one pass through A.
  if (*beta != 1) {
    if (incY == 1) {
      if (*beta == 0) {
        for (i = 0; i < lenY; ++i) {
          Y[i] = 0;
        }
      } else {
        for (i = 0; i < lenY; ++i) {
          Y[i] *= *beta;
        }
      }
    } else {
      iy = ky;
      if (*beta == 0) {
        for (i = 0; i < lenY; ++i) {
          Y[iy] = 0;
          iy += incY;
        }
      } else {
        for (i = 0; i < lenY; ++i) {
          Y[iy] *= *beta;
          iy += incY;
        }
      }
    }
  }

  if (*alpha == 0) return false;

  if (Trans == CblasNoTrans) {

    // Form  y := alpha*A*x + y.
    jx = kx;
    if (incY == 1) {
      for (j = 0; j < N; ++j) {
        if (X[jx] != 0) {
          temp = *alpha * X[jx];
          for (i = 0; i < M; ++i) {
            Y[i] += A[j+i*lda] * temp;
          }
        }
        jx += incX;
      }
    } else {
      for (j = 0; j < N; ++j) {
        if (X[jx] != 0) {
          temp = *alpha * X[jx];
          iy = ky;
          for (i = 0; i < M; ++i) {
            Y[iy] += A[j+i*lda] * temp;
            iy += incY;
          }
        }
        jx += incX;
      }
    }

  } else { // TODO: Check that indices are correct! They're switched for C.

    // Form  y := alpha*A**DType*x + y.
    jy = ky;

    if (incX == 1) {
      for (j = 0; j < N; ++j) {
        temp = 0;
        for (i = 0; i < M; ++i) {
          temp += A[j+i*lda]*X[j];
        }
        Y[jy] += *alpha * temp;
        jy += incY;
      }
    } else {
      for (j = 0; j < N; ++j) {
        temp = 0;
        ix = kx;
        for (i = 0; i < M; ++i) {
          temp += A[j+i*lda] * X[ix];
          ix += incX;
        }

        Y[jy] += *alpha * temp;
        jy += incY;
      }
    }
  }

  return true;
}  // end of GEMV

#if (defined(HAVE_CBLAS_H) || defined(HAVE_ATLAS_CBLAS_H))
template <>
inline bool gemv(const enum CBLAS_TRANSPOSE Trans, const int M, const int N, const float* alpha, const float* A, const int lda,
          const float* X, const int incX, const float* beta, float* Y, const int incY) {
  cblas_sgemv(CblasRowMajor, Trans, M, N, *alpha, A, lda, X, incX, *beta, Y, incY);
  return true;
}

template <>
inline bool gemv(const enum CBLAS_TRANSPOSE Trans, const int M, const int N, const double* alpha, const double* A, const int lda,
          const double* X, const int incX, const double* beta, double* Y, const int incY) {
  cblas_dgemv(CblasRowMajor, Trans, M, N, *alpha, A, lda, X, incX, *beta, Y, incY);
  return true;
}

template <>
inline bool gemv(const enum CBLAS_TRANSPOSE Trans, const int M, const int N, const Complex64* alpha, const Complex64* A, const int lda,
          const Complex64* X, const int incX, const Complex64* beta, Complex64* Y, const int incY) {
  cblas_cgemv(CblasRowMajor, Trans, M, N, alpha, A, lda, X, incX, beta, Y, incY);
  return true;
}

template <>
inline bool gemv(const enum CBLAS_TRANSPOSE Trans, const int M, const int N, const Complex128* alpha, const Complex128* A, const int lda,
          const Complex128* X, const int incX, const Complex128* beta, Complex128* Y, const int incY) {
  cblas_zgemv(CblasRowMajor, Trans, M, N, alpha, A, lda, X, incX, beta, Y, incY);
  return true;
}
#endif

}} // end of namespace nm::math

#endif // GEMM_H


/*
 This file originates from LibSVM <http://www.csie.ntu.edu.tw/~cjlin/libsvm/>

 Copyright (c) 2000-2013 Chih-Chung Chang and Chih-Jen Lin
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 1. Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.

 3. Neither name of copyright holders nor the names of its contributors
 may be used to endorse or promote products derived from this software
 without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 */

/*
 This file is part of the Princeton FCMA Toolbox-- namely
 the "svm_cross_validation_no_shuffle" function, an edited
 version of libsvm's "svm_cross_validation"
*/

#ifndef _SVM_H
#define _SVM_H

#define LIBSVM_VERSION 310
#define MAXSVMITERATION 20000000

#if defined(__GNUC__)
// gcc supports this syntax
#define ALIGNED(x) __attribute__((aligned(x)))
#else
// visual c++, clang, icc
#define ALIGNED(x) __declspec(align(x))
#endif

//#include "LibSVM.h"
#include <cassert>

// BLAS dependency
#ifdef USE_MKL
#include <mkl.h>
#else
typedef int MKL_INT;
#if defined __APPLE__
#include <Accelerate/Accelerate.h>
#else
extern "C" {
#include <cblas.h>
}
#endif
#endif

// SSE/AVX instrinsics
#if defined(_MSC_VER)
// Microsoft C/C++-compatible compiler */
#include <intrin.h>
#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
// GCC-compatible compiler (gcc,clang,icc) targeting x86/x86-64
#include <x86intrin.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

// unsigned long long counter;
typedef float Qfloat;
typedef signed char schar;

extern int libsvm_version;

// use SOA instead of AOS --later!!
struct svm_node {
  int index;
  float value;
};

// change y from double to schar, x is one-step pointer now
struct svm_problem {
  int l;
  schar *y;
  struct svm_node **x;
};

struct svm_parameter {
  int svm_type;
  int kernel_type;
  int degree;   /* for poly */
  double gamma; /* for poly/rbf/sigmoid */
  double coef0; /* for poly/sigmoid */

  /* these are for training only */
  double cache_size; /* in MB */
  double eps;        /* stopping criteria */
  double C;          /* for C_SVC, EPSILON_SVR and NU_SVR */
  int nr_weight;     /* for C_SVC */
  int *weight_label; /* for C_SVC */
  double *weight;    /* for C_SVC */
  double nu;         /* for NU_SVC, ONE_CLASS, and NU_SVR */
  double p;          /* for EPSILON_SVR */
  int shrinking;     /* use the shrinking heuristics */
  int probability;   /* do probability estimates */
};

//
// svm_model
//
struct svm_model {
  struct svm_parameter param; /* parameter */
  int nr_class;         /* number of classes, = 2 in regression/one class svm */
  int l;                /* total #SV */
  struct svm_node **SV; /* SVs (SV[l]) */
  double **sv_coef;     /* coefficients for SVs in decision functions
                       (sv_coef[k-1][l]) */
  double *rho; /* constants in decision functions (rho[k*(k-1)/2]) */

  /* for classification only */

  int *label; /* label of each class (label[k]) */
  int *nSV;   /* number of SVs for each class (nSV[k]) */
              /* nSV[0] + nSV[1] + ... + nSV[k-1] = l */
  /* XXX */
  int free_sv; /* 1 if svm_model is created by svm_load_model*/
               /* 0 if svm_model is created by svm_train */
};

enum {
  C_SVC
};
    /* svm_type */  // only C_SVC is needed for now
enum {
  LINEAR,
  PRECOMPUTED
}; /* kernel_type */

typedef struct svm_problem SVMProblem;
typedef struct svm_parameter SVMParameter;
typedef struct svm_node SVMNode;

struct svm_model *svm_train(const struct svm_problem *prob,
                            const struct svm_parameter *param);
void svm_cross_validation(const struct svm_problem *prob,
                          const struct svm_parameter *param,
                          int nr_fold, double *target);

void svm_cross_validation_no_shuffle(const struct svm_problem *prob,
                                     const struct svm_parameter *param,
                                     int nr_fold, double *target);

int svm_get_svm_type(const struct svm_model *model);
int svm_get_nr_class(const struct svm_model *model);
void svm_get_labels(const struct svm_model *model, int *label);

double svm_predict_values(const struct svm_model *model,
                          const struct svm_node *x, double *dec_values);
double svm_predict(const struct svm_model *model, const struct svm_node *x);
double svm_predict_distance(const struct svm_model *model,
                            const struct svm_node *x);

void svm_free_model_content(struct svm_model *model_ptr);
void svm_free_and_destroy_model(struct svm_model **model_ptr_ptr);
void svm_destroy_param(struct svm_parameter *param);

const char *svm_check_parameter(const struct svm_problem *prob,
                                const struct svm_parameter *param);
int svm_check_probability_model(const struct svm_model *model);

void svm_set_print_string_function(void (*print_func)(const char *));

int svm_save_model(const char *model_file_name, const struct svm_model *model);
struct svm_model *svm_load_model(const char *model_file_name);

#ifdef __cplusplus
}
#endif

#endif /* _SVM_H */


#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_odeiv2.h>

/* don't check ranges in Matrix operations and make Matrix call inline */
#define GSL_RANGE_CHECK_OFF
//#define HAVE_INLINE

#define FALSE 0
#define TRUE 1

#ifndef M_PI
#define M_PI            3.141592653589793238462643383280        /* pi */
#endif

#define LN2PI 1.837877066409345339

#define ODEdriver gsl_odeiv2_driver_alloc_y_new
#define rkf45 gsl_odeiv2_step_rkf45
#define rk8pd gsl_odeiv2_step_rk8pd
#define rkck  gsl_odeiv2_step_rkck
#define rk2   gsl_odeiv2_step_rk2

#define EOL -1

#define max(A, B) ((A) > (B) ? (A) : (B))
#define min(A, B) ((A) < (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define V(x) (x)*(x)

#define FIT_TIME_POINT(x) (x->mode == FIT)
#define RECORD_TIME_POINT(x, u) (x->mode == OUTPUT || (x->mode == FIT && u == x->t[x->i]))
#define NEXT_TIME_POINT(x) if (x->mode == FIT) x->i++
#define RESET_TIME_POINT(x) x->i = 0

enum {FIT, OUTPUT};

enum {SUCCESS=0, FAIL=1, ERROR1=1, ERROR2, ERROR3, ERROR4, ERROR5, ERROR6,
      ERROR7, ERROR8, ERROR9, ERROR10};

typedef uint boolean;

// for internal use
// type of parameter
enum {NOTUSED=0, DERIVED=21, FITTED=22, CONSTANT=23, AUXILLARY=24, INDIVIDUAL_CONSTANT=28};
// whether a fitted or auxillary parameter has a hyperparameter associated with it
enum {HYPER=26};
// prior of a fitted parameter that does not have a hyperparameters
enum {NOTAPPLICABLE=0, NORMAL=1, GAMMA=2, SICHI2=3, UNIFORM=4, PARETO=5,
      TRUNCNORMAL=6, EXPONENTIAL=7, BINOMIAL=8, POISSON=9, INVGAMMA=10,
      UNIFORM0=11, BETA=12, NORMALMEAN=13, CATEGORICAL=14, GEOMETRIC=15,
      USERDEFINED=16, AUXILLARY_HYPER=27, CIRCULAR=29};
// number type of the parameter
enum {REAL=17, INTEGER=18, CATEGORY=19, INT = INTEGER};
// type of data
enum {NOT_AVAILABLE=0, ABOVE_DETECTION=1, BELOW_DETECTION=2, MEASURED=3};

// for use in model files
// prior or type of the declared parameter
enum {Normal        =NORMAL,
      Gamma         =GAMMA,
      Sichi2        =SICHI2,
      Uniform       =UNIFORM,
      Circular      =CIRCULAR,
      Pareto        =PARETO,
      TruncNormal   =TRUNCNORMAL,
      Exponential   =EXPONENTIAL,
      Binomial      =BINOMIAL,
      Poisson       =POISSON,
      InvGamma      =INVGAMMA,
      Uniform0      =UNIFORM0,
      Beta          =BETA,
      NormalMean    =NORMALMEAN,
      Categorical   =CATEGORICAL,
      Geometric     =GEOMETRIC,
      UserDefined   =USERDEFINED,
      HyperP         =HYPER,
      Constant      =CONSTANT,
      Auxillary     =AUXILLARY,
      Auxillary_Hyper =AUXILLARY_HYPER,
      Derived       =DERIVED,
      Individual_Constant=INDIVIDUAL_CONSTANT};
enum {Nor           =NORMAL,
      Gam 	    =GAMMA,
      Sic 	    =SICHI2,
      Unif 	    =UNIFORM,
      Circ          =CIRCULAR,
      Pare 	    =PARETO,
      TNorm 	    =TRUNCNORMAL,
      Exp 	    =EXPONENTIAL,
      Bin 	    =BINOMIAL,
      Poi 	    =POISSON,
      IGam 	    =INVGAMMA,
      Unif0 	    =UNIFORM0,
      Be 	    =BETA,
      NorM 	    =NORMALMEAN,
      Cat 	    =CATEGORICAL,
      Geom 	    =GEOMETRIC,
      UD 	    =USERDEFINED,
      Hyp 	    =HYPER,
      Cst 	    =CONSTANT,
      Aux 	    =AUXILLARY,
      Aux_Hyp       =AUXILLARY_HYPER,
      Der	    =DERIVED,
      IndCst        =INDIVIDUAL_CONSTANT};

// number type of the parameter
enum {Real=REAL, Integer=INTEGER, Category=CATEGORY, Int=INTEGER};
// type of data
enum {NA=0, Above_Detection=1, Below_Detection=2, Measured=3};

typedef struct {
  uint mode, n, i;
  double *t;
} TimePoints;

struct line {
  int n;
  double *x;
  double *y;
  double *m;
  double *c;
};

struct data_individual {
  int n;
  int np;
  uint **value;
  double **Y;
  double saturated;
};

struct data_treatment {
  int nind;
  int *usevar;
  char levels[1000];
  struct data_individual *Individual;
};

struct data {
  int ntrt;
  int nvar;
  int maxnind;
  int maxnp;
  char filename[100];
  struct data_treatment *Treatment;
};

typedef struct treatmentlist {
  int n;
  int *treatment;
} Treatments;

typedef struct hyperblock {
  /* set in CreateBlock */
  int use;

  /* set in AddHyperToTreatment */
  int nhypers_used;
  int *hyper_used;
  int **nhypers_with_treatment;
  int ***hyper_with_treatment;
  int nassociated_pars;
  int *associated_par;
  int *ntreatments_with_parameter;
  int **treatment_with_parameter;

  /* set in Bayes */
  int hdistribution;
  int pdistribution;
  int *dependent_block;
} Hyperblock;

struct hyper {
  int index;
  int use;
  int parno;
  int hdist;
  int number;
  char name[100];
  double location;
  double scale;
  double shape;
};

typedef struct allhypers {
  int init;
  int ntrt;
  int *nind;
  int *trtused;
  int nhyper_parameters;
  struct hyper *H;
  int nblocks;
  Hyperblock *block;
  double *hyper;         //used for initialisation of thread hyper values
  double **hyper_store;
} Hyperparameters;

struct variable {
  int index;
  char name[100];
};

struct variables {
  int    variables;
  double scaletime;
  struct variable *W;
};

struct parameter {
  int index;
  int number;
  int distribution;
  int circular;
  int type;
  int hashyper;
  int hyper_location_block_index;
  int hyper_shape_block_index;
  int hyper_scale_block_index;
  int ncategories;
  double *p; // used for categorical parameters
  double location;
  double scale;
  double shape;
  char name[100];
  double (*(*link))(double);
};

struct blocks {
  int nblocks;
  int **pblock;
};

struct parameterset {
  int    pass;
  int    parameters;   // maximum parameter index
  int    npar;         // number of fitted parameters
  int    ninteger;
  int    *integer;
  int    nauxillary;
  int    *auxillary;
  struct parameter *P;
  struct blocks Blocks;
};

struct parameter_block {
  int categorical;
  int done;
  int npar;
  int *pblock;
  int covfinal;
  int covstart;
  int inrange;
  int storeno;
  int error;
  double accept;      /*mcmc acceptances*/
  double proposed;    /*mcmc iterations*/
  double covscale;    /*covariance matrix scaling factor*/
  //  double **L;         /*covariance matrix*/
  gsl_matrix *L;
  double **theta_store;
};

struct chain {
  int done;       /*if current individual is ready for next phase*/
  int loop;
  /* parameters for threads */
  double logmaxL;
  double logL;
  double *logP;
  double logpost;
  double *theta;       /*current parameter values*/
  double minlogL;
  double prop_logL;
  double prop_logP;
  double prop_logpost;
  int storeno;
  int laststoreno;
  double accept;      /*mcmc acceptances*/
  double proposed;    /*mcmc iterations*/
  double **theta_store;
  double *logLstore;
  double *logpoststore;
  double exchangeaccept;      /*mcmc acceptances*/
  double exchangeproposed;    /*mcmc iterations*/
  struct parameter_block *PB;
};

struct sim_individual {
  int done;
  int chains;
  int *chainno;
  double *temp;
  struct chain *Chain;
  struct parameterset Q;
};

typedef struct {
  int n;
  int m;
  int **a;
} Matrix;

struct sim_treatment {
  struct sim_individual *Individual;
};

struct thread {
  double *hyper;
  struct sim_treatment *Treatment;
  gsl_rng *stream;
};

typedef struct simulation {
  Hyperparameters HP;
  int *trtused;
  int **indused;
  int ntp;            // individual with most parameters
  int nrp;            // individual with most parameters
  int rdp;
  int sim;
  int maxc;
  int seed;
  int thin;
  int mcmc;
  int popd;
  int marg;
  int maxnp;
  int error;
  int range;
  int chains;
  int notime;
  int copyind;
  int threads;
  int verbose; /* 0: no output, 1: output chain 0+lowest exchangerate,
		  2: all chains*/
  int derived;
  int discard;
  int samples;
  int storeno;
  int gsl_error;
  int population;
  int covsamples;
  int growthcurves;
  int parsperblock;
  int phase1adjust;
  int phase2adjust;
  int mincovsamples;
  int ignorewarning;
  int usehypermeans;
  int sample_parameters;
  char expno[50];
  char outexpno[50];
  char *parsfile;
  char *tempfile;
  char logfile[1000];
  char factors[1000];
  double xrate;
  double timeend;
  double lowrate;
  double highrate;
  double minprior;
  double initscale;
  double covthresh;
  double timestart;
  double scalefactor;
  double saturatedlogL;
  double minexchangerate;
  struct sim_individual Sample;
  struct sim_treatment *Treatment;
  struct variables V;
} Simulation;

extern char expno[];

extern int* integervector( int );
extern uint* uintegervector( int );
extern uint** uintegermatrix( int, int );
extern double* doublevector( int );
extern double** doublematrix( int, int );
extern double*** doublematrix3( int, int, int );
extern double**** doublematrix4( int, int, int, int );
extern char* charvector( int );
extern char** charmatrix( int, int );
extern double factln( int );
extern double gammln( double );
extern void binorm( double*, double, double, double, double, double );
extern long int lrint(double);
extern double pow10(double);
extern double round(double);
extern double rtnewt(void(*)(double, double*, double*, double*),
		     double, double, double, double*);
extern double rtbis( double (*)(double, double*), double, double,
		     double, int*, double*);

extern void *malloc(size_t);
extern void exit(int);
extern void free(void*);
extern void Error(char*);
extern void ReadParam( char*, void*, char* ) ;

extern uint   binomialRNG(uint, double, const gsl_rng*);
extern void   multinomialRNG(uint, const double*, size_t, unsigned int*, const gsl_rng*);
extern int    poissonRNG(double, const gsl_rng*);
extern int    uniform_intRNG(int, int, const gsl_rng*);
extern double normalRNG(double, double, const gsl_rng*);
extern double betaRNG(double, double, const gsl_rng*);
extern double chisqRNG(double, const gsl_rng*);
extern double scaleinvchisqRNG(double, double, const gsl_rng*);
extern double invgammaRNG(double, double, const gsl_rng*);
extern double gammaRNG(double, double, const gsl_rng*);
extern double uniformRNG(double, double, const gsl_rng*);
extern double paretoRNG(double, double, const gsl_rng*);
extern void   dirichlet_multinomialRNG(uint, const double*, size_t, uint*, const gsl_rng*);
extern void   dirichletRNG(const double*, size_t, double*, const gsl_rng*);
extern uint   beta_binomialRNG(uint, double, double, const gsl_rng*);
extern uint   negative_binomialRNG(double, double, const gsl_rng*);
extern uint   beta_negative_binomialRNG(uint, double, double, const gsl_rng*);
/* Matrix *wishartRNG(int, int, Matrix*, Matrix*, const gsl_rng*); */
/* void   MVNRNG(int, Matrix*, Matrix*, Matrix*, const gsl_rng*); */

extern double poissonCMF(const double, const double, int);
extern double gamma_scalePDF(const double, const double, const double);
extern double betaPDF(const double, const double, const double);
extern double exponentialPDF(const double, const double);
extern double poissonPMF(const double, const double);
extern double geometricPDF(const double, const double);
extern double trnormalPDF(const double, const double, const double);
extern double normalCDF(const double, const double, const double, int);
extern double normalPDF(const double, const double, const double);
extern double binomialPMF(const double, const double, const double);
extern double multinomialPMF(const unsigned int, const double*, const double*);
extern double uniformPDF(const double, const double, const double);
extern double logisticPDF(const double, const double, const double);
extern double betabinomialPMF(const double, const double, const double, const double);
//extern double MVNPDF(Matrix*, Matrix*, Matrix*, const double, int);
extern double von_mises_cdf ( double x, double a, double b );
extern double von_mises_cdf_inv ( double cdf, double a, double b );

extern Matrix *MatrixInit(Matrix*, int, int, int*);
extern Matrix *MatrixDestroy(Matrix*);
extern double identity(double);
extern double logit(double);
extern double invlogit(double);

int    function(int, int, double*, double*, double*, TimePoints*);
int    Model(const uint, const uint, const uint, double*, double**,
	     TimePoints*);
//int    Model(int, int, int, double*, double**);
void   Block(struct parameterset*, int, ...);
void   Par(struct parameterset*, int, int, char*, int, ...);
void Create(Hyperparameters *H, int *h, int block, int par,
	    Matrix M, char *name, int type, double p1, double p2);
void   Hyper(Hyperparameters *HP, int index, int number, char *name, int distribution,
	     double shape_or_loc, double scale);
void   CreateHyperBlock(Hyperparameters *HP, int index);
void   AddHyperToATreatment(Hyperparameters *HP, int index, int h, int pindex,
			    int treatment);
void   AddHyperToTreatments(Hyperparameters *HP, int index, int h, int pindex, ...);
void   HyperParameters(Treatments, Hyperparameters*);
void   Var(struct variables*, int, char*);
void   GlobalParameters(void);
void   Variables(struct variables*);
void   OutputModel(int, int, double*, double*);
void   OutputData(int, int, double*, double*, double*, uint*);
void   DerivedHyperParameters(int trt, double *p);
void   DerivedParameters(int, int, int, double*, double**, TimePoints*);
void   Parameters(int, int, double*, struct parameterset*);
void   PredictData(int, int, double*, double*, double*, gsl_rng*);
void   SimulateData(int, int, double*, double*, double*, double*, uint*, gsl_rng*);
void   SaturatedModel(int, int, double*, double*, double*, uint*);
void   Residual(int, int, double*, double*, double*, double*, uint*);
void   Equations(double*, double*, int, double*, double*, int, double);
double timestep(void);
double logLikelihood(const uint, const uint, const double*,
		     const double*, const double*, const uint*);
double logLikelihoodI(const uint trt, const uint ind, const double *p,
		      double **V, double *Data, const uint *value,
		      const uint N);
void WAIC(int trt, int ind, double *lnL, double *V,
	  double *p, double *Data, uint *value);
//double logLikelihood(int, int, double*, double*, double*, int*);
double UserPDF(double);
void   ScaleTime(struct variables*, double);