/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2006 Joseph Wang
 Copyright (C) 2009 Liquidnet Holdings, Inc.

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <https://www.quantlib.org/license.shtml>.

 This program 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 license for more details.
*/

#include "toplevelfixture.hpp"
#include "utilities.hpp"
#include <ql/math/fastfouriertransform.hpp>
#include <ql/math/array.hpp>
#include <complex>
#include <vector>
#include <functional>

using namespace QuantLib;
using namespace boost::unit_test_framework;

BOOST_FIXTURE_TEST_SUITE(QuantLibTests, TopLevelFixture)

BOOST_AUTO_TEST_SUITE(FastFourierTransformTests)

BOOST_AUTO_TEST_CASE(testSimple) {
    BOOST_TEST_MESSAGE("Testing complex direct FFT...");
    typedef std::complex<Real> cx;
    cx a[] = { cx(0,0), cx(1,1), cx(3,3), cx(4,4),
               cx(4,4), cx(3,3), cx(1,1), cx(0,0) };
    cx b[8];
    FastFourierTransform fft(3);
    fft.transform(a, a+8, b);
    cx expected[] = { cx(16,16), cx(-4.8284,-11.6569),
                      cx(0,0),   cx(-0.3431,0.8284),
                      cx(0,0),   cx(0.8284, -0.3431),
                      cx(0,0),   cx(-11.6569,-4.8284) };
    for (size_t i = 0; i<8; i++) {
        if ((std::fabs(b[i].real() - expected[i].real()) > 1.0e-2) ||
            (std::fabs(b[i].imag() - expected[i].imag()) > 1.0e-2))
            BOOST_ERROR("Convolution(" << i << ")\n"
                        << std::setprecision(4) << std::scientific
                        << "    calculated: " << b[i] << "\n"
                        << "    expected:   " << expected[i]);
    }
}

BOOST_AUTO_TEST_CASE(testInverse) {
    BOOST_TEST_MESSAGE("Testing convolution via inverse FFT...");
    Array x(3);
    x[0] = 1;
    x[1] = 2;
    x[2] = 3;

    size_t order = FastFourierTransform::min_order(x.size())+1;
    FastFourierTransform fft(order);
    size_t nFrq = fft.output_size();
    std::vector< std::complex<Real> > ft (nFrq);
    std::vector< Real > tmp (nFrq);
    std::complex<Real> z = std::complex<Real>();

    fft.inverse_transform(x.begin(), x.end(), ft.begin());
    for (Size i=0; i<nFrq; ++i) {
        tmp[i] = std::norm(ft[i]);
        ft[i] = z;
    }
    fft.inverse_transform(tmp.begin(), tmp.end(), ft.begin());

    // 0
    Real calculated = ft[0].real() / nFrq;
    Real expected = x[0]*x[0] + x[1]*x[1] + x[2]*x[2];
    if (fabs (calculated - expected) > 1.0e-10)
        BOOST_ERROR("Convolution(0)\n"
                    << std::setprecision(16) << std::scientific
                    << "    calculated: " << calculated << "\n"
                    << "    expected:   " << expected);

    // 1
    calculated = ft[1].real() / nFrq;
    expected = x[0]*x[1] + x[1]*x[2];
    if (fabs (calculated - expected) > 1.0e-10)
        BOOST_ERROR("Convolution(1)\n"
                    << std::setprecision(16) << std::scientific
                    << "    calculated: " << calculated << "\n"
                    << "    expected:   " << expected);

    // 2
    calculated = ft[2].real() / nFrq;
    expected = x[0]*x[2];
    if (fabs (calculated - expected) > 1.0e-10)
        BOOST_ERROR("Convolution(1)\n"
                    << std::setprecision(16) << std::scientific
                    << "    calculated: " << calculated << "\n"
                    << "    expected:   " << expected);

}

BOOST_AUTO_TEST_CASE(testTrivialOrder) {
    BOOST_TEST_MESSAGE("Testing FFT of size 1 (order 0)...");
    // min_order(1) is 0; constructing an FFT of order 0 used to write
    // out of bounds in the constructor.  A size-1 transform reduces to
    // a copy of the single input element.
    BOOST_CHECK_EQUAL(FastFourierTransform::min_order(1), 0U);

    typedef std::complex<Real> cx;
    FastFourierTransform fft(0);
    BOOST_CHECK_EQUAL(fft.output_size(), 1U);

    cx a[] = { cx(2.5, -1.5) };
    cx b[1];
    fft.transform(a, a+1, b);
    if (std::fabs(b[0].real() - a[0].real()) > 1.0e-12 ||
        std::fabs(b[0].imag() - a[0].imag()) > 1.0e-12)
        BOOST_ERROR("Size-1 FFT\n"
                    << std::setprecision(16) << std::scientific
                    << "    calculated: " << b[0] << "\n"
                    << "    expected:   " << a[0]);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
