/*
********************************************************************************
MIT License

Copyright(c) 2018 Christopher Brandt

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 <iostream>
#include <math.h>

#include "ProjDynRHSInterpol.h"
#include "ProjDynConstraints.h"
#include "ProjDynSimulator.h"
#include "ProjUtils/ProjDynUtil.h"
#include <Eigen/Eigenvalues>

PDMatrix PD::RHSInterpolationGroup::snapshotPCA(PDMatrix& Y, PDVector& masses, unsigned int size)
{
	/* PCA via the method of snapshots*/

	std::cout << "Performing snapshot PCA..." << std::endl;

	PDMatrix base(Y.rows(), size + 1);

	std::cout << "	Removing column wise mean..." << std::endl;

	// Remove column wise mean
	PROJ_DYN_PARALLEL_FOR
		for (int j = 0; j < Y.cols(); j++) {
			PDScalar mean = Y.col(j).mean();
			for (unsigned int i = 0; i < Y.rows(); i++) {
				Y(i, j) -= mean;
			}
		}

	// We solve the SVD by solving an eigenvalue problem on the smaller matrix A = Y^T M Y
	// and converting the eigenvectors to that problem back to v = Y u
	std::cout << "	Computing Y^T M Y..." << std::endl;
	PDMatrix A;
#ifdef PROJ_DYN_USE_CUBLAS_IN_PRE
	std::cout << "		uploading Y to GPU..." << std::endl;
	CUDAMatrixVectorMultiplier* yMulti = new CUDAMatrixVectorMultiplier(Y, masses);
	std::cout << "		computing Y^T (M Y) on GPU..." << std::endl;
	A.resize(Y.cols(), Y.cols());
	for (unsigned int i = 0; i < Y.cols(); i++) {
		PDScalar one = 1;
		yMulti->mult(Y.data() + (i * Y.rows()), A.data() + (i * A.rows()), one, true);
	}
#else
	PDMatrix Y_T = Y.transpose();
	PROJ_DYN_PARALLEL_FOR
		for (int j = 0; j < Y.cols(); j++) {
			Y_T.col(j) *= masses(j);
		}
	A.resize(Y.cols(), Y.cols());
	PROJ_DYN_PARALLEL_FOR
		for (int j = 0; j < Y.cols(); j++) {
			A.col(j) = Y_T * Y.col(j);
		}
#endif
	std::cout << "	Computing eigenvectors v of Y^T M Y..." << std::endl;
	Eigen::EigenSolver< PDMatrix > eSolver;
	eSolver.compute(A, true);
	Eigen::Matrix< std::complex< double >, -1, -1> eVecsC = eSolver.eigenvectors();
	Eigen::Matrix< std::complex< double >, -1, 1> eValsC = eSolver.eigenvalues();
	std::cout << "	Extracting PCA vectors as Y v..." << std::endl;
	PDScalar currentEVal = -1;
	std::vector<unsigned int> eVecInds;
	for (int v = 0; v < size; v++) {
		PDScalar currentLargest = 0;
		int indOfLargest = -1;
		for (int j = 0; j < eValsC.rows(); j++) {
			if (eValsC(j).real() > currentLargest && (currentEVal < 0 || eValsC(j).real() < currentEVal)) {
				currentLargest = eValsC(j).real();
				indOfLargest = v;
			}
		}
		if (indOfLargest >= 0) {
			eVecInds.push_back(indOfLargest);
			currentEVal = eValsC(indOfLargest).real();
			//std::cout << eValsC(indOfLargest).real() << std::endl;
		}
		else {
			std::cout << "Could only find " << (v - 1) << " eigenvectors with strictly positive eigenvalues during snapshot PCA ..." << std::endl;
			base.conservativeResize(base.rows(), std::max(1, v - 10));
			break;
		}
	}

#ifdef PROJ_DYN_USE_CUBLAS_IN_PRE
	for (int v = 0; v < std::min((int)base.cols(), (int)eVecInds.size()); v++) {
		PDScalar weight = (1. / std::sqrt(eValsC(eVecInds[v]).real()));
		PDVector eVec = eVecsC.col(eVecInds[v]).real();
		yMulti->mult(eVec.data(), base.data() + (v * base.rows()), weight, false);
	}
	delete yMulti;
#else
	PROJ_DYN_PARALLEL_FOR
		for (int v = 0; v < std::min((int)base.cols(), (int)eVecInds.size()); v++) {
			PDVector eVec = eVecsC.col(eVecInds[v]).real();
			base.col(v) = Y * eVec;
			PDScalar weight = (1. / std::sqrt(eValsC(eVecInds[v]).real()));
			base.col(v) *= weight;
		}
#endif

	if (base.hasNaN()) {
		std::cout << "Warning: base contains NaN values!" << std::endl;
	}

	// Make sure to include global translations since we removed column wise mean
	base.col(base.cols() - 1).setConstant(1.);

	std::cout << "	Done." << std::endl;

	return base;
}

PD::RHSInterpolationGroup::RHSInterpolationGroup(std::string groupName, std::vector<ProjDynConstraint*>& constraints, PDPositions& restPositions,
	PDVector& vertexMasses, PDTriangles& tris, PDTets& tets, PDScalar regularizationWeight)
	:
	m_auxiliarySize(0),
	m_basis(0, 0),
	m_interpolReady(false),
	m_basisReady(false),
	m_groupName(groupName),
	m_regularizationWeight(regularizationWeight),
	m_restPositionFull(restPositions)
{
	for (auto c : constraints) {
		//ProjDynConstraint* cc = c->copy();
		m_constraints.push_back(c);
	}

	m_isTetExampleGroup = (m_groupName == "tetex");

	if (m_constraints.size() >= 1) {
		int dummy = -1;
		m_auxiliarySize = m_constraints.at(0)->getP(restPositions, dummy).rows();

		// Compute the mass matrix for the unassembled auxiliaries
		computeMassMatrix(vertexMasses, tris, tets);
	}
}

PD::RHSInterpolationGroup::RHSInterpolationGroup(std::string groupName, unsigned int auxiliarySize)
	:
	m_groupName(groupName),
	m_auxiliarySize(auxiliarySize),
	m_basis(0, 0),
	m_selectionMatrix(0, 0),
	m_interpolReady(false),
	m_basisReady(false),
	m_regularizationWeight(0.),
	m_restPositionFull(0, 3)
{
}

void PD::RHSInterpolationGroup::computeMassMatrix(PDVector& vertexMasses, PDTriangles& tris, PDTets& tets) {
	// Compute the mass matrix for the unassembled auxiliaries
	m_massMatrix.resize(m_constraints.size() * m_auxiliarySize, m_constraints.size() * m_auxiliarySize);
	m_massMatrixDiag.resize(m_constraints.size() * m_auxiliarySize);
	int i = 0;
	for (auto& c : m_constraints) {
		double weight = 1.;
		if (!NO_MASSES_IN_PCA) {
			if (c->getMainVertexIndex() > 0) {
				weight = vertexMasses(c->getMainVertexIndex());
			}
			else if (c->getMainTriangleIndex() > 0) {
				weight = 0;
				for (int j = 0; j < 3; j++) {
					weight += vertexMasses(tris(c->getMainTriangleIndex(), j));
				}
			}
			else if (c->getMainTetIndex() > 0) {
				weight = 0;
				for (int j = 0; j < 4; j++) {
					weight += vertexMasses(tets(c->getMainTetIndex(), j));
				}
			}
		}
		for (int j = 0; j < m_auxiliarySize; j++) {
			m_massMatrix.insert(i * m_auxiliarySize + j, i * m_auxiliarySize + j) = weight;
			m_massMatrixDiag(i * m_auxiliarySize + j) = weight;
		}
		i++;
	}
}

void PD::RHSInterpolationGroup::createBasisViaSkinningWeights(unsigned int size, PDPositions& restPos, PDMatrix& skinningWeights, bool usePCA,
	PDTriangles& tris, PDTets &tets)
{
	std::cout << "Creating interpolation basis via skinning construction for " << getName() << "... " << std::endl;
	std::cout << "	Computing rest state auxiliary variables..." << std::endl;
	PDPositions restStateAuxils = PD::getAssemblyMatrix(m_constraints, true, NO_WEIGHTS_IN_CONSTRUCTION).transpose() * restPos;
	std::cout << "	Computing skinning space..." << std::endl;
	PDMatrix Y = PD::createSkinningSpace(restStateAuxils, skinningWeights, &m_constraints, m_auxiliarySize, &tris, &tets);

	if (!usePCA) {
		m_basis = Y;
	}
	else {
		m_basis = RHSInterpolationGroup::snapshotPCA(Y, m_massMatrixDiag, size);
	}
	m_basisReady = true;
	m_interpolReady = false;
}

void PD::RHSInterpolationGroup::setBasis(PDMatrix & base)
{
	m_basis = base;

	m_basisReady = true;
	m_interpolReady = false;

}

void PD::RHSInterpolationGroup::initInterpolation(unsigned int numVertices, std::vector<unsigned int>& sampledElements,
	PDSparseMatrix const& positionSubspaceT)
{
	std::cout << "Finalizing rhs interpolation for " << getName() << "... " << std::endl;
	std::cout << "	Computing assembly, selection and weight matrix..." << std::endl;

	std::vector<unsigned int> sampledElsCopy = sampledElements;

	// Create assembly matrix
	m_assemblyMatrix = PD::getAssemblyMatrix(m_constraints, true, NO_WEIGHTS_IN_CONSTRUCTION);

	// Create selection and weights matrix
	std::vector<Eigen::Triplet<PDScalar>> selectedInds;
	m_weightMatrix.resize(m_constraints.size() * m_auxiliarySize, m_constraints.size() * m_auxiliarySize);
	PDVector weightVector(m_constraints.size() * m_auxiliarySize);
	weightVector.setConstant(1.);
	for (unsigned int i = 0; i < m_constraints.size(); i++) {
		ProjDynConstraint* c = m_constraints[i];
		unsigned int constraintElement = -1;
		if (c->getMainVertexIndex() >= 0) {
			constraintElement = c->getMainVertexIndex();
		}
		else if (c->getMainTriangleIndex() >= 0) {
			constraintElement = c->getMainTriangleIndex();
		}
		else if (c->getMainTetIndex() >= 0) {
			constraintElement = c->getMainTetIndex();
		}
		auto const& it = std::find(sampledElsCopy.begin(), sampledElsCopy.end(), constraintElement);
		if (m_sampledConstraints.size() < sampledElements.size() && it != sampledElsCopy.end()) {
			sampledElsCopy.erase(it);
			m_sampledConstraints.push_back(c->copy());
			int ind = m_sampledConstraints.size() - 1;
			for (int d = 0; d < m_auxiliarySize; d++) {
				selectedInds.push_back(Eigen::Triplet<PDScalar>(ind * m_auxiliarySize + d, i * m_auxiliarySize + d, 1.));
			}
		}
		for (int j = 0; j < m_auxiliarySize; j++) {
			if (NO_WEIGHTS_IN_CONSTRUCTION) {
				m_weightMatrix.insert(i * m_auxiliarySize + j, i * m_auxiliarySize + j) = c->getWeight();
				weightVector(i * m_auxiliarySize + j) = c->getWeight();
			}
			else {
				// Weights are included in the interpolation problem and snapshot basis
				m_weightMatrix.insert(i * m_auxiliarySize + j, i * m_auxiliarySize + j) = 1.;
			}
		}
	}
	m_selectionMatrix = PDSparseMatrix(sampledElements.size() * m_auxiliarySize, m_constraints.size() * m_auxiliarySize);
	m_selectionMatrix.setZero();
	m_selectionMatrix.setFromTriplets(selectedInds.begin(), selectedInds.end());
	// If we couldn't find as many constraint samples as we have sampled
	// elements, we overestimated the number of rows in the selection matrix
	// and need to cut off the rest.
	if (m_sampledConstraints.size() < sampledElements.size()) {
		m_selectionMatrix.conservativeResize(m_sampledConstraints.size() * m_auxiliarySize, m_selectionMatrix.cols());
	}
	if (m_selectionMatrix.rows() < m_basis.cols() - MIN_OVERSAMPLING) {
		std::cout << "Warning: not enough sampled constraints (" << m_sampledConstraints.size()
			<< ") for this rhs interpolation base (" << m_basis.cols() << ")." << std::endl;
		std::cout << "	Cutting off last vectors in base." << std::endl;
		m_basis.conservativeResize(m_basis.rows(), m_selectionMatrix.rows() - MIN_OVERSAMPLING);
	}

	// Compute lhs and rhs matrices and solver for the inteprolation problem
	std::cout << "	Computing lhs and rhs matrices for linear interpolation system and factorizing..." << std::endl;
	m_interpolRHSMatrix = m_basis.transpose() * m_selectionMatrix.transpose();
	PDMatrix lhsMat;
	if (m_regularizationWeight > 0) {
		lhsMat = m_basis.transpose() * ((m_selectionMatrix.transpose() * m_selectionMatrix) + m_regularizationWeight * m_massMatrix) * m_basis;
	}
	else {
		lhsMat = m_basis.transpose() * ((m_selectionMatrix.transpose() * m_selectionMatrix)) * m_basis;
	}
	m_interpolSolver.compute(lhsMat);
	m_tempSolution.resize(lhsMat.rows(), 3);
	m_solutionLastFrame.resize(lhsMat.rows(), 3);
	m_tempAuxils.resize(m_selectionMatrix.rows(), 3);
	m_tempRHS.resize(lhsMat.rows(), 3);

	// If regularization is used we have to get some initial value for m_solutionLastFrame
	// For this we have to evaluate the auxilaries for the rest state and set up a projection
	// problem, which returns the best subspace approximation of these auxilaries in
	// the interpolation base.
	if (m_regularizationWeight > 0) {
		std::cout << "	Computing extra stuff for regularization..." << std::endl;
		m_regularizationRHSMatrix = m_basis.transpose() * m_massMatrix * m_basis;
		PDPositions rhsFull(m_constraints.size() * m_auxiliarySize, 3);
		// Collect auxiliary variables for sampled constraints
		int didCollide = -1;
		PROJ_DYN_PARALLEL_FOR
			for (int i = 0; i < m_constraints.size(); i++) {
				PDScalar weight = 1.;
				if (!NO_WEIGHTS_IN_CONSTRUCTION) {
					weight = std::sqrt(m_constraints[i]->getWeight());
				}
				didCollide = -1;
				rhsFull.block(i*m_auxiliarySize, 0, m_auxiliarySize, 3) = m_constraints[i]->getP(m_restPositionFull, didCollide)
					* weight;
			}
		PDPositions rhsSub = m_basis.transpose() * m_massMatrix * rhsFull;
		PDMatrix projLHS = m_basis.transpose() * m_massMatrix * m_basis;
		Eigen::LLT<PDMatrix> projSolver;
		projSolver.compute(projLHS);
		for (unsigned int d = 0; d < 3; d++) {
			m_solutionLastFrame.col(d) = projSolver.solve(rhsSub.col(d));
		}
	}

	// Compute the finalization matrix, i.e. the one that maps a vector from the subspace
	// spanned by the snapshots' PCA to a fully assembled rhs.
	// In case a positions subspace is used, the transpose of the subspace matrix will
	// also be applied to the matrix
	std::cout << "	Computing 'finalize' matrices..." << std::endl;
	if (positionSubspaceT.rows() > 0) {
		std::cout << "		sparse part..." << std::endl;
		PDSparseMatrix finalTemp = positionSubspaceT * m_assemblyMatrix;
		if (NO_WEIGHTS_IN_CONSTRUCTION) {
			PROJ_DYN_PARALLEL_FOR
				for (int c = 0; c < finalTemp.cols(); c++) {
					finalTemp.col(c) *= weightVector(c);
				}
		}
		std::cout << "		dense part..." << std::endl;
		m_finalizeMatrix.resize(positionSubspaceT.rows(), m_basis.cols());
		PROJ_DYN_PARALLEL_FOR
			for (int c = 0; c < m_basis.cols(); c++) {
				m_finalizeMatrix.col(c) = finalTemp * m_basis.col(c);
			}
		m_usingPositionSubspace = true;
	}
	else {
		m_finalizeMatrixBig = m_assemblyMatrix * m_weightMatrix * m_basis;
		m_usingPositionSubspace = false;
	}

	// Finally compute the solver for the projection problem if desired
	if (PROJ_DYN_ENABLE_RHS_PROJECTION) {
		std::cout << "	Computing projection solver for this group..." << std::endl;
		m_projectionSolver.compute(m_basis.transpose() * m_basis);
		if (m_usingPositionSubspace) {
			m_projectionFinalizeMatrix = positionSubspaceT * m_assemblyMatrix;
		}
	}

	std::cout << "Initiated snapshot interpolation for group " << m_groupName << "." << std::endl;

	m_interpolReady = true;
}

void PD::RHSInterpolationGroup::approximateRHS(PDPositions & pos, PDPositions & rhs, bool * collidedVertices)
{
	if (!m_interpolReady) {
		std::cout << "Interpolation is not initialized!!!" << std::endl;
		return;
	}

	interpolate(pos, m_tempSolution, collidedVertices);

	// Apply finalization matrix and add to current rhs
	if (m_usingPositionSubspace) {
		PROJ_DYN_PARALLEL_FOR
			for (int d = 0; d < 3; d++) {
				rhs.col(d) += m_weightFac * m_finalizeMatrix * m_tempSolution.col(d);
				//std::cout << m_finalizeMatrix * m_tempSolution.col(d) << std::endl;
			}
	}
	else {
		PROJ_DYN_PARALLEL_FOR
			for (int d = 0; d < 3; d++) {
				rhs.col(d) += m_weightFac * m_finalizeMatrixBig * m_tempSolution.col(d);
			}
	}
}

void PD::RHSInterpolationGroup::evaluateRHS(PDPositions & pos, PDPositions & rhs, bool projectToInterpolationSpaceAndBack, bool forceFull)
{
	if (PROJ_DYN_ENABLE_RHS_PROJECTION) {
		PDPositions auxils(m_constraints.size() * m_auxiliarySize, 3);
		PDPositions auxilsNoWeights(m_constraints.size() * m_auxiliarySize, 3);
		// Collect auxiliary variables for sampled constraints
		int didCollide = -1;
		PROJ_DYN_PARALLEL_FOR
			for (int i = 0; i < m_constraints.size(); i++) {
				didCollide = -1;
				PDScalar weight = m_constraints[i]->getWeight();
				if (!NO_WEIGHTS_IN_CONSTRUCTION) {
					weight = std::sqrt(weight);
				}
				auxils.block(i*m_auxiliarySize, 0, m_auxiliarySize, 3) = m_constraints[i]->getP(pos, didCollide)
					* weight * m_weightFac;
				auxilsNoWeights.block(i*m_auxiliarySize, 0, m_auxiliarySize, 3) = m_constraints[i]->getP(pos, didCollide);
			}

		if (projectToInterpolationSpaceAndBack) {
			PDPositions solutionSub = m_projectionSolver.solve(m_basis.transpose() * auxilsNoWeights);
			auxils = m_basis * solutionSub;
			PROJ_DYN_PARALLEL_FOR
				for (int i = 0; i < m_constraints.size(); i++) {
					didCollide = -1;
					PDScalar weight = m_constraints[i]->getWeight();
					if (!NO_WEIGHTS_IN_CONSTRUCTION) {
						weight = std::sqrt(weight);
					}
					auxils.block(i*m_auxiliarySize, 0, m_auxiliarySize, 3) *= weight * m_weightFac;
				}
		}

		if (m_usingPositionSubspace && !forceFull) {
			rhs += m_projectionFinalizeMatrix * auxils;
		}
		else {
			rhs += m_assemblyMatrix * auxils;
		}
	}
	else {
		std::cout << "Enable PROJ_DYN_ENABLE_RHS_PROJECTION if you desire to evaluate full rhs!" << std::endl;
	}
}

void PD::RHSInterpolationGroup::interpolate(PDPositions & pos, PDPositions & interpol, bool* collidedVertices)
{
	// Collect auxiliary variables for sampled constraints
	int didCollide = -1;
	PROJ_DYN_PARALLEL_FOR
		for (int i = 0; i < m_sampledConstraints.size(); i++) {
			didCollide = -1;
			PDScalar weight = 1.;
			if (!NO_WEIGHTS_IN_CONSTRUCTION) {
				weight = std::sqrt(m_sampledConstraints[i]->getWeight());
			}
			m_tempAuxils.block(i*m_auxiliarySize, 0, m_auxiliarySize, 3) = m_sampledConstraints[i]->getP(pos, didCollide)
				* weight * m_blowUp;
			if (collidedVertices && didCollide > 0) {
				collidedVertices[didCollide] = true;
			}
		}

	// Set up rhs from auxiliaries and solve the system for all three columns
	PROJ_DYN_PARALLEL_FOR
		for (int d = 0; d < 3; d++) {
			m_tempRHS.col(d) = m_interpolRHSMatrix * m_tempAuxils.col(d);
			if (m_regularizationWeight > 0.) {
				m_tempRHS.col(d) += m_regularizationWeight * m_regularizationRHSMatrix * m_solutionLastFrame.col(d);
			}
			interpol.col(d) = m_interpolSolver.solve(m_tempRHS.col(d));
		}

	m_solutionLastFrame = interpol;
}

PDMatrix PD::RHSInterpolationGroup::getLHSMatrixSubspace(PDMatrix& posSubspaceMat, PDMatrix& posSubspaceMat_T)
{
	if (!m_lhsMatrixSubspaceInitialized) {
		PDSparseMatrix temp;
		if (NO_WEIGHTS_IN_CONSTRUCTION) {
			temp = m_assemblyMatrix * m_weightMatrix * m_assemblyMatrix.transpose();
		}
		else {
			temp = m_assemblyMatrix * m_assemblyMatrix.transpose();
		}
		m_lhsMatrixSubspace = posSubspaceMat_T * temp * posSubspaceMat;
		m_lhsMatrixSubspaceInitialized = true;
	}
	return m_lhsMatrixSubspace;
}

PDSparseMatrix PD::RHSInterpolationGroup::getLHSMatrix()
{
	PDSparseMatrix temp;
	if (NO_WEIGHTS_IN_CONSTRUCTION) {
		temp = m_assemblyMatrix * m_weightMatrix * m_assemblyMatrix.transpose();
	}
	else {
		temp = m_assemblyMatrix * m_assemblyMatrix.transpose();
	}

	return temp;
}

bool PD::RHSInterpolationGroup::hasBasis()
{
	return m_basis.cols() >= 1 && m_basis.rows() > 1;
}

std::vector<ProjDynConstraint*>& PD::RHSInterpolationGroup::getSampledConstraints()
{
	return m_sampledConstraints;
}

std::vector<ProjDynConstraint*>& PD::RHSInterpolationGroup::getConstraints()
{
	return m_constraints;
}

void PD::RHSInterpolationGroup::setExampleWeights(std::vector<PDScalar>& weights)
{
	if (m_isTetExampleGroup) {
		for (ProjDynConstraint* c : m_sampledConstraints) {
			TetExampleBased* tc = (TetExampleBased*)c;
			tc->setExampleWeights(weights);
		}
	}
	else if (getName() == "spring") {
		for (ProjDynConstraint* c : m_sampledConstraints) {
			SpringConstraint* sc = (SpringConstraint*)c;
			sc->setExWeights(weights);
		}
	}
	else {
		return;
	}
}

void PD::RHSInterpolationGroup::setWeightFactor(PDScalar w)
{
	m_weightFac = w;
}

void PD::RHSInterpolationGroup::setBlowup(PDScalar s)
{
	m_blowUp = s;
}

std::string PD::RHSInterpolationGroup::getName()
{
	return m_groupName;
}


// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2019 Blue Cheetah Analog Design Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <boost/container_hash/hash.hpp>

#include <fmt/core.h>

#include <cbag/layout/track_coloring.h>
#include <cbag/util/math.h>

namespace cbag {
namespace layout {

color_info::color_info() noexcept = default;

color_info::color_info(cnt_t mod, offset_t scale, offset_t offset) noexcept
    : mod_(mod), scale_(scale), offset_(offset) {}

bool color_info::operator==(const color_info &rhs) const noexcept {
    return mod_ == rhs.mod_ && scale_ == rhs.scale_ && offset_ == rhs.offset_;
}

bool color_info::is_valid() const noexcept { return mod_ >= 1; }

std::size_t color_info::get_hash() const noexcept {
    auto seed = static_cast<std::size_t>(mod_);
    boost::hash_combine(seed, scale_);
    boost::hash_combine(seed, offset_);
    return seed;
}

std::string color_info::to_string() const {
    return fmt::format("({}, {}, {})", mod_, scale_, offset_);
}

cnt_t color_info::get_htr_parity(htr_t htr) const noexcept {
    // htr transforms to scale * htr + (scale - 1), since
    // htr = -1 aligns at the axis.
    return util::pos_mod(scale_ * htr + scale_ - 1 + offset_, 2 * mod_) / 2;
}

cnt_t color_info::get_modulus() const noexcept { return mod_; }

color_info color_info::get_transform(offset_t axis_scale, htr_t orig_htr) const noexcept {
    auto new_scale = axis_scale * scale_;
    auto new_offset = util::pos_mod(scale_ * orig_htr + offset_, 2 * mod_);
    if (mod_ == 2 && new_scale < 0) {
        // optimization: for 2 colors, sign flip is equivalent to shifting
        new_scale = 1;
        new_offset = 3 - new_offset;
    }
    return {mod_, new_scale, new_offset};
}

track_coloring::track_coloring() = default;

track_coloring::track_coloring(level_t bot_level, std::vector<color_info> &&data)
    : bot_level_(bot_level), data_(std::move(data)) {}

bool track_coloring::operator==(const track_coloring &rhs) const noexcept {
    return data_ == rhs.data_;
}

std::size_t track_coloring::size() const noexcept { return data_.size(); }

std::size_t track_coloring::get_hash() const noexcept {
    auto seed = static_cast<std::size_t>(0);
    for (const auto &info : data_) {
        boost::hash_combine(seed, info.get_hash());
    }
    return seed;
}

std::string track_coloring::to_string() const {
    auto n = data_.size();
    switch (n) {
    case 0:
        return "TrackColoring[]";
    case 1:
        return fmt::format("TrackColoring[{}]", data_.front().to_string());
    default: {
        auto ans = fmt::format("TrackColoring[{}", data_.front().to_string());
        for (decltype(n) idx = 1; idx < n; ++idx) {
            ans += fmt::format(", {}", data_[idx].to_string());
        }
        ans += "]";
        return ans;
    }
    }
}

cnt_t track_coloring::get_htr_parity(level_t level, htr_t htr) const noexcept {
    auto idx = level - bot_level_;
    if (idx < 0 || static_cast<std::size_t>(idx) >= data_.size())
        return 0;

    return data_[idx].get_htr_parity(htr);
}

const color_info &track_coloring::get_color_info(level_t level) const {
    return data_[level - bot_level_];
}

} // namespace layout
} // namespace cbag


//
// Variable for Scheme
//
// Author: Zhang Zhenghao (zhangzhenghao@hotmail.com)
//
#pragma once

#include <string>
#include <memory>
#include <sstream>
#include <iostream>
#include <functional>
#include <unordered_map>
#include <boost/multiprecision/cpp_int.hpp>
#include "exception.hpp"
#include "environment.hpp"
#include "garbage.hpp"

class Variable: public GarbageObject
{
public:

	// Type of variable
	enum Type {
		TYPE_RATIONAL 	= 0x01,
		TYPE_FLOAT 		= 0x02,
		TYPE_STRING 	= 0x04,
		TYPE_SYMBOL  	= 0x08,
		TYPE_SPEC     	= 0x10,
		TYPE_PAIR 	 	= 0x20,
		TYPE_PRIM 	 	= 0x40,
		TYPE_COMP 	 	= 0x80,
		// Type class
		TYPE_TEXT		= 0x0C,
		TYPE_NUMBER		= 0x03,
		TYPE_PROCEDURE	= 0xC0,
		// Sub type
		TYPE_INTEGER	= 0x100
	};

private:

	// Indent class
	struct Primitive;
	struct Compound;
	friend Environment;

	// Type alias
	using string = std::string;
	using ostream = std::ostream;
	using istream = std::istream;
	using ostringstream = std::ostringstream;
	using pair = std::pair<Variable, Variable>;
	using cpp_rational = boost::multiprecision::cpp_rational;
	using function = std::function<Variable(const Variable&, Environment&)>;

	// Optimization: constant pool
	static std::unordered_map<std::string, Variable> pool;

	// Type of variable
	Type type;

	// Reference count
	int* refCount = nullptr;

	// Value of variable
	union {
		cpp_rational*	rationalPtr;
		double*		doublePtr;
		string*		stringPtr;
		pair*		pairPtr;
		void*		voidPtr;
		Primitive*	primPtr;
		Compound*	compPtr;
	};

public:

	// Constructor for special
	Variable();

	// Constructor for rational
	Variable(const cpp_rational& rational);

	// Constructor for double
	Variable(double value);

	// Constructor for rational, double, string and symbol
	Variable(const string &str, Type type);

	// Constructor for pairs
	Variable(const Variable& lhs, const Variable& rhs);

	// Constructor for primitive procedure
	Variable(const string& name, const function& func);

	// Constructor for compound procedure
	Variable(const string& name, const Variable& args, const Variable& body, const Environment& env);

	// Copy constructor
	Variable(const Variable& var);

	// Destructor
	~Variable();

	// Swap two variables
	friend void swap(Variable& lhs, Variable& rhs);

	// Assignment
	Variable& operator=(Variable var);

	// Optimization: constant pool
	static Variable createSymbol(const std::string& str);

	// Finalize value
	void finalize() const override;

	// Scan and tag value in using
	void scan(int tag) const override;

	// Standard I/O
	friend ostream& operator<<(ostream& out, const Variable& var);
	friend istream& operator>>(istream& in, Variable& var);

	// Require type, throw exception if type is wrong
	void requireType(const string &caller, Type type) const;

	// Get type name
	static string getTypeName(Type type);

	// Convert operations
	string toString() const;
	double toDouble() const;

	// Check operations
	bool isNull() const;
	bool isVoid() const;
	bool isPair() const;
	bool isNumber() const;
	bool isInteger() const;
	bool isSymbol() const;
	bool isString() const;
	bool isPrim() const;
	bool isComp() const;
	bool isProcedure() const;

	// Arithmetic operations
	friend Variable operator+(const Variable& lhs, const Variable& rhs);
	friend Variable operator-(const Variable& lhs, const Variable& rhs);
	friend Variable operator*(const Variable& lhs, const Variable& rhs);
	friend Variable operator/(const Variable& lhs, const Variable& rhs);
	friend Variable operator-(const Variable& var);
	friend Variable remainder(const Variable& lhs, const Variable& rhs);
	friend Variable quotient(const Variable& lhs, const Variable& rhs);
	friend Variable gcd(const Variable& lhs, const Variable& rhs);
	bool isEven() const;
	bool isOdd() const;

	// Compare operations
	friend bool operator<(const Variable& lhs, const Variable& rhs);
	friend bool operator>(const Variable& lhs, const Variable& rhs);
	friend bool operator<=(const Variable& lhs, const Variable& rhs);
	friend bool operator>=(const Variable& lhs, const Variable& rhs);
	friend bool operator==(const Variable& lhs, const Variable& rhs);
	friend bool operator!=(const Variable& lhs, const Variable& rhs);

	// Pair operations
	Variable& car() const;
	Variable& cdr() const;
	Variable setCar(const Variable& var) const;
	Variable setCdr(const Variable& var) const;

	// Procedure operations
	Variable operator()(const Variable& arg, Environment& env) const;
	Variable& getProcedureArgs() const;
	Variable& getProcedureBody() const;
	string getProcedureName() const;
	Environment getProcedureEnv() const;
};

// Primitive procedure

struct Variable::Primitive
{
	string name;	// Name of procedure
	function func;	// Function object
	Primitive(const string& name, const function& func): name(name), func(func) {}
};

// Compound procedure

struct Variable::Compound
{
	string name;		// Name of procedure
	Variable args, body;// Argument and body
	Environment env;	// Closure
	Compound(const string& name, const Variable& args, const Variable& body, const Environment& env):
		name(name), args(args), body(body), env(env) {}
};

// Constant values

extern const Variable VAR_NULL;
extern const Variable VAR_VOID;
extern const Variable VAR_TRUE;
extern const Variable VAR_FALSE;


//  Copyright 2010 Vicente J. Botet Escriba
//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

// test ratio typedef's

#include <boost/ratio/ratio.hpp>

#if !defined(BOOST_NO_STATIC_ASSERT)
#define NOTHING ""
#endif

BOOST_RATIO_STATIC_ASSERT(boost::atto::num == 1 && boost::atto::den == 1000000000000000000ULL, NOTHING, (boost::mpl::integral_c<boost::intmax_t,boost::atto::den>));
BOOST_RATIO_STATIC_ASSERT(boost::femto::num == 1 && boost::femto::den == 1000000000000000ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::pico::num == 1 && boost::pico::den == 1000000000000ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::nano::num == 1 && boost::nano::den == 1000000000ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::micro::num == 1 && boost::micro::den == 1000000ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::milli::num == 1 && boost::milli::den == 1000ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::centi::num == 1 && boost::centi::den == 100ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::deci::num == 1 && boost::deci::den == 10ULL, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::deca::num == 10ULL && boost::deca::den == 1, NOTHING, (boost::mpl::integral_c<boost::intmax_t,boost::deca::den>));
BOOST_RATIO_STATIC_ASSERT(boost::hecto::num == 100ULL && boost::hecto::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::kilo::num == 1000ULL && boost::kilo::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::mega::num == 1000000ULL && boost::mega::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::giga::num == 1000000000ULL && boost::giga::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::tera::num == 1000000000000ULL && boost::tera::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::peta::num == 1000000000000000ULL && boost::peta::den == 1, NOTHING, ());
BOOST_RATIO_STATIC_ASSERT(boost::exa::num == 1000000000000000000ULL && boost::exa::den == 1, NOTHING, ());


// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2017.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * 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.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// 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 ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS 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.
//
// --------------------------------------------------------------------------
// $Maintainer: Chris Bielow $
// $Authors: Andreas Bertsch, Chris Bielow $
// --------------------------------------------------------------------------
//

#include <OpenMS/MATH/STATISTICS/GaussFitter.h>

#include <boost/math/distributions/normal.hpp>
#include <unsupported/Eigen/NonLinearOptimization>

using namespace std;

// #define GAUSS_FITTER_VERBOSE
// #undef  GAUSS_FITTER_VERBOSE

namespace OpenMS
{
  namespace Math
  {
    GaussFitter::GaussFitter()
    : init_param_(0.06, 3.0, 0.5)
    {
    }

    GaussFitter::~GaussFitter()
    {
    }

    void GaussFitter::setInitialParameters(const GaussFitResult & param)
    {
      init_param_ = param;
    }

    struct GaussFunctor
    {
      int inputs() const { return m_inputs; }
      int values() const { return m_values; }

      GaussFunctor(int dimensions, const std::vector<DPosition<2> >* data)
      : m_inputs(dimensions),
        m_values(static_cast<int>(data->size())),
        m_data(data)
      {}

      int operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec) const
      {
        const double A = x(0);
        const double x0 = x(1);
        const double sig = x(2);
        const double sig2 = 2 * sig * sig;

        UInt i = 0;
        for (std::vector<DPosition<2> >::const_iterator it = m_data->begin(); it != m_data->end(); ++it, ++i)
        {
          fvec(i) = A * std::exp(- (it->getX() - x0) * (it->getX() - x0) / sig2) - it->getY();
        }

        return 0;
      }
      // compute Jacobian matrix for the different parameters
      int df(const Eigen::VectorXd &x, Eigen::MatrixXd &J) const
      {
        const double A = x(0);
        const double x0 = x(1);
        const double sig = x(2);
        const double sig2 = 2 * sig * sig;
        const double sig3 = 2 * sig2 * sig;

        UInt i = 0;
        for (std::vector<DPosition<2> >::const_iterator it = m_data->begin(); it != m_data->end(); ++it, ++i)
        {
          const double xd = (it->getX() - x0);
          const double xd2 = xd*xd;
          double j0 = std::exp(-1.0 * xd2 / sig2);
          J(i,0) = j0;
          J(i,1) = (A * j0 * (-(-2 * it->getX() + 2.0 * x0) / sig2));
          J(i,2) = (A * j0 * (xd2 / sig3));
        }
        return 0;
      }

      const int m_inputs, m_values;
      const std::vector<DPosition<2> >* m_data;
    };

    GaussFitter::GaussFitResult GaussFitter::fit(vector<DPosition<2> > & input) const
    {
      Eigen::VectorXd x_init (3);
      x_init(0) = init_param_.A;
      x_init(1) = init_param_.x0;
      x_init(2) = init_param_.sigma;
      GaussFunctor functor (3, &input);
      Eigen::LevenbergMarquardt<GaussFunctor> lmSolver (functor);
      Eigen::LevenbergMarquardtSpace::Status status = lmSolver.minimize(x_init);

      // the states are poorly documented. after checking the source and
      // http://www.ultimatepp.org/reference%24Eigen_demo%24en-us.html we believe that
      // all states except TooManyFunctionEvaluation and ImproperInputParameters are good
      // termination states.
      if (status == Eigen::LevenbergMarquardtSpace::ImproperInputParameters ||
          status == Eigen::LevenbergMarquardtSpace::TooManyFunctionEvaluation)
      {
          throw Exception::UnableToFit(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, "UnableToFit-GaussFitter", "Could not fit the Gaussian to the data: Error " + String(status));
      }

      x_init(2) = fabs(x_init(2)); // sigma can be negative, but |sigma| would actually be the correct solution

#ifdef GAUSS_FITTER_VERBOSE
      std::stringstream formula;
      formula << "f(x)=" << result.A << " * exp(-(x - " << result.x0 << ") ** 2 / 2 / (" << result.sigma << ") ** 2)";
      std::cout << formular.str() << std::endl;
#endif

      return GaussFitResult (x_init(0), x_init(1), x_init(2));
    }

    // static
    std::vector<double> GaussFitter::eval(const std::vector<double>& evaluation_points, const GaussFitter::GaussFitResult& model)
    {
      std::vector<double> out;
      out.reserve(evaluation_points.size());
      boost::math::normal_distribution<> ndf(model.x0, model.sigma);
      double int0 = model.A / boost::math::pdf(ndf, model.x0); // intensity normalization factor of the max @ x0 (simply multiplying the CDF with A is wrong!)
      for (Size i = 0; i < evaluation_points.size(); ++i)
      {
        out.push_back(boost::math::pdf(ndf, evaluation_points[i]) * int0 );
      }
      return out;
    }

  }   //namespace Math
} // namespace OpenMS


#ifndef _WORD2VEC_H
#define _WORD2VEC_H

#define MAX_STRING 100
#define EXP_TABLE_SIZE 1000
#define MAX_EXP 6
#define MAX_SENTENCE_LENGTH 1000
#define MAX_CODE_LENGTH 40

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <pthread.h>
#include <Eigen/Dense>
#include <Eigen/Core>
#include <iostream>
#include <thread>
#include <vector>
#include "IEmbeddingModel.hpp"

using namespace Eigen;
using namespace std;

//typedef float real;

struct vocab_word {
  long long cn;
  int *point;
  char *word, *code, codelen;
};

class Word2Vec: public IEmbeddingModel
{
  private:

    // internal parameters
    const int vocab_hash_size = 30000000;  // Maximum 30 * 0.7 = 21M words in the vocabulary
    int *vocab_hash;
    float starting_alpha;

    const int table_size = 1e8;
    int *table;
    clock_t start;
    bool read_vocab_flag;

    //adjustable params
    char train_file[MAX_STRING];
    int cbow = 1, debug_mode = 2, window = 5, min_count = 1, num_threads = 10, min_reduce = 1;
    long long vocab_max_size = 100000, layer1_size = 50;
    long long train_words = 0, word_count_actual = 0, iter = 1, file_size = 0, classes = 0;
    float alpha = 0.025, sample = 1e-3;
    int hs = 0, negative = 5;

  protected:
    struct vocab_word *vocab;
    float *syn0, *syn1, *syn1neg, *expTable;
    long long vocab_size = 0;

  public:
    Word2Vec(char* _train_file);
    ~Word2Vec();
    //native functions
    int AddWordToVocab(char *word);
    int ArgPos(char* str,int argc, char** argv);
    void CreateBinaryTree();
    void InitNet();
    void InitUnigramTable();
    void LearnVocabFromTrainFile();
    void ReadWord(char* word,FILE* fin);
    int ReadWordIndex(FILE* fin);
    void ReduceVocab();
    int GetWordHash(char *word);
    int SearchVocab(char* word);
    void SortVocab();
    //void *TrainModelThread(void* id);
    void TrainModelThread(int id);
    friend int VocabCompare(const void*a, const void* b);

    void GetVocab(vector<string> &vocabulary) override;
    void GetEmbeddingMatrix(MatrixXf &Embeddings) override;
    void GetEmbeddingMatrix(vector<VectorXf> &Embeddings) override;

    void clear_mem();
    void word2vecStandartInit();
    //modified function
    void TrainModel();
    void SaveVocab(char * save_vocab_file);
    void ReadVocab(char * read_vocab_file);
};

#endif