/*
 * Copyright (c) 2012-2017, Peter Abeles. All Rights Reserved.
 *
 * This file is part of DDogleg (http://ddogleg.org).
 *
 * 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.
 */

package org.ddogleg.solver.impl;

import org.ddogleg.solver.Polynomial;
import org.ddogleg.solver.PolynomialOps;
import org.ddogleg.solver.PolynomialRoots;
import org.ejml.data.Complex_F64;
import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
import org.ejml.interfaces.decomposition.EigenDecomposition_F64;

import java.util.ArrayList;
import java.util.List;

/**
 * Finds the roots of a polynomial using a companion matrix and eigenvalue decomposition.  Note that the companion
 * matrix is not as stable as people think and will some times fail.  To help improve the situations the found roots
 * are refined.
 *
 * @author Peter Abeles
 */
public class RootFinderCompanion implements PolynomialRoots {

	// Companion matrix
	DMatrixRMaj c = new DMatrixRMaj(1,1);

	// use generalized eigenvalue decomposition to find the roots
	EigenDecomposition_F64<DMatrixRMaj> evd =  DecompositionFactory_DDRM.eig(11, false, false);

	// storage for found roots
	List<Complex_F64> roots = new ArrayList<Complex_F64>();


	public RootFinderCompanion() {
	}

	@Override
	public boolean process( Polynomial poly ) {
		int N = poly.size-1;

		while( poly.c[N] == 0.0 && N > 0 )
			N--;

		if( N <= 0)
			return false;

		// Companion matrix
		if( c.numCols != N ) {
			c.reshape(N,N);
			c.zero();
		} else if( evd.inputModified() ) {
			c.zero();
		}

		double a = poly.c[N];
		for( int i = 0; i < N; i++ ) {
			c.set(i,N-1,-poly.c[i]/a);
		}
		for( int i = 1; i < N; i++ ) {
			c.set(i,i-1,1);
		}

		if( !evd.decompose(c) ) {
			return false;
		}

		roots.clear();
		for( int i = 0; i < N; i++ ) {
			Complex_F64 r = evd.getEigenvalue(i);

			// increase the accuracy of real roots
			if( r.isReal() ) {
				r.real = PolynomialOps.refineRoot(poly, r.real, 30);
			}

			roots.add(r);
		}

		return true;
	}

	@Override
	public List<Complex_F64> getRoots() {
		return roots;
	}
}
