void LVlinear_train(lvError *lvErr, const LVlinear_problem *prob_in, const LVlinear_parameter *param_in, LVlinear_model * model_out){
	try{
		// Input verification: Problem dimensions
		if ((*(prob_in->x))->dimSize != (*(prob_in->y))->dimSize)
			throw LVException(__FILE__, __LINE__, "The problem must have an equal number of labels and feature vectors (x and y).");

		//-- Convert problem
		std::unique_ptr<problem> prob(new problem);
		uint32_t nr_nodes = (*(prob_in->y))->dimSize;
		prob->l = nr_nodes;
		prob->y = (*(prob_in->y))->elt;

		// Create and array of pointers (sparse datastructure)
		std::unique_ptr<feature_node*[]> x(new feature_node*[nr_nodes]);
		prob->x = x.get();

		auto x_in = prob_in->x;
		for (unsigned int i = 0; i < (*x_in)->dimSize; i++){
			// Assign the innermost svm_node array pointers to the array of pointers
			auto xi_in_Hdl = (*x_in)->elt[i];
			x[i] = reinterpret_cast<feature_node*>((*xi_in_Hdl)->elt);
		}

		//-- Convert parameters
		std::unique_ptr<parameter> param(new parameter());
		LVConvertParameter(param_in, param.get());

		// Verify parameters
		const char * param_check = check_parameter(prob.get(), param.get());
		if (param_check != nullptr)
			throw LVException(__FILE__, __LINE__, "Parameter check failed with the following error: " + std::string(param_check));

		// Train model
		model *result = train(prob.get(), param.get());

		// Copy model to LabVIEW memory
		LVConvertModel(result, model_out);

		// Release memory allocated by train
		free_model_content(result);
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		// To avoid LabVIEW reading and utilizing bad memory, the dimension sizes of arrays is set to zero
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
	}
	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
	}
}
double LVlinear_predict_values(lvError *lvErr, const LVlinear_model  *model_in, const LVArray_Hdl<LVlinear_node> x_in, LVArray_Hdl<double> dec_values_out){
	try{
		// Input validation: Uninitialized model
		if (model_in == nullptr || model_in->w == nullptr || (*model_in->w)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Uninitialized model passed to liblinear_predict_values.");

		// Input validation: Empty feature vector
		if (x_in == nullptr || (*x_in)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Empty feature vector passed to liblinear_predict_values.");

		// Input validation: Final index -1?
		if ((*x_in)->elt[(*x_in)->dimSize - 1].index != -1)
			throw LVException(__FILE__, __LINE__, "The index of the last element of the feature vector needs to be -1 (liblinear_predict_values).");

		// Convert LVsvm_model to svm_model
		auto mdl = std::make_unique<model>();
		LVConvertModel(*model_in, *mdl);

		int nr_class = model_in->nr_class;
		int solver = (model_in->param).solver_type;

		// Set up output array
		int nr_dec = 0;
		if (nr_class <= 2){
			if (solver == MCSVM_CS)
				nr_dec = 2;
			else
				nr_dec = 1;
		}
		else {
			nr_dec = nr_class;
		}

		LVResizeNumericArrayHandle(dec_values_out, nr_dec);
		(*dec_values_out)->dimSize = nr_dec;

		double predicted_label = predict_values(mdl.get(), reinterpret_cast<feature_node*>((*x_in)->elt), (*dec_values_out)->elt);

		return predicted_label;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*dec_values_out)->dimSize = 0;
		return std::nan("");
	}

	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		(*dec_values_out)->dimSize = 0;
		return std::nan("");
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		(*dec_values_out)->dimSize = 0;
		return std::nan("");
	}
}
void LVlinear_cross_validation(lvError *lvErr, const LVlinear_problem *prob_in, const LVlinear_parameter *param_in, const int32_t nr_fold, LVArray_Hdl<double> target_out){
	try{
		// Input verification: Problem dimensions
		if ((*(prob_in->x))->dimSize != (*(prob_in->y))->dimSize)
			throw LVException(__FILE__, __LINE__, "The problem must have an equal number of labels and feature vectors (x and y).");

		// Convert LVsvm_problem to svm_problem
		std::unique_ptr<problem> prob(new problem);
		uint32_t nr_nodes = (*(prob_in->y))->dimSize;
		prob->l = nr_nodes;
		prob->y = (*(prob_in->y))->elt;

		// Create and array of pointers (sparse datastructure)
		std::unique_ptr<feature_node*[]> x(new feature_node*[nr_nodes]);
		prob->x = x.get();

		auto x_in = prob_in->x;
		for (unsigned int i = 0; i < (*x_in)->dimSize; i++){
			// Assign the innermost svm_node array pointers to the array of pointers
			auto xi_in_Hdl = (*x_in)->elt[i];
			x[i] = reinterpret_cast<feature_node*>((*xi_in_Hdl)->elt);
		}

		// Assign parameters to svm_parameter
		std::unique_ptr<parameter> param(new parameter());
		LVConvertParameter(param_in, param.get());

		// Verify parameters
		const char * param_check = check_parameter(prob.get(), param.get());
		if (param_check != nullptr)
			throw LVException(__FILE__, __LINE__, "Parameter check failed with the following error: " + std::string(param_check));

		// Allocate room in target_out
		LVResizeNumericArrayHandle(target_out, nr_nodes);

		// Run cross validation
		cross_validation(prob.get(), param.get(), nr_fold, (*target_out)->elt);
		(*target_out)->dimSize = nr_nodes;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*target_out)->dimSize = 0;
	}
	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		(*target_out)->dimSize = 0;
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		(*target_out)->dimSize = 0;
	}
}
double LVlinear_predict_probability(lvError *lvErr, const LVlinear_model  *model_in, const LVArray_Hdl<LVlinear_node> x_in, LVArray_Hdl<double> prob_estimates_out){
	try{
		// Input validation: Uninitialized model
		if (model_in == nullptr || model_in->w == nullptr || (*model_in->w)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Uninitialized model passed to liblinear_predict_probability.");

		// Input validation: Empty feature vector
		if (x_in == nullptr || (*x_in)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Empty feature vector passed to liblinear_predict_probability.");

		// Input validation: Final index -1?
		if ((*x_in)->elt[(*x_in)->dimSize - 1].index != -1)
			throw LVException(__FILE__, __LINE__, "The index of the last element of the feature vector needs to be -1 (liblinear_predict_probability).");

		// Convert LVsvm_model to svm_model
		auto mdl = std::make_unique<model>();
		LVConvertModel(*model_in, *mdl);

		// Check probability model
		int valid_probability = check_probability_model(mdl.get());
		if (!valid_probability)
			throw LVException(__FILE__, __LINE__, "The selected solver type does not support probability output.");

		// Allocate room for probability estimates
		LVResizeNumericArrayHandle(prob_estimates_out, mdl->nr_class);
		(*prob_estimates_out)->dimSize = mdl->nr_class;

		double highest_prob_label = predict_probability(mdl.get(), reinterpret_cast<feature_node*>((*x_in)->elt), (*prob_estimates_out)->elt);

		return highest_prob_label;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}

	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}
}
void LVConvertParameter(const LVlinear_parameter &param_in, parameter &param_out){
	param_out.solver_type = param_in.solver_type;
	param_out.eps = param_in.eps;
	param_out.C = param_in.C;
	param_out.p = param_in.p;
	param_out.init_sol = nullptr; // TODO: add support for warm-start

	// Weight label
	if (param_in.weight_label != nullptr && param_in.weight != nullptr){
		if ((*(param_in.weight))->dimSize != (*(param_in.weight_label))->dimSize)
			throw LVException(__FILE__, __LINE__, "Parameter error: Number of elements in weight_label and weight does not match.");

		if ((*(param_in.weight_label))->dimSize > 0)
			param_out.weight_label = (*(param_in.weight_label))->elt;
		else
			param_out.weight_label = nullptr;

		// Weight
		if ((*(param_in.weight))->dimSize > 0){
			param_out.weight = (*(param_in.weight))->elt;
			param_out.nr_weight = (*(param_in.weight))->dimSize;
		}
		else{
			param_out.weight = nullptr;
			param_out.nr_weight = 0;
		}
	}
}
void LVlinear_save_model(lvError *lvErr, const char *path_in, const LVlinear_model *model_in){
	try{
		errno = 0;

		// Convert LVsvm_model to svm_model
		auto mdl = std::make_unique<model>();
		LVConvertModel(*model_in, *mdl);

		int err = save_model(path_in, mdl.get());

		if (err == -1){
			// Allocate room for output error message (truncated if buffer is too small)
			const size_t bufSz = 256;
			char buf[bufSz] = "";
			std::string errstr;

#if defined(_WIN32) || defined(_WIN64)
			if (strerror_s(buf, bufSz, errno) != 0)
				errstr = buf;
			else
				errstr = "Unknown error";
#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
			if (strerror_r(errno, buf, bufSz) != 0)
				errstr = buf;
			else
				errstr = "Unknown error";
#else
			char* gnuerr = strerror_r(errno, buf, bufSz);
			if (gnuerr != nullptr)
				errstr = gnuerr;
			else
				errstr = "Unknown error";
#endif

			errno = 0;

			throw LVException(__FILE__, __LINE__, "Model load operation failed (" + errstr + ").");
		}
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
	}
	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
	}
}
double LVlinear_predict(lvError *lvErr, const struct LVlinear_model *model_in, const LVArray_Hdl<LVlinear_node> x_in){
	try{
		// Input validation: Uninitialized model
		if (model_in == nullptr || model_in->w == nullptr || (*model_in->w)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Uninitialized model passed to liblinear_predict.");

		// Input validation: Empty feature vector
		if (x_in == nullptr || (*x_in)->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Empty feature vector passed to liblinear_predict.");

		// Input validation: Final index -1?
		if ((*x_in)->elt[(*x_in)->dimSize - 1].index != -1)
			throw LVException(__FILE__, __LINE__, "The index of the last element of the feature vector needs to be -1 (liblinear_predict).");

		// Convert LVsvm_model to svm_model
		auto mdl = std::make_unique<model>();
		LVConvertModel(*model_in, *mdl);

		double label = predict(mdl.get(), reinterpret_cast<feature_node*>((*x_in)->elt));

		return label;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		return std::nan("");
	}

	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		return std::nan("");
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		return std::nan("");
	}
}
void LVConvertParameter(const LVlinear_parameter *param_in, parameter *param_out){
	param_out->solver_type = param_in->solver_type;
	param_out->eps = param_in->eps;
	param_out->C = param_in->C;
	param_out->p = param_in->p;

	if ((*(param_in->weight))->dimSize != (*(param_in->weight_label))->dimSize)
		throw LVException(__FILE__, __LINE__, "Parameter error: Number of elements in weight_label and weight does not match.");

	param_out->nr_weight = (*(param_in->weight))->dimSize;

	// Weight label
	if ((*(param_in->weight_label))->dimSize > 0)
		param_out->weight_label = (*(param_in->weight_label))->elt;
	else
		param_out->weight_label = nullptr;

	// Weight
	if ((*(param_in->weight))->dimSize > 0)
		param_out->weight = (*(param_in->weight))->elt;
	else
		param_out->weight = nullptr;
}
double LVlinear_predict_probability(lvError *lvErr, const LVlinear_model  *model_in, const LVArray_Hdl<LVlinear_node> x_in, LVArray_Hdl<double> prob_estimates_out){
	try{
		// Convert LVsvm_model to svm_model
		std::unique_ptr<model> model(new model);
		LVConvertModel(model_in, model.get());

		// Check probability model
		int valid_probability = check_probability_model(model.get());
		if (!valid_probability)
			throw LVException(__FILE__, __LINE__, "The model does not support probability output.");

		// Allocate room for probability estimates
		LVResizeNumericArrayHandle(prob_estimates_out, model->nr_class);
		(*prob_estimates_out)->dimSize = model->nr_class;

		double highest_prob_label = predict_probability(model.get(), reinterpret_cast<feature_node*>((*x_in)->elt), (*prob_estimates_out)->elt);

		return highest_prob_label;
	}
	catch (LVException &ex) {
		ex.returnError(lvErr);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}

	catch (std::exception &ex) {
		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}
	catch (...) {
		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
		(*prob_estimates_out)->dimSize = 0;
		return std::nan("");
	}
}
void LVlinear_load_model(lvError *lvErr, const char *path_in, LVlinear_model *model_out){
	try{
		errno = 0;

		model *mdl = load_model(path_in);

		if (mdl == nullptr){
			// Allocate room for output error message (truncated if buffer is too small)
			const size_t bufSz = 256;
			char buf[bufSz] = "";
			std::string errstr;

#if defined(_WIN32) || defined(_WIN64)
			if (strerror_s(buf, bufSz, errno) != 0)
				errstr = buf;
			else
				errstr = "Unknown error";
#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
			if (strerror_r(errno, buf, bufSz) != 0)
				errstr = buf;
			else
				errstr = "Unknown error";
#else
			char* gnuerr = strerror_r(errno, buf, bufSz);
			if (gnuerr != nullptr)
				errstr = gnuerr;
			else
				errstr = "Unknown error";
#endif

			errno = 0;

			throw LVException(__FILE__, __LINE__, "Model load operation failed (" + errstr + ").");
		}
		else{
			// liblinear returns uninitialized values for the parameters (except solver type)
			(mdl->param).C = 0;
			(mdl->param).eps = 0;
			(mdl->param).init_sol = nullptr;
			(mdl->param).nr_weight = 0;
			(mdl->param).p = 0;
			(mdl->param).weight = nullptr;
			(mdl->param).weight_label = nullptr;

			LVConvertModel(*mdl, *model_out);
			free_model_content(mdl);
		}
	}
	catch (LVException &ex) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		ex.returnError(lvErr);
	}
	catch (std::exception &ex) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
	}
	catch (...) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);
	}
}
void LVlinear_train(lvError *lvErr, const LVlinear_problem *prob_in, const LVlinear_parameter *param_in, LVlinear_model * model_out){
	try{
		// Input verification: Nonempty problem
		if (prob_in->x == nullptr || (*(prob_in->x))->dimSize == 0)
			throw LVException(__FILE__, __LINE__, "Empty problem passed to liblinear_train.");

		// Input verification: Problem dimensions
		if ((*(prob_in->x))->dimSize != (*(prob_in->y))->dimSize)
			throw LVException(__FILE__, __LINE__, "The problem must have an equal number of labels and feature vectors (x and y).");

		uint32_t nr_nodes = (*(prob_in->y))->dimSize;

		// Input validation: Number of feature vectors too large (exceeds max signed int)
		if(nr_nodes > INT_MAX)
			throw LVException(__FILE__, __LINE__, "Number of feature vectors too large (grater than " + std::to_string(INT_MAX) + ")");

		//-- Convert problem
		auto prob = std::make_unique<problem>();
		prob->l = nr_nodes;
		prob->y = (*(prob_in->y))->elt;
		prob->n = 0; // Calculated later
		prob->bias = prob_in->bias;

		// Create and array of pointers (sparse datastructure)
		auto x = std::make_unique<feature_node*[]>(nr_nodes);
		prob->x = x.get();

		auto x_in = prob_in->x;
		for (unsigned int i = 0; i < (*x_in)->dimSize; i++){
			// Assign the innermost svm_node array pointers to the array of pointers
			auto xi_in_Hdl = (*x_in)->elt[i];
			x[i] = reinterpret_cast<feature_node*>((*xi_in_Hdl)->elt);

			// Input validation: Final index -1?
			if ((*xi_in_Hdl)->elt[(*xi_in_Hdl)->dimSize - 1].index != -1)
				throw LVException(__FILE__, __LINE__, "The index of the last element of each feature vector needs to be -1 (liblinear_train).");

			// Calculate the max index
			// This detail is not exposed in LabVIEW, as setting the wrong value causes a crash
			// Second to last element should contain the max index for that feature vector (as they are in ascending order).
			auto secondToLast = (*xi_in_Hdl)->dimSize - 2; // Ignoring -1 index
			auto largestIndex = (*xi_in_Hdl)->elt[secondToLast].index;
			if (secondToLast >= 0 && largestIndex > prob->n)
				prob->n = largestIndex;
		}

		//-- Convert parameters
		auto param = std::make_unique<parameter>();
		LVConvertParameter(*param_in, *param);

		// Verify parameters
		const char * param_check = check_parameter(prob.get(), param.get());
		if (param_check != nullptr)
			throw LVException(__FILE__, __LINE__, "Parameter check failed with the following error: " + std::string(param_check));

		// Train model
		model *result = train(prob.get(), param.get());

		// Copy model to LabVIEW memory
		LVConvertModel(*result, *model_out);

		// Release memory allocated by train
		free_model_content(result);
	}
	catch (LVException &ex) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		ex.returnError(lvErr);
	}
	catch (std::exception &ex) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		LVException::returnStdException(lvErr, __FILE__, __LINE__, ex);
	}
	catch (...) {
		(*(model_out->label))->dimSize = 0;
		(*(model_out->w))->dimSize = 0;
		(*(model_out->param).weight)->dimSize = 0;
		(*(model_out->param).weight_label)->dimSize = 0;

		LVException ex(__FILE__, __LINE__, "Unknown exception has occurred");
		ex.returnError(lvErr);

	}
}