// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
void Foam::highestMomentReconstruction::invert(const univariateMomentSet& moments)
{
    univariateMomentSet m(moments);

    reset();
	
	invertSingular(m);
	if (nullSigma_) return;
	
	label nRealizableMoments = m.nRealizableMoments();
	
	// Resizing the moment set to avoid copying again
	m.resize(nRealizableMoments);

	// Local set of starred moments
	univariateMomentSet mStar(nRealizableMoments, m.support());

	// Compute target function for sigma = 0
	scalar sigmaLow = 0.0;
	scalar fLow = targetFunction(sigmaLow, m, mStar);
	sigma_ = sigmaLow;

	// Check if sigma = 0 is root
	if (mag(fLow) <= targetFunctionTol_)
	{
		sigma_ = 0.0;
		nullSigma_ = true;
		momentInverter_().invert(m);

		secondaryQuadrature
		(
			momentInverter_().weights(),
			momentInverter_().abscissae()
		);

		return;
	}

	// Compute target function for sigma = sigmaMax
	scalar sigMax = kernel_().sigmaMax(m);
	scalar sigmaHigh = sigMax;
	scalar fHigh = targetFunction(sigmaHigh, m, mStar);

	// This should not happen with the new algorithm
	if (fLow*fHigh > 0)
	{
		// Root not found. Minimize target function in [0, sigma_]
		sigma_ = minimizeTargetFunction(0, sigmaHigh, m, mStar);

		// If sigma_ is small, use QMOM
		if (mag(sigma_) < sigmaMin_)
		{
			sigma_ = 0.0;
			nullSigma_ = true;
			momentInverter_().invert(m);

			secondaryQuadrature
			(
				momentInverter_().weights(),
				momentInverter_().abscissae()
			);

			return;
		}

		targetFunction(sigma_, m, mStar);
		secondaryQuadrature  // secondary quadrature from mStar
		(
			momentInverter_().weights(),
			momentInverter_().abscissae()
		);

		return;
	}

	// Apply Ridder's algorithm to find sigma
	for (label iter = 0; iter < maxSigmaIter_; iter++)
	{
		scalar fMid, sigmaMid;

		sigmaMid = (sigmaLow + sigmaHigh)/2.0;
		fMid = targetFunction(sigmaMid, m, mStar);

		scalar s = sqrt(sqr(fMid) - fLow*fHigh);

		if (s == 0.0)
		{
			FatalErrorInFunction
				<< "Singular value encountered searching for root." << nl
				<< "    Moment set = " << m << nl
				<< "    sigma = " << sigma_ << nl
				<< "    fLow = " << fLow << nl
				<< "    fMid = " << fMid << nl
				<< "    fHigh = " << fHigh
				<< abort(FatalError);
		}

		sigma_ = sigmaMid + (sigmaMid - sigmaLow)*sign(fLow - fHigh)*fMid/s;

		kernel_().momentsToMomentsStar(sigma_, m, mStar);

		scalar fNew = targetFunction(sigma_, m, mStar);
		scalar dSigma = (sigmaHigh - sigmaLow)/2.0;

		// Check for convergence
		if (mag(fNew) <= targetFunctionTol_ || mag(dSigma) <= sigmaTol_)
		{
			// Root finding converged

			// If sigma_ is small, use QMOM
			if (mag(sigma_) < sigmaMin_)
			{
				sigma_ = 0.0;
				nullSigma_ = true;
				momentInverter_().invert(m);

				secondaryQuadrature
				(
					momentInverter_().weights(),
					momentInverter_().abscissae()
				);

				return;
			}

			scalar momentError = normalizedMomentError(sigma_, m, mStar);

			if
			(
				momentError < momentsTol_
			)
			{
				// Found a value of sigma that preserves all the moments
				secondaryQuadrature  // Secondary quadrature from mStar
				(
					momentInverter_().weights(),
					momentInverter_().abscissae()
				);

				return;
			}
			else
			{
				// Root not found. Minimize target function in [0, sigma_]
				sigma_ = minimizeTargetFunction(0, sigma_, m, mStar);

				// If sigma_ is small, use QMOM
				if (mag(sigma_) < sigmaMin_)
				{
					sigma_ = 0.0;
					nullSigma_ = true;
					momentInverter_().invert(m);

					secondaryQuadrature
					(
						momentInverter_().weights(),
						momentInverter_().abscissae()
					);

					return;
				}

				targetFunction(sigma_, m, mStar);

				secondaryQuadrature // Secondary quadrature from  mStar
				(
					momentInverter_().weights(),
					momentInverter_().abscissae()
				);

				return;
			}
		}
		else
		{
			// Root finding did not converge. Refine search.

			if (fNew*fMid < 0 && sigma_ < sigmaMid)
			{
				sigmaLow = sigma_;
				fLow = fNew;
				sigmaHigh = sigmaMid;
				fHigh = fMid;
			}
			else if (fNew*fMid < 0 && sigma_ > sigmaMid)
			{
				sigmaLow = sigmaMid;
				fLow = fMid;
				sigmaHigh = sigma_;
				fHigh = fNew;
			}
			else if (fNew*fLow < 0)
			{
				sigmaHigh = sigma_;
				fHigh = fNew;
			}
			else if (fNew*fHigh < 0)
			{
				sigmaLow = sigma_;
				fLow = fNew;
			}
		}
	}

	FatalErrorInFunction
		<< "Number of iterations exceeded." << nl
		<< "    Max allowed iterations = " << maxSigmaIter_
		<< abort(FatalError);
}
// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
void Foam::extendedMomentInversion::invert(const univariateMomentSet& moments)
{   
    univariateMomentSet m(moments);
    reset();

    // Terminate execution if negative number density is encountered
    if (m[0] < 0.0)
    {
        FatalErrorIn
        (
            "Foam::extendedMomentInversion::invert\n"
            "(\n"
            "   const univariateMomentSet& moments\n"
            ")"
        )   << "The zero-order moment is negative."
            << abort(FatalError);
    }

    // Exclude cases where the zero-order moment is very small to avoid
    // problems in the inversion due to round-off error  
    if (m[0] < SMALL)
    {
        sigma_ = 0.0;
        nullSigma_ = true;

        return;
    }
    
    label nRealizableMoments = m.nRealizableMoments();
   
    if (nRealizableMoments % 2 == 0)
    {
        // If the number of realizable moments is even, we apply the standard
        // QMOM directly to maximize the number of preserved moments.

        // Info << "Even number of realizable moments: using QMOM" << endl;
        // Info << "Moments: " << m << endl;
        // Info << "Invertible: " << m.nInvertibleMoments() << endl;
        // Info << "Realizable: " << m.nRealizableMoments() << endl;
        m.invert();
        sigma_ = 0.0;
        nullSigma_ = true;
        secondaryQuadrature(m);
    }
    else
    {
        // Resizing the moment set to avoid copying again
        m.resize(nRealizableMoments);

        // Local set of starred moments
        univariateMomentSet mStar(nRealizableMoments, 0);

        // Compute target function for sigma = 0
        scalar sigmaLow = 0.0;
        scalar fLow = targetFunction(sigmaLow, m, mStar);
        sigma_ = sigmaLow;
        
        // Check if sigma = 0 is root
        if (mag(fLow) <= targetFunctionTol_)
        {
            m.invert();
            sigma_ = 0.0;
            nullSigma_ = true;
            secondaryQuadrature(m);
            
            return;
        }

        // Compute target function for sigma = sigmaMax 
        scalar sigMax = sigmaMax(m);    
        scalar sigmaHigh = sigMax;
        scalar fHigh = targetFunction(sigmaHigh, m, mStar);

        if (fLow*fHigh > 0)
        {
            // Root not found. Minimize target function in [0, sigma_]
            sigma_ = minimizeTargetFunction(0, sigmaHigh, m, mStar);
            
            // Check if sigma is small and use QMOM
            if (mag(sigma_) < sigmaTol_)
            {
                m.invert();
                sigma_ = 0.0;
                nullSigma_ = true;
                secondaryQuadrature(m);

                return;
            }
            
            targetFunction(sigma_, m, mStar);
            secondaryQuadrature(mStar);

            return;
        }

        // Apply Ridder's algorithm to find sigma
        for (label iter = 0; iter < maxSigmaIter_; iter++)
        {           
            scalar fMid, sigmaMid;

            sigmaMid = (sigmaLow + sigmaHigh)/2.0;
            fMid = targetFunction(sigmaMid, m, mStar);

            scalar s = sqrt(sqr(fMid) - fLow*fHigh);

            if (s == 0.0)
            {
                FatalErrorIn
                (
                    "Foam::extendedMomentInversion::invert\n"
                    "(\n"
                    "   const univariateMomentSet& moments\n"
                    ")"
                )<< "Singular value encountered while attempting to find root."
                 << "Moment set = " << m << endl
                 << "sigma = " << sigma_ << endl
                 << "fLow = " << fLow << endl
                 << "fMid = " << fMid << endl
                 << "fHigh = " << fHigh
                 << abort(FatalError);
            }

            sigma_ = sigmaMid + (sigmaMid - sigmaLow)*sign(fLow - fHigh)*fMid/s;
            
            momentsToMomentsStar(sigma_, m, mStar);

            scalar fNew = targetFunction(sigma_, m, mStar);
            scalar dSigma = (sigmaHigh - sigmaLow)/2.0;

            // Check for convergence
            if (mag(fNew) <= targetFunctionTol_ || mag(dSigma) <= sigmaTol_)
            {             
                if (mag(sigma_) < sigmaTol_)
                {
                    m.invert();
                    sigma_ = 0.0;
                    nullSigma_ = true;
                    secondaryQuadrature(m);

                    return;
                }
                
                scalar momentError = normalizedMomentError(sigma_, m, mStar);

                if 
                (
                    momentError < momentsTol_
                )
                {
                    // Found a value of sigma that preserves all the moments
                    secondaryQuadrature(mStar);

                    return;
                }
                else
                {
                    // Root not found. Minimize target function in [0, sigma_]
                    sigma_ = minimizeTargetFunction(0, sigma_, m, mStar);
                    
                    if (mag(sigma_) < sigmaTol_)
                    {
                        m.invert();
                        sigma_ = 0.0;
                        nullSigma_ = true;
                        secondaryQuadrature(m);
                        
                        return;
                    }

                    targetFunction(sigma_, m, mStar);
                    secondaryQuadrature(mStar);

                    return;
                }
            }
            else
            {
                if (fNew*fMid < 0 && sigma_ < sigmaMid)
                {
                    sigmaLow = sigma_;
                    fLow = fNew;
                    sigmaHigh = sigmaMid;
                    fHigh = fMid;
                }
                else if (fNew*fMid < 0 && sigma_ > sigmaMid)
                {
                    sigmaLow = sigmaMid;
                    fLow = fMid;
                    sigmaHigh = sigma_;
                    fHigh = fNew;
                }
                else if (fNew*fLow < 0)
                {
                    sigmaHigh = sigma_;
                    fHigh = fNew;
                }
                else if (fNew*fHigh < 0)
                {
                    sigmaLow = sigma_;
                    fLow = fNew;
                }
            }
        }

        FatalErrorIn
        (
            "Foam::extendedMomentInversion::invert\n"
            "(\n"
            "   const univariateMomentSet& moments\n"
            ")"
        )   << "Number of iterations exceeded."
            << abort(FatalError);
    }
}
// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
void Foam::extendedMomentInversion::invert(const univariateMomentSet& moments)
{
    univariateMomentSet m(moments);

    reset();

    // Terminate execution if negative number density is encountered
    if (m[0] < 0.0)
    {
        FatalErrorInFunction
            << "The zero-order moment is negative."
            << abort(FatalError);
    }

    // Exclude cases where the zero-order moment is very small to avoid
    // problems in the inversion due to round-off error
    if (m[0] < SMALL)
    {
        sigma_ = 0.0;
        nullSigma_ = true;

        return;
    }

    label nRealizableMoments = m.nRealizableMoments();

    // If the moment set is on the boundary of the moment space, the
    // distribution will be reconstructed by a summation of Dirac delta,
    // and no attempt to use the extended quadrature method of moments is made.
    if (m.isOnMomentSpaceBoundary())
    {

        sigma_ = 0.0;
        nullSigma_ = true;
        m.invert();
        secondaryQuadrature(m);

        return;
    }

    if (nRealizableMoments % 2 == 0)
    {
        // If the number of realizable moments is even, we apply the standard
        // QMOM directly to maximize the number of preserved moments.

        m.invert();
        sigma_ = 0.0;
        nullSigma_ = true;
        secondaryQuadrature(m);
    }
    else
    {
        // Do not attempt the EQMOM reconstruction if mean or variance of the
        //  moment set are small to avoid numerical problems. These problems are
        // particularly acute in the calculation of the recurrence relationship
        // of the Jacobi orthogonal polynomials used for the beta kernel density
        // function.
        if (m[1]/m[0] < minMean_ || (m[2]/m[0] - sqr(m[1]/m[0])) < minVariance_)
        {
            sigma_ = 0.0;
            nullSigma_ = true;

            m.invert();
            secondaryQuadrature(m);

            return;
        }

        // Resizing the moment set to avoid copying again
        m.resize(nRealizableMoments);

        // Local set of starred moments
        univariateMomentSet mStar(nRealizableMoments, 0, m.support());

        // Compute target function for sigma = 0
        scalar sigmaLow = 0.0;
        scalar fLow = targetFunction(sigmaLow, m, mStar);
        sigma_ = sigmaLow;

        // Check if sigma = 0 is root
        if (mag(fLow) <= targetFunctionTol_)
        {
            m.invert();
            sigma_ = 0.0;
            nullSigma_ = true;
            secondaryQuadrature(m);

            return;
        }

        // Compute target function for sigma = sigmaMax
        scalar sigMax = sigmaMax(m);
        scalar sigmaHigh = sigMax;
        scalar fHigh = targetFunction(sigmaHigh, m, mStar);

        // This should not happen with the new algorithm
        if (fLow*fHigh > 0)
        {
            // Root not found. Minimize target function in [0, sigma_]
            sigma_ = minimizeTargetFunction(0, sigmaHigh, m, mStar);

            // If sigma_ is small, use QMOM
            if (mag(sigma_) < sigmaMin_)
            {
                m.invert();
                sigma_ = 0.0;
                nullSigma_ = true;
                secondaryQuadrature(m);

                return;
            }

            targetFunction(sigma_, m, mStar);
            secondaryQuadrature(mStar);

            return;
        }

        // Apply Ridder's algorithm to find sigma
        for (label iter = 0; iter < maxSigmaIter_; iter++)
        {
            scalar fMid, sigmaMid;

            sigmaMid = (sigmaLow + sigmaHigh)/2.0;
            fMid = targetFunction(sigmaMid, m, mStar);

            scalar s = sqrt(sqr(fMid) - fLow*fHigh);

            if (s == 0.0)
            {
                FatalErrorInFunction
                    << "Singular value encountered searching for root.\n"
                    << "Moment set = " << m << endl
                    << "sigma = " << sigma_ << endl
                    << "fLow = " << fLow << endl
                    << "fMid = " << fMid << endl
                    << "fHigh = " << fHigh
                    << abort(FatalError);
            }

            sigma_ = sigmaMid + (sigmaMid - sigmaLow)*sign(fLow - fHigh)*fMid/s;

            momentsToMomentsStar(sigma_, m, mStar);

            scalar fNew = targetFunction(sigma_, m, mStar);
            scalar dSigma = (sigmaHigh - sigmaLow)/2.0;

            // Check for convergence
            if (mag(fNew) <= targetFunctionTol_ || mag(dSigma) <= sigmaTol_)
            {
                // Root finding converged

                // If sigma_ is small, use QMOM
                if (mag(sigma_) < sigmaMin_)
                {
                    m.invert();
                    sigma_ = 0.0;
                    nullSigma_ = true;
                    secondaryQuadrature(m);

                    return;
                }

                scalar momentError = normalizedMomentError(sigma_, m, mStar);

                if
                (
                    momentError < momentsTol_
                )
                {
                    // Found a value of sigma that preserves all the moments
                    secondaryQuadrature(mStar);

                    return;
                }
                else
                {
                    // Root not found. Minimize target function in [0, sigma_]
                    sigma_ = minimizeTargetFunction(0, sigma_, m, mStar);

                    // If sigma_ is small, use QMOM
                    if (mag(sigma_) < sigmaMin_)
                    {
                        m.invert();
                        sigma_ = 0.0;
                        nullSigma_ = true;
                        secondaryQuadrature(m);

                        return;
                    }

                    targetFunction(sigma_, m, mStar);
                    secondaryQuadrature(mStar);

                    return;
                }
            }
            else
            {
                // Root finding did not converge. Refine search.

                if (fNew*fMid < 0 && sigma_ < sigmaMid)
                {
                    sigmaLow = sigma_;
                    fLow = fNew;
                    sigmaHigh = sigmaMid;
                    fHigh = fMid;
                }
                else if (fNew*fMid < 0 && sigma_ > sigmaMid)
                {
                    sigmaLow = sigmaMid;
                    fLow = fMid;
                    sigmaHigh = sigma_;
                    fHigh = fNew;
                }
                else if (fNew*fLow < 0)
                {
                    sigmaHigh = sigma_;
                    fHigh = fNew;
                }
                else if (fNew*fHigh < 0)
                {
                    sigmaLow = sigma_;
                    fLow = fNew;
                }
            }
        }

        FatalErrorInFunction
            << "Number of iterations exceeded.\n"
            << "Max allowed iterations = " << maxSigmaIter_
            << abort(FatalError);
    }
}