/*

	This file is part of PEST++.

	PEST++ 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.

	PEST++ 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 PEST++.  If not, see<http://www.gnu.org/licenses/>.
*/
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include <math.h>
#include <sstream>
#include <Eigen/Dense>
#include <cassert>
#include <iostream>
#include "Transformation.h"
#include "Transformable.h"
#include "SVD_PROPACK.h"
#include "eigen_tools.h"
#include "Jacobian.h"
#include "QSqrtMatrix.h"
#include "pest_data_structs.h"
#include "debug.h"
#include "Regularization.h"
#include "Serialization.h"
#include "eigen_tools.h"

using namespace std;
using namespace Eigen;

///////////////// Transformation Methods /////////////////

///////////////// TranMapBase Methods /////////////////
void TranMapBase::insert(const string &item_name, double item_value)
{
	items[item_name] = item_value;
}

void TranMapBase::insert(const Parameters &pars)
{
	for (const auto &ipar : pars)
	{
		items[ipar.first] = ipar.second;
	}
}

void TranMapBase::reset(const Parameters &pars)
{
	items.clear();
	for (const auto &ipar : pars)
	{
		items[ipar.first] = ipar.second;
	}
}

void TranMapBase::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranMapBase)" << endl;
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << (*b).first << ";  value = " << (*b).second << endl;
	}
}

pair<bool, double> TranMapBase::get_value(const string &name) const
{
	pair<bool, double> ret_val(false, 0.0);
	map<string, double>::const_iterator it;

	it = items.find(name);
	if (it !=items.end()) {
		ret_val = pair<bool, double>(true, (*it).second);
	}
	return ret_val;
}

///////////////// TranSetBase Methods /////////////////
void TranSetBase::insert(const string &item_name)
{
	items.insert(item_name);
}

void TranSetBase::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranSetBase)" << endl;
	for (set<string>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << *b << endl;
	}
}

bool TranSetBase::has_value(const string &name) const
{
	bool ret_val = false;
	set<string>::const_iterator it;

	it = items.find(name);
	if (it !=items.end()) {
		ret_val = true;
	}
	return ret_val;
}

///////////////// TranOffset Methods /////////////////
void TranOffset::forward(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
		data_iter = data.find(b->first);
		if (data_iter != data_end)
		{
			(*data_iter).second += (*b).second;
		}
	}
}

void TranOffset::reverse(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
		data_iter = data.find(b->first);
		if (data_iter != data_end)
		{
			(*data_iter).second -= b->second;
		}
	}
}

void TranOffset::jacobian_forward(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	forward(data);
}

void TranOffset::jacobian_reverse(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	reverse(data);
}

void TranOffset::d2_to_d1(Transformable &del_data, Transformable &data)
{
	// Offset transformation does not affect derivatives.
	// Nothing to do.
	reverse(data);
}

void TranOffset::d1_to_d2(Transformable &del_data, Transformable &data)
{
	// Offset transformation does not affect derivatives.
	// Nothing to do.
	forward(data);
}

void TranOffset::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranOffset)" << endl;
	for (map<string, double>::const_iterator b = items.begin(), e = items.end();
		b != e; ++b) {
		os << "  item name = " << (*b).first << ";  offset value = " << (*b).second << endl;
	}
}

///////////////// TranScale Methods /////////////////
void TranScale::forward(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
		data_iter = data.find(b->first);
		if (data_iter != data_end)
		{
			(*data_iter).second *= b->second;
		}
	}
}

void TranScale::reverse(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
		data_iter = data.find(b->first);
		if (data_iter != data_end)
		{
			(*data_iter).second /= b->second;
		}
	}
}

void TranScale::jacobian_forward(Jacobian &jac)
{
	size_t icol = 0;
	double factor = 0;
	Transformable &data = jac.base_numeric_parameters;
	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
	auto iter_end = par_2_col_map.end();
	for (const auto &irec : items)
	{
		auto iter = par_2_col_map.find(irec.first);
		if (iter != iter_end)
		{
			icol = iter->second;
			factor = irec.second;
			jac.matrix.col(icol) /= factor;
		}
	}
	forward(data);
}

void TranScale::jacobian_reverse(Jacobian &jac)
{
	size_t icol = 0;
	double factor = 0;
	Transformable &data = jac.base_numeric_parameters;
	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
	auto iter_end = par_2_col_map.end();
	for (const auto &irec : items)
	{
		auto iter = par_2_col_map.find(irec.first);
		if (iter != iter_end)
		{
			icol = iter->second;
			factor = irec.second;
			jac.matrix.col(icol) *= factor;
		}
	}
	reverse(data);
}

void TranScale::d1_to_d2(Transformable &del_data, Transformable &data)
{
	Transformable::iterator del_data_iter, del_data_end = del_data.end();
	for (map<string, double>::const_iterator b = items.begin(), e = items.end();
		b != e; ++b) {
		del_data_iter = del_data.find(b->first);
		if (del_data_iter != del_data_end)
		{
			(*del_data_iter).second /= b->second;
		}
	}
	forward(data);
}

void TranScale::d2_to_d1(Transformable &del_data, Transformable &data)
{
	Transformable::iterator del_data_iter, del_data_end = del_data.end();
	for (map<string, double>::const_iterator b = items.begin(), e = items.end();
		b != e; ++b) {
		del_data_iter = del_data.find(b->first);
		if (del_data_iter != del_data_end)
		{
			(*del_data_iter).second *= b->second;
		}
	}
	reverse(data);
}

void TranScale::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranScale)" << endl;
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << (*b).first << ";  scale value = " << (*b).second << endl;
	}
}

///////////////// TranLog10 Methods /////////////////
void TranLog10::forward(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (set<string>::const_iterator b=items.begin(), e=items.end(); b!=e; ++b)
	{
		data_iter = data.find(*b);
		if (data_iter != data_end)
		{
			(*data_iter).second = log10((*data_iter).second);
		}
	}
}

void TranLog10::reverse(Transformable &data)
{
	Transformable::iterator data_iter, data_end = data.end();
	for (set<string>::const_iterator b=items.begin(), e=items.end(); b!=e; ++b)
	{
		data_iter = data.find(*b);
		if (data_iter != data_end)
		{
			(*data_iter).second = pow(10.0, (*data_iter).second);
		}
	}
}

void TranLog10::jacobian_forward(Jacobian &jac)
{
	size_t icol = 0;
	double factor = 0;
	double d = 0;
	Transformable &data = jac.base_numeric_parameters;
	forward(data);
	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
	auto iter_end = par_2_col_map.end();
	for (const auto &ipar : items)
	{
		auto iter = par_2_col_map.find(ipar);
		if (iter != iter_end)
		{
			d = data.get_rec(ipar);
			icol = iter->second;
			factor = pow(10, d) * log(10.0);
		}
		jac.matrix.col(icol) *= factor;
	}
}

void TranLog10::jacobian_reverse(Jacobian &jac)
{
	size_t icol = 0;
	double factor = 0;
	double d = 0;
	Transformable &data = jac.base_numeric_parameters;
	reverse(data);
	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
	auto iter_end = par_2_col_map.end();
	for (const auto &ipar : items)
	{
		auto iter = par_2_col_map.find(ipar);
		if (iter != iter_end)
		{
			d = data.get_rec(ipar);
			icol = iter->second;
			factor = 1.0 / (d * log(10.0));
			jac.matrix.col(icol) *= factor;
		}
	}
}

void TranLog10::d1_to_d2(Transformable &del_data, Transformable &data)
{
	forward(data);
	Transformable::iterator del_data_iter, del_data_end = del_data.end();
	for (set<string>::const_iterator b = items.begin(), e = items.end(); b != e; ++b)
	{
		del_data_iter = del_data.find(*b);
		if (del_data_iter != del_data_end)
		{
			double d1 = data.get_rec(*b);
			double factor = pow(10.0, d1) * log(10.0);
			(*del_data_iter).second *= factor;
		}
	}
}

void TranLog10::d2_to_d1(Transformable &del_data, Transformable &data)
{
	reverse(data);
	Transformable::iterator del_data_iter, del_data_end = del_data.end();
	for (set<string>::const_iterator b = items.begin(), e = items.end(); b != e; ++b)
	{
		del_data_iter = del_data.find(*b);
		if (del_data_iter != del_data_end)
		{
			double d2 = data.get_rec(*b);
			double factor = 1.0 / (d2 * log(10.0));
			(*del_data_iter).second *= factor;
		}
	}
	reverse(data);
}

void TranLog10::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranLog10)" << endl;
	for (set<string>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << *b << endl;
	}
}

///////////////// TranFixed Methods /////////////////
void TranFixed::forward(Transformable &data)
{
	for (map<string,double>::iterator b=items.begin(), e=items.end(); b!=e; ++b)
	{
		data.erase(b->first);
	}
}

void TranFixed::reverse(Transformable &data)
{
	for (map<string,double>::iterator b=items.begin(), e=items.end(); b!=e; ++b)
	{
		data.insert(b->first, b->second);
	}
}

void TranFixed::jacobian_forward(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	set<string> rm_par_set;
	for (const auto &irec : items)
	{
		rm_par_set.insert(irec.first);
	}
	//remove Fixed parameters from base_parameter_names
	jac.remove_cols(rm_par_set);
	forward(data);
}

void TranFixed::jacobian_reverse(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	set<string> new_pars;
	for (auto &i : items)
	{
		new_pars.insert(i.first);
	}
	jac.add_cols(new_pars);
	reverse(data);
}

void TranFixed::d1_to_d2(Transformable &del_data, Transformable &data)
{
	for (map<string, double>::iterator b = items.begin(), e = items.end(); b != e; ++b)
	{
		del_data.erase(b->first);
	}
	forward(data);
}

void TranFixed::d2_to_d1(Transformable &del_data, Transformable &data)
{
	for (map<string, double>::iterator b = items.begin(), e = items.end(); b != e; ++b)
	{
		del_data.insert(b->first, 0.0);
	}
	reverse(data);
}

void TranFixed::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranFixed)" << endl;
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << (*b).first << ";  imposed value = " << (*b).second << endl;
	}
}

void TranFrozen::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranFrozen)" << endl;
	for (map<string,double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << (*b).first << ";  imposed value = " << (*b).second << endl;
	}
}

void TranTied::insert(const string &item_name, const pair<string, double> &item_value)
{
	items[item_name] = item_value;
}

void TranTied::forward(Transformable &data)
{
	for (map<string, pair_string_double>::iterator ii = items.begin(); ii != items.end(); ++ii)
	{
		data.erase(ii->first);
	}
}

void TranTied::reverse(Transformable &data)
{
	string const *base_name;
	double *factor;
	Transformable::iterator base_iter;
	for (map<string, pair_string_double>::iterator b = items.begin(), e = items.end();
		b != e; ++b)
	{
		base_name = &(b->second.first);
		factor = &(b->second.second);
		base_iter = data.find(*base_name);
		if (base_iter != data.end())
		{
			//cout << b->first << ',' << data[b->first] <<  ',' << (*base_iter).second << endl;
			data.erase(b->first);
			data.insert(b->first, (*base_iter).second * (*factor));
			//cout << b->first << ',' << data[b->first] << ',' << (*base_iter).second << endl;
		}

	}
}

void TranTied::jacobian_forward(Jacobian &jac)
{
	throw(PestError("Error: TranTied::jacobian_forward - TranTied does not support Jacobian transformations"));
}

void TranTied::jacobian_reverse(Jacobian &jac)
{
	throw(PestError("Error: TranTied::jacobian_forward - TranTied does not support Jacobian transformations"));
}

void TranTied::d1_to_d2(Transformable &del_data, Transformable &data)
{
	throw(PestError("Error: TranTied::d1_to_d2 - TranTied does not support d1_to_d2 transformations"));
}

void TranTied::d2_to_d1(Transformable &del_data, Transformable &data)
{
	throw(PestError("Error: TranTied::d2_to_d1 - TranTied does not support d2_to_d1 transformations"));
}

void TranTied::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranTied)" << endl;
	for (map<string, pair_string_double>::const_iterator b=items.begin(), e=items.end();
		b!=e; ++b) {
			os << "  item name = " << (*b).first << "   tied to \"" << (*b).second.first <<
				"\" with factor " <<  (*b).second.second<<  endl;
	}
}

const  Eigen::SparseMatrix<double>& TranSVD::get_vt() const
{
	return Vt;
}

TranSVD::TranSVD(int _max_sing, double _eign_thresh, const string &_name) : Transformation(_name)
{
	tran_svd_pack = new SVD_EIGEN(_max_sing, _eign_thresh);
}

TranSVD::TranSVD(const TranSVD &rhs)
	: Transformation(rhs), base_parameter_names(rhs.base_parameter_names),
	super_parameter_names(rhs.super_parameter_names),
	obs_names(rhs.obs_names),
	SqrtQ_J(rhs.SqrtQ_J),
	Sigma(rhs.Sigma),
	U(rhs.U),
	Vt(rhs.Vt),
	init_base_numeric_parameters(rhs.init_base_numeric_parameters),
	frozen_derivative_parameters(rhs.frozen_derivative_parameters)
{
	tran_svd_pack = rhs.tran_svd_pack->clone();
}

void TranSVD::set_SVD_pack_propack()
{
	int max_sing = tran_svd_pack->get_max_sing();
	double eigthresh = tran_svd_pack->get_eign_thres();
	delete tran_svd_pack;
	tran_svd_pack = new SVD_PROPACK(max_sing, eigthresh);
}

void TranSVD::set_performance_log(PerformanceLog *performance_log)
{
	tran_svd_pack->set_performance_log(performance_log);
}

void TranSVD::calc_svd()
{
	debug_msg("TranSVD::calc_svd begin");
	stringstream sup_name;
	VectorXd Sigma_trunc;
	tran_svd_pack->solve_ip(SqrtQ_J, Sigma, U, Vt, Sigma_trunc);
	// calculate the number of singluar values above the threshold

	debug_print(Sigma);
	debug_print(U);
	debug_print(Vt);
	debug_print(Sigma_trunc);

	int n_sing_val = Sigma.size();

	super_parameter_names.clear();
	for(int i=0; i<n_sing_val; ++i) {
		sup_name.str("");
		sup_name << "SUP_";
		sup_name << i+1;
		super_parameter_names.push_back(sup_name.str());
	}
	if (n_sing_val <= 0 )
	{
		throw PestError("TranSVD::update() - super parameter transformation returned 0 super parameters.  Jacobian must equal 0.");
	}
	debug_print(super_parameter_names);
	debug_msg("TranSVD::calc_svd end");
}

void TranSVD::update_reset_frozen_pars(const Jacobian &jacobian, const QSqrtMatrix &Q_sqrt, const Parameters &base_numeric_pars,
		int maxsing, double _eigthresh, const vector<string> &par_names, const vector<string> &_obs_names,
		const Parameters &_frozen_derivative_pars)
{
	debug_msg("TranSVD::update_reset_frozen_pars begin");
	debug_print(_frozen_derivative_pars);
	stringstream sup_name;
	super_parameter_names.clear();

	tran_svd_pack->set_max_sing(maxsing);
	tran_svd_pack->set_eign_thres(_eigthresh);
	obs_names = _obs_names;

	//these are where the derivative was computed so they can be different than the frozen values;
	init_base_numeric_parameters = base_numeric_pars;
	base_parameter_names = par_names;
	//remove frozen parameters from base_parameter_names
	auto end_iter = std::remove_if(base_parameter_names.begin(), base_parameter_names.end(),
		[&_frozen_derivative_pars](string &str)->bool{return _frozen_derivative_pars.find(str)!=_frozen_derivative_pars.end();});
	base_parameter_names.resize(std::distance(base_parameter_names.begin(), end_iter));
	frozen_derivative_parameters = _frozen_derivative_pars;
	//remove frozen derivatives from matrix parameter list
	std::remove_if(base_parameter_names.begin(), base_parameter_names.end(),
		[this](string &str)->bool{return this->frozen_derivative_parameters.find(str)!=this->frozen_derivative_parameters.end();});

	SqrtQ_J = Q_sqrt.get_sparse_matrix(obs_names, DynamicRegularization::get_unit_reg_instance()) * jacobian.get_matrix(obs_names, base_parameter_names);

	calc_svd();
	debug_print(this->base_parameter_names);
	debug_print(this->frozen_derivative_parameters);
	debug_msg("TranSVD::update_reset_frozen_pars end");
}

void TranSVD::update_add_frozen_pars(const Parameters &frozen_pars)
{
	debug_msg("TranSVD::update_reset_frozen_pars begin");
	debug_print(frozen_pars);
	vector<size_t> del_col_ids;
	Parameters new_frozen_pars;
	for (auto &ipar : frozen_pars)
	{
		auto iter = frozen_derivative_parameters.find(ipar.first);
		if (iter == frozen_derivative_parameters.end())
		{
			new_frozen_pars.insert(ipar);
		}
	}

	frozen_derivative_parameters.insert(new_frozen_pars);

	// build list of columns that needs to be removed from the matrix
	for (int i=0; i<base_parameter_names.size(); ++i)
	{
		if(new_frozen_pars.find(base_parameter_names[i]) != new_frozen_pars.end())
		{
			del_col_ids.push_back(i);
		}
	}

	if (del_col_ids.size() == base_parameter_names.size())
	{
		throw PestError("TranSVD::update_add_frozen_pars - All parameters are frozen in SVD transformation");
	}
	//remove frozen parameters from base_parameter_names
	auto end_iter = std::remove_if(base_parameter_names.begin(), base_parameter_names.end(),
		[&new_frozen_pars](string &str)->bool{return new_frozen_pars.find(str)!=new_frozen_pars.end();});
	base_parameter_names.resize(std::distance(base_parameter_names.begin(), end_iter));
	matrix_del_cols(SqrtQ_J, del_col_ids);
	calc_svd();
	debug_print(this->base_parameter_names);
	debug_print(this->frozen_derivative_parameters);
	debug_msg("TranSVD::update_reset_frozen_pars end");
}
void TranSVD::reverse(Transformable &data)
{
	// Transform super-parameters to base parameters
	assert(Vt.cols() == base_parameter_names.size());
	int n_base = Vt.cols();
	vector<double> super_par_vec = data.get_data_vec(super_parameter_names);
	vector<double>::iterator it;
	for (it=super_par_vec.begin(); it!=super_par_vec.end(); ++it)
	{
		(*it) -= 10.0;
	}
	Transformable ret_base_pars;
	int n_sing_val = Sigma.size();
	VectorXd delta_base_mat = Vt.block(0,0,n_sing_val, Vt.cols()).transpose() *  stlvec_2_egienvec(super_par_vec);
	for (int i=0; i<n_base; ++i) {
		ret_base_pars.insert(base_parameter_names[i], delta_base_mat(i) + init_base_numeric_parameters.get_rec(base_parameter_names[i]));
	}

	data = ret_base_pars;
}

void TranSVD::forward(Transformable &data)
{
	//Transform base parameters to super-parameters
	Transformable super_pars;
	VectorXd value;

	Transformable delta_data = init_base_numeric_parameters;
	delta_data *= 0.0;

	for (auto &it : data)
	{
		delta_data[it.first] = it.second - init_base_numeric_parameters.get_rec(it.first);
	}
	int n_sing_val = Sigma.size();
	value = Vt * delta_data.get_data_eigen_vec(base_parameter_names);
	for (int i=0; i<n_sing_val; ++i) {
		super_pars.insert(super_parameter_names[i], value(i)+10.0);
	}
	data = super_pars;
}

void TranSVD::jacobian_forward(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	Eigen::SparseMatrix<double> old_matrix = jac.get_matrix(jac.observation_list(), base_parameter_names);
	Eigen::SparseMatrix<double> super_jacobian;
	super_jacobian = old_matrix * Vt.transpose();
	jac.matrix = super_jacobian;
	jac.base_numeric_par_names = super_parameter_names;

	forward(data);
}

void TranSVD::jacobian_reverse(Jacobian &jac)
{
	Transformable &data = jac.base_numeric_parameters;
	Eigen::SparseMatrix<double> old_matrix = jac.get_matrix(jac.observation_list(), super_parameter_names);
	Eigen::SparseMatrix<double> base_jacobian;
	base_jacobian = old_matrix * Vt;
	jac.matrix = base_jacobian;
	jac.base_numeric_par_names = base_parameter_names;
	reverse(data);
}

void TranSVD::d1_to_d2(Transformable &del_data, Transformable &data)
{
	Transformable new_data;
	VectorXd d1_vec = del_data.get_partial_data_eigen_vec(base_parameter_names);
	VectorXd d2_vec = Vt * d1_vec;
	for (int i = 0; i<Sigma.size(); ++i) {
		new_data[super_parameter_names[i]] = d2_vec[i];
	}
	del_data = new_data;
	forward(data);
}

void TranSVD::d2_to_d1(Transformable &del_data, Transformable &data)
{
	Transformable new_data;
	VectorXd d2_vec = del_data.get_partial_data_eigen_vec(super_parameter_names);
	VectorXd d1_vec = Vt.transpose() * d2_vec;
	for (size_t i = 0; i < base_parameter_names.size(); ++i)
	{
		new_data[base_parameter_names[i]] = d1_vec[i];
	}
	del_data = new_data;
	reverse(data);
}

void TranSVD::save(ostream &fout) const
{
	size_t size;
	vector<int8_t> serial_data;
	serial_data = Serialization::serialize(base_parameter_names);
	size = serial_data.size();
	fout.write((char*)&size, sizeof(size));
	fout.write((char*)serial_data.data(), size);

	serial_data = Serialization::serialize(super_parameter_names);
	size = serial_data.size();
	fout.write((char*)&size, sizeof(size));
	fout.write((char*)serial_data.data(), size);

	serial_data = Serialization::serialize(obs_names);
	size = serial_data.size();
	fout.write((char*)&size, sizeof(size));
	fout.write((char*)serial_data.data(), size);

	save_triplets_bin(SqrtQ_J, fout);
	save_vector_bin(Sigma, fout);
	save_triplets_bin(U, fout);
	save_triplets_bin(Vt, fout);

	serial_data = Serialization::serialize(init_base_numeric_parameters);
	size = serial_data.size();
	fout.write((char*)&size, sizeof(size));
	fout.write((char*)serial_data.data(), size);

	serial_data = Serialization::serialize(frozen_derivative_parameters);
	size = serial_data.size();
	fout.write((char*)&size, sizeof(size));
	fout.write((char*)serial_data.data(), size);
}

void TranSVD::read(istream &fin)
{
	size_t size;
	vector<int8_t> serial_data;

	base_parameter_names.clear();
	fin.read((char*)&size, sizeof(size));
	serial_data.clear();
	serial_data.resize(size);
	fin.read((char*)serial_data.data(), size);
	Serialization::unserialize(serial_data, base_parameter_names);

	super_parameter_names.clear();
	fin.read((char*)&size, sizeof(size));
	serial_data.clear();
	serial_data.resize(size);
	fin.read((char*)serial_data.data(), size);
	Serialization::unserialize(serial_data, super_parameter_names);

	obs_names.clear();
	fin.read((char*)&size, sizeof(size));
	serial_data.clear();
	serial_data.resize(size);
	fin.read((char*)serial_data.data(), size);
	Serialization::unserialize(serial_data, obs_names);

	load_triplets_bin(SqrtQ_J, fin);
	load_vector_bin(Sigma, fin);
	load_triplets_bin(U, fin);
	load_triplets_bin(Vt, fin);

	init_base_numeric_parameters.clear();
	fin.read((char*)&size, sizeof(size));
	serial_data.clear();
	serial_data.resize(size);
	fin.read((char*)serial_data.data(), size);
	Serialization::unserialize(serial_data, init_base_numeric_parameters);

	frozen_derivative_parameters.clear();
	fin.read((char*)&size, sizeof(size));
	serial_data.clear();
	serial_data.resize(size);
	fin.read((char*)serial_data.data(), size);
	Serialization::unserialize(serial_data, frozen_derivative_parameters);
}

ParameterGroupInfo TranSVD::build_par_group_info(const ParameterGroupInfo &base_pg_info)
{
	double derinc_sup;
	double derinc_par;
	int max_col;
	double max_val;
	ParameterGroupInfo pg_info;
	stringstream grp_name;
	for (int i_sup=0, n_sup=super_parameter_names.size(); i_sup < n_sup; ++i_sup)
	{
		get_MatrixXd_row_abs_max(Vt, i_sup, &max_col, &max_val);
		derinc_par = base_pg_info.get_group_rec_ptr(base_parameter_names[max_col])->derinc;
		derinc_sup = .01;
		grp_name.str("");
		grp_name << "g_" << super_parameter_names[i_sup];
		ParameterGroupRec sup_rec(grp_name.str(), "ABSOLUTE", derinc_sup, 0.0, "SWITCH", 2.0, "PARABOLIC");
		//add new group
		pg_info.insert_group(grp_name.str(), sup_rec);
		// connect super parameter to new group
		pg_info.insert_parameter_link(super_parameter_names[i_sup], grp_name.str());
	}
	return pg_info;
}

Parameters TranSVD::map_basepar_to_super(const Parameters &base_pars)
{
	Parameters super_pars;
	VectorXd base_par_vec = base_pars.get_partial_data_eigen_vec(base_parameter_names);
	VectorXd super_par_vec = Vt * base_par_vec;
	for (size_t i=0; i<super_parameter_names.size(); ++i)
	{
		super_pars[super_parameter_names[i]] = super_par_vec(i);
	}
	return super_pars;
}

TranSVD::~TranSVD()
{
	delete tran_svd_pack;
}

void TranSVD::print(ostream &os) const
{
	os << "Transformation name = " << name << "; (type=TranSVD)" << endl;
	os << "  Singular Values = " << Sigma << endl;
}

//void TranNormalize::forward(Transformable &data)
//{
//	Transformable::iterator data_iter, data_end = data.end();
//	for (map<string,NormData>::const_iterator b=items.begin(), e=items.end();
//		b!=e; ++b) {
//		data_iter = data.find(b->first);
//		if (data_iter != data_end)
//		{
//			(*data_iter).second += b->second.offset;
//			(*data_iter).second *= b->second.scale;
//		}
//	}
//}
//
//
//void TranNormalize::reverse(Transformable &data)
//{
//	Transformable::iterator data_iter, data_end = data.end();
//	for (map<string,NormData>::const_iterator b=items.begin(), e=items.end();
//		b!=e; ++b) {
//		data_iter = data.find(b->first);
//		if (data_iter != data_end)
//		{
//			(*data_iter).second /= b->second.scale;
//			(*data_iter).second -= b->second.offset;
//		}
//	}
//}
//
//
//void TranNormalize::jacobian_forward(Jacobian &jac)
//{
//	size_t icol = 0;
//	double factor = 0;
//	Transformable &data = jac.base_numeric_parameters;
//	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
//	auto iter_end = par_2_col_map.end();
//	for (const auto &irec : items)
//	{
//		auto iter = par_2_col_map.find(irec.first);
//		if (iter != iter_end)
//		{
//			icol = iter->second;
//			factor = irec.second.scale;
//			jac.matrix.col(icol) /= factor;
//		}
//	}
//	forward(data);
//}
//
//void TranNormalize::jacobian_reverse(Jacobian &jac)
//{
//	size_t icol = 0;
//	double factor = 0;
//	Transformable &data = jac.base_numeric_parameters;
//	unordered_map<string, int> par_2_col_map = jac.get_par2col_map();
//	auto iter_end = par_2_col_map.end();
//	for (const auto &irec : items)
//	{
//		auto iter = par_2_col_map.find(irec.first);
//		if (iter != iter_end)
//		{
//			icol = iter->second;
//			factor = irec.second.scale;
//			jac.matrix.col(icol) *= factor;
//		}
//	}
//	reverse(data);
//}
//
//void TranNormalize::d1_to_d2(Transformable &del_data, Transformable &data)
//{
//	Transformable::iterator del_data_iter, del_data_end = del_data.end();
//	for (map<string, NormData>::const_iterator b = items.begin(), e = items.end();
//		b != e; ++b) {
//		del_data_iter = data.find(b->first);
//		if (del_data_iter != del_data_end)
//		{
//			(*del_data_iter).second /= b->second.scale;
//		}
//	}
//	forward(data);
//}
//
//void TranNormalize::d2_to_d1(Transformable &del_data, Transformable &data)
//{
//	Transformable::iterator del_data_iter, del_data_end = del_data.end();
//	for (map<string, NormData>::const_iterator b = items.begin(), e = items.end();
//		b != e; ++b) {
//		del_data_iter = del_data.find(b->first);
//		if (del_data_iter != del_data_end)
//		{
//			(*del_data_iter).second *= b->second.scale;
//		}
//	}
//	reverse(data);
//}
//
//void TranNormalize::insert(const string &item_name, double _offset, double _scale)
//{
//	items[item_name] = NormData(_offset, _scale);
//}
//
//void TranNormalize::print(ostream &os) const
//{
//	os << "Transformation name = " << name << "; (type=TranNormalize)" << endl;
//	for (map<string,NormData>::const_iterator b=items.begin(), e=items.end();
//		b!=e; ++b) {
//			os << "  item name = " << (*b).first << ";  scale value = " << (*b).second.scale
//				<< ";  offset value = " << (*b).second.offset <<endl;
//	}
//}


/*
 * added by Shufang ZHU on Jan 10th, 2017
 * translate ltlf formulas to fol, the input of MONA
*/

#include "ltlf2fol.h"
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <set>
#include <assert.h>
#include <iostream>
#include <fstream>
#include <boost/algorithm/string.hpp>

using namespace std;
#define MAXN 1000000

void ltlf2fol (ltl_formula *root)
{
  int c = 1;
  string res;

  set<string> P = get_alphabet (root);
  if(!P.empty()){
    // cout<<"var2 ALIVE, ";
    cout<<"m2l-str;"<<endl;
    cout<<"var2 ";
    print_alphabet_no_comma(root);
    cout<<";"<<endl;
    // cout<<"allpos ALIVE;"<<endl;
    // cout<<"0 in ALIVE;"<<endl;
  }

  res = trans_fol(root, 0, c);
  cout<<res<<";"<<endl;

}

void print_alphabet_no_comma (ltl_formula* root){
  set<string> P = get_alphabet (root);
  if(!P.empty()){
    set<string>::iterator it = P.begin ();
    // cout<<toupper(*it);
    cout<<up(*it);
    it++;
    while (it != P.end ()){
      cout<<", "<<up(*it);
      it++;
    }
  }
}

string alphabet_no_comma (ltl_formula* root){
  string res = "";
  set<string> P = get_alphabet (root);
  if(!P.empty()){
    set<string>::iterator it = P.begin ();
    // cout<<toupper(*it);
    res += up(*it);
    it++;
    while (it != P.end ()){
      res += ", "+up(*it);
      it++;
    }
  }
  return res;
}

void print_alphabet (ltl_formula* root){
  set<string> P = get_alphabet (root);
  if(!P.empty()){
    set<string>::iterator it = P.begin ();
    // cout<<toupper(*it);
    cout<<", "<<up(*it);
    it++;
    while (it != P.end ()){
      cout<<", "<<up(*it);
      it++;
    }
  }
}

void print_alphabet_not (ltl_formula* root){
  set<string> P = get_alphabet (root);
  if(!P.empty()){
    set<string>::iterator it = P.begin ();
    // cout<<toupper(*it);
    cout<<", "<<up(*it)<<"\\{p}";
    it++;
    while (it != P.end ()){
      cout<<", "<<up(*it);
      it++;
    }
  }
}

void printvars (ltl_formula* root){
  set<string> P = get_alphabet (root);
  if(!P.empty()){
    set<string>::iterator it = P.begin ();
    // cout<<toupper(*it);
    cout<<", var2 "<<up(*it);
    it++;
    while (it != P.end ()){
      cout<<", var2 "<<up(*it);
      it++;
    }
  }
}

string trans_fol(ltl_formula* root, int t, int& c){
  string curs, ts;
  string exs, alls;
  string res;
  int cur;
  switch(root->_type)
  {
        case eNOT:
          res = "~(";
          res += trans_fol(root->_right, t, c);
          res += ")";
          break;
        case eNEXT:
          exs = "x"+to_string(t+1);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "(ex1 "+exs+": ("+exs+"="+ts+"+1 & (";
          res += trans_fol(root->_right, t+1, c);
          res += ")))";
          break;
        case eWNEXT:
          exs = "x"+to_string(t+1);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "((ex1 "+exs+": ("+exs+"="+ts+"+1 & (";
          res += trans_fol(root->_right, t+1, c);
          res += "))) | ("+ts+" = max $))";
          break;
        case eFUTURE:
          exs = "x"+to_string(t+1);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "(ex1 "+exs+": ("+ts+" <= "+exs+" & (";
          res += trans_fol(root->_right, t+1, c);
          res += ")))";
          break;
        case eGLOBALLY:
          alls = "x"+to_string(t+1);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "(all1 "+alls+": (("+ts+" <= "+alls+") => (";
          res += trans_fol(root->_right, t+1, c);
          res += ")))";
          break;
        case eUNTIL:
          exs = "x"+to_string(t+1);
          alls = "x"+to_string(t+2);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "(ex1 "+exs+": ("+ts+" <= "+exs+" & (";
          res += trans_fol(root->_right, t+1, c);
          res += ") & (all1 "+alls+": ("+ts+" <= "+alls+" & "+alls;
          res += " < "+exs+" => (";
          res += trans_fol(root->_left, t+2, c);
          res += ")))))";
          break;
        case eRELEASE: //New
          exs = "x"+to_string(t+1);
          alls = "x"+to_string(t+2);
          if (t == 0)
            ts = to_string(t);
          else
            ts = "x"+to_string(t);
          res = "((ex1 "+exs+": ("+ts+" <= "+exs+" & (";
          res += trans_fol(root->_left, t+1, c);
          res += ") & (all1 "+alls+": ("+ts+" <= "+alls+" & "+alls;
          res += " <= "+exs+" => (";
          res += trans_fol(root->_right, t+2, c);
          res += ")))))";
          res += "| (all1 "+alls+": (("+ts+" <= "+alls+" & "+alls+" <= max $) => (";
          res += trans_fol(root->_right, t+2, c);
          res += "))))";
          break;
        case eOR:
          res += "(("+trans_fol(root->_right, t, c);
          res += ") | (";
          res += trans_fol(root->_left, t, c)+"))";
          break;
        case eAND:
          res += "(("+trans_fol(root->_right, t, c);
          res += ") & (";
          res += trans_fol(root->_left, t, c)+"))";
          break;
        case eTRUE:
          res += "(true)";
          break;
        case eFALSE:
          res += "(false)";
          break;
        case 3:
          if (t == 0)
            ts = "("+to_string(t);
          else
            ts = "(x"+to_string(t);
          res += ts+" in ";
          res += alphabet_no_comma(root);
          res +=")";
          break;
        default:
          break;
  }
  // cout<<res<<endl;
  return res;
}

string up(string a){
  return boost::to_upper_copy<std::string>(a);
}

char in[MAXN];

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

		string StrLine;
		std::string input;
    std::string format;
		if(argc != 3){
        cout<<"Usage: ./ltlf2fol format(NNF, BNF) filename"<<endl;
        return 0;
    }
		input = argv[2];
    format = argv[1];
		ifstream myfile(input);
		if (!myfile.is_open()) //判断文件是否存在及可读
		{
		    printf("unreadable file!");
		    return -1;
		}
		getline(myfile, StrLine);
		myfile.close(); //关闭文件
    strcpy (in, StrLine.c_str());
    printf ("#%s\n", in);

    ltl_formula *root = getAST (in);
    ltl_formula *bnfroot = bnf (root);
    ltl_formula *newroot;
    printf ("#%s\n", to_string (bnfroot).c_str ());
    if(format == "NNF"){
      printf ("#NNF format\n");
      newroot = nnf (bnfroot) ;
    }
    else{
      printf ("#BNF format\n");
      newroot = bnfroot;
    }

    printf ("#%s\n", to_string (newroot).c_str ());
    ltlf2fol (newroot);

    // printf ("%s\n", res.c_str ());
    destroy_formula (root);
    destroy_formula (newroot);
    //destroy_formula (nnfroot);
}


/*
The MIT License

Copyright (c) 2015-2016 Albert Murienne

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

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

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

#include "network_bnu_base.h"

#include "common/network_config.h"
#include "common/network_exception.h"
#include "common/network_random.h"

#include <boost/optional.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>

namespace bnu = boost::numeric::ublas;

namespace neurocl { namespace mlp {

const std::string dump_mat( const matrixF& mat, boost::optional<std::string> label = boost::none )
{
    std::string separator;
    std::stringstream ss;
    ss << ( label ? label.get() : "" ) << std::endl;
    for( matrixF::const_iterator1 it1 = mat.cbegin1(); it1 != mat.cend1(); ++it1 )
    {
        for( matrixF::const_iterator2 it2 = it1.begin(); it2 !=it1.end(); ++it2 )
        {
            ss << separator << *it2;
            separator = " ";
        }
        separator = "";
        ss << std::endl;
    }
    return ss.str();
}

const std::string dump_vec( const vectorF& vec, boost::optional<std::string> label = boost::none )
{
    std::string separator;
    std::stringstream ss;
    ss << ( label ? label.get() : "" ) << std::endl;
    for( auto val : vec )
    {
            ss << separator << val;
            separator = " ";
    }
    ss << std::endl;
    return ss.str();
}

template<class T>
void random_normal_init( T& container, const float stddev = 1.f )
{
    random::rand_gaussian_generator rgg( 0.f, stddev );

    for( auto& element : container.data() )
    {
        element = rgg();
    }
}

layer_bnu::layer_bnu()
{
}

// WARNING : size is the square side size
void layer_bnu::populate( const layer_size& cur_layer_size, const layer_size& next_layer_size )
{
    //LOGGER(info) << "layer_bnu::populate - populating layer of size " << cur_layer_size << " (next size is " << next_layer_size << ")" << std::endl;

    if ( next_layer_size.size() ) // non-output layer
    {
        m_output_weights = matrixF( next_layer_size.size(), cur_layer_size.size() );
        // cf. http://neuralnetworksanddeeplearning.com/chap3.html#weight_initialization
        random_normal_init( m_output_weights, 1.f / std::sqrt( cur_layer_size.size() ) );
        m_deltas_weight = matrixF( next_layer_size.size(), cur_layer_size.size() );
        m_deltas_weight.clear();

        m_bias = vectorF( next_layer_size.size() );
        random_normal_init( m_bias, 1.f );
        m_deltas_bias = vectorF( next_layer_size.size() );
        m_deltas_bias.clear();
    }

    m_activations = vectorF( cur_layer_size.size() );
    m_activations.clear();
    m_errors = vectorF( cur_layer_size.size() ); // not needed for input layer...?
    m_errors.clear();
}

const std::string layer_bnu::dump_weights() const
{
    return dump_mat( m_output_weights );
}

const std::string layer_bnu::dump_bias() const
{
    return dump_vec( m_bias );
}

const std::string layer_bnu::dump_activations() const
{
    return dump_vec( m_activations );
}

network_bnu_base::network_bnu_base() : m_training_samples( 0 ), m_learning_rate( 3.0f/*0.01f*/ ), m_weight_decay( 0.0f )
{
    const network_config& nc = network_config::instance();
    nc.update_optional( "learning_rate", m_learning_rate );
}

void network_bnu_base::set_input(  const size_t& in_size, const float* in )
{
    if ( in_size > m_layers[0].activations().size() )
        throw network_exception( "sample size exceeds allocated layer size!" );

    //LOGGER(info) << "network_bnu::set_input - input (" << in << ") size = " << in_size << std::endl;

    vectorF& input_activations = m_layers[0].activations();
    std::copy( in, in + in_size, input_activations.begin() );
}

void network_bnu_base::set_output( const size_t& out_size, const float* out )
{
    if ( out_size > m_training_output.size() )
        throw network_exception( "output size exceeds allocated layer size!" );

    //LOGGER(info) << "network_bnu::set_output - output (" << out << ") size = " << out_size << std::endl;

    std::copy( out, out + out_size, m_training_output.begin() );
}

void network_bnu_base::add_layers_2d( const std::vector<layer_size>& layer_sizes )
{
    m_layers.resize( layer_sizes.size() );

    // Last layer should be output layer
    const layer_size& _last_size = layer_sizes.back();
    m_layers.back().populate( _last_size, layer_size( 0, 0 ) );

    // Initialize training output
    m_training_output = vectorF( _last_size.size() );

    // Populate all but input layer
    for ( int idx=layer_sizes.size()-2; idx>=0; idx-- )
    {
        const layer_size& _size = layer_sizes[idx];
        const layer_size& _next_layer_size = layer_sizes[idx+1];
        m_layers[idx].populate( _size, _next_layer_size );
    }
}

const layer_ptr network_bnu_base::get_layer_ptr( const size_t layer_idx )
{
    if ( layer_idx >= m_layers.size() )
    {
        LOGGER(error) << "network_bnu_base::get_layer_ptr - cannot access layer " << layer_idx << std::endl;
        throw network_exception( "invalid layer index" );
    }

    matrixF& weights = m_layers[layer_idx].weights();
    vectorF& bias = m_layers[layer_idx].bias();
    layer_ptr l( weights.size1() * weights.size2(), bias.size() );
    std::copy( &weights.data()[0], &weights.data()[0] + ( weights.size1() * weights.size2() ), l.weights.get() );
    std::copy( &bias[0], &bias[0] + bias.size(), l.bias.get() );

    return l;
}

void network_bnu_base::set_layer_ptr( const size_t layer_idx, const layer_ptr& layer )
{
    if ( layer_idx >= m_layers.size() )
    {
        LOGGER(error) << "network_bnu_base::set_layer_ptr - cannot access layer " << layer_idx << std::endl;
        throw network_exception( "invalid layer index" );
    }

    LOGGER(info) << "network_bnu_base::set_layer_ptr - setting layer  " << layer_idx << std::endl;

    matrixF& weights = m_layers[layer_idx].weights();
    std::copy( layer.weights.get(), layer.weights.get() + layer.num_weights, &weights.data()[0] );
    vectorF& bias = m_layers[layer_idx].bias();
    std::copy( layer.bias.get(), layer.bias.get() + layer.num_bias, &bias.data()[0] );
}

const output_ptr network_bnu_base::output()
{
    vectorF& output = m_layers.back().activations();
    output_ptr o( output.size() );
    std::copy( &output[0], &output[0] + output.size(), o.outputs.get() );

    return o;
}

void network_bnu_base::clear_gradients()
{
    // Clear gradients
    for ( size_t i=0; i<m_layers.size()-1; i++ )
    {
        m_layers[i].w_deltas().clear();
        m_layers[i].b_deltas().clear();
    }

    m_training_samples = 0;
}

float network_bnu_base::loss()
{
    vectorF& output = m_layers.back().activations();
    vectorF loss = bnu::element_prod( output - m_training_output, output - m_training_output );

    float _acc = 0.f;
    std::for_each( loss.begin(), loss.end(),
        [&_acc] ( float a ) {
            _acc += a;
        });

    return 0.5f * _acc / static_cast<float>( loss.size() );
}

const std::string network_bnu_base::dump_weights()
{
    std::stringstream ss;
    ss << "*************************************************" << std::endl;
    for( const auto& layer : m_layers )
    {
        ss << layer.dump_weights();
        ss << "-------------------------------------------------" << std::endl;
    }
    ss << "*************************************************" << std::endl;
    return ss.str();
}

const std::string network_bnu_base::dump_bias()
{
    std::stringstream ss;
    ss << "*************************************************" << std::endl;
    for( const auto& layer : m_layers )
    {
        ss << layer.dump_bias();
        ss << "-------------------------------------------------" << std::endl;
    }
    ss << "*************************************************" << std::endl;
    return ss.str();
}

const std::string network_bnu_base::dump_activations()
{
    std::stringstream ss;
    ss << "*************************************************" << std::endl;
    for( const auto& layer : m_layers )
    {
        ss << layer.dump_activations();
        ss << "-------------------------------------------------" << std::endl;
    }
    ss << "*************************************************" << std::endl;
    return ss.str();
}

void network_bnu_base::gradient_check( const output_ptr& out_ref )
{
    LOGGER(error) << "network_bnu_base::gradient_check - not implemented yet for MLP" << std::endl;
}

} /*namespace neurocl*/ } /*namespace mlp*/