/**
 * @example waveq3d/test/refraction_test.cc
 */
#include <boost/test/unit_test.hpp>
#include <usml/waveq3d/waveq3d.h>
#include <usml/types/seq_vector.h>
#include <iostream>
#include <iomanip>
#include <fstream>

BOOST_AUTO_TEST_SUITE(refraction_test)

using namespace boost::unit_test;
using namespace usml::waveq3d;

/**
 * @ingroup waveq3d_test
 * @{
 */

static const double time_step = 0.1 ;
static const seq_log freq(10e3, 10e3, 1);

/**
 * In this test, an isovelocity ocean (1500 m/s) is used to verify the
 * accuracy of those terms the ray equations that do not depend on the
 * gradient of the sound speed.  The factors of "r" in these equations are
 * all on the order of the radius of Earth, which is over six million meters.
 * Prior to conducting this test, it was unclear if double precision numbers
 * would be accurate enough to compute small differences in positions that
 * included such large numbers.
 *
 * Four horizontal rays were launched from 45N 45W, at a depth of 1000 meters,
 * with azimuths of 0, 30, 60, and 90 degrees, and a time step of 100
 * milliseconds.  In this scenario, the depth of each ray appears to curve
 * toward the ocean surface and strike it 75 seconds later, at a range of
 * around 110 km.  But, in reality, the rays are actually traveling in a
 * straight line; the earth's surface is curving down to meet it.
 *
 * The accuracy of the ray path locations were computed by converting the
 * latitude, longitude, and altitude of each ray back into geocentric
 * (Cartesian) coordinates and then measuring the distance from the analytic
 * form of straight line, as a function of time, in the geocentric system.
 * The equations for this comparison are
 *  * \f[
 *          x(t) = r(t) \sin{\theta(t)} \cos{\phi(t)} ,
 * \f]\f[
 *          y(t) = r(t) \sin{\theta(t)} \sin{\phi(t)} ,
 * \f]\f[
 *          z(t) = r(t) \cos{\theta(t)} ,
 * \f]\f[
 *          x_a(t) = x(0) + n_x(0) \: c t ,
 * \f]\f[
 *          y_a(t) = y(0) + n_y(0) \: c t ,
 * \f]\f[
 *          z_a(t) = z(0) + n_z(0) \: c t ,
 * \f]\f[
 * n_x = -\sin{\phi(0)} \sin{\varphi(0)} - \cos{\theta(0)} \cos{\phi(0)} \cos{\varphi(0)}
 * \f]\f[
 * n_y = \cos{\phi(0)} \sin{\varphi(0)} - \cos{\theta(0)} \sin{\phi(0)} \cos{\varphi(0)}
 * \f]\f[
 * n_z = \sin{\theta(0)} \cos{\varphi(0)}
 * \f]
 * where
 *      - \f$(x(t), y(t), z(t))\f$ are the geocentric coordinates of the
 *        modeled ray path as a function of time,
 *      - \f$(x_a(t), y_a(t), z_a(t))\f$ are the geocentric coordinates
 *        of the analytic ray path as a function of time, and
 *      - \f$(n_x(0), n_y(0), n_z(0))\f$ are the initial direction in
 *        geocentric coordinates.
 *
 * The difference between the altitude predicted by the model and the analytic
 * solution were also computed using
 * \f[
 * r_a(t) = r(0) sec \left[ arctan \left( \frac{c t}{r(0)} \right) \right] \:.
 * \f]
 * The maximum deviation of any modeled ray from the analytic result
 * is required to be less than 1 millimeter.  If this result is true,
 * we'll conclude that double precision numbers are accurate enough to
 * compute ray paths in spherical Earth coordinates.  A BOOST error
 * is thrown if this condition fails.
 *
 * @xref S.M. Reilly, M.S. Goodrich, "Geodetic Acoustic Rays in the
 * Time Domain, Comprehensive Test Results", Alion Science and Technology,
 * Norfolk, VA, September, 2006.
 */
BOOST_AUTO_TEST_CASE(refraction_isovelocity) {
    cout << "=== refraction_test: refraction_isovelocity ===" << endl;

    // initialize propagation model

    const double c0 = 1500.0;
    profile_model* profile = new profile_linear(c0);
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(5000.0);
    ocean_model ocean(surface, bottom, profile);

    wposition1 pos(45.0, -45.0, -1000.0);
    seq_linear de(0.0, 1.0, 1); // along the tangent plane
    seq_linear az(0.0, 30.0, 90.0); // due north, east, and in between

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    // compute initial position in cartesian coordinates

    double rho = pos.rho();
    double st = sin(pos.theta());
    double ct = cos(pos.theta());
    double sp = sin(pos.phi());
    double cp = cos(pos.phi());

    const double x0 = rho * st * cp;
    const double y0 = rho * st * sp;
    const double z0 = rho * ct;

    // compute initial direction in cartesian coordinates.

    vector<double> east = az;
    east = sin(to_radians(east));
    vector<double> north = az;
    north = cos(to_radians(north));

    // assume initial "up" coordinate is always zero

    vector<double> x_dir = -sp * east - ct * cp * north;
    vector<double> y_dir = cp * east - ct * sp * north;
    vector<double> z_dir = st * north;

    // initialize analytic solution for depth vs. time

    const double r0 = rho;

    // initialize output to spreadsheet file

    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_isovelocity.csv";
    std::ofstream os(name);
    cout << "writing tables to " << name << endl;
    os << "time,x,y,z,d";
    for (size_t n = 0; n < az.size(); ++n) {
        os << ",x" << az(n)
           << ",y" << az(n)
           << ",z" << az(n)
           << ",d" << az(n);
    }
    os << endl;

    // compute modeled and analytic depth until surface is hit

    double max_error = 0.0;
    while (wave.curr()->position.altitude(0,0) < -10.0) {
        double time = wave.time();

        // compute analytic solution for (x,y,z) vs. time

        vector<double> x;
        x = x0 + c0 * time * x_dir;
        vector<double> y;
        y = y0 + c0 * time * y_dir;
        vector<double> z;
        z = z0 + c0 * time * z_dir;

        // compute analytic solution for depth vs. time

        double d_analytic = r0 / cos(atan(c0 * time / r0))
                - wposition::earth_radius;

        // check answer and write to spreadsheet file

        os << time << "," << x(0) << "," << y(0) << "," << z(0)
                << "," << d_analytic;
        for (size_t n = 0; n < az.size(); ++n) {

            // decode model's solution for (x,y,z) vs. time

            rho = wave.curr()->position.rho(0, n);
            st = sin(wave.curr()->position.theta(0, n));
            ct = cos(wave.curr()->position.theta(0, n));
            sp = sin(wave.curr()->position.phi(0, n));
            cp = cos(wave.curr()->position.phi(0, n));

            double x_model = rho * st * cp;
            double y_model = rho * st * sp;
            double z_model = rho * ct;

            double dx = x_model - x(n);
            max_error = max(max_error, fabs(dx));
            double dy = y_model - y(n);
            max_error = max(max_error, fabs(dy));
            double dz = z_model - z(n);
            max_error = max(max_error, fabs(dz));

            os << "," << x_model << "," << y_model << "," << z_model;
            BOOST_CHECK(fabs(dx) < 1e-3);
            BOOST_CHECK(fabs(dy) < 1e-3);
            BOOST_CHECK(fabs(dz) < 1e-3);

            // decode model's solution for depth vs. time

            double alt_model = wave.curr()->position.altitude(0, n);
            double dd = alt_model - d_analytic;
            max_error = max(max_error, fabs(dd));
            os << "," << alt_model;
            BOOST_CHECK(fabs(dd) < 1e-3);

        }
        os << endl;

        // move wavefront to next time step

        wave.step();
    }
    cout << "wave breaks surface around " << wave.time() << " secs" << endl
         << "max error = " << max_error << " meters" << endl;
}

/**
 * In this test, an ocean with a small amount of downward refraction is used
 * to verify the model's ability to follow great circle routes along the
 * earth's surface.  Great circle routes, the shortest distance between two
 * points along the Earth's surface, are frequently used in aviation and
 * other long distance navigation problems.  The amount downward refraction
 * needed to test this feature is computed in terms of a "flat Earth"
 * correction given by
 * \f[
 *          c(r) = \frac{r}{R} \: c(z)
 * \f]
 * where
 *      - \f$c(z)\f$ is the speed of sound as a function of depth on a flat
 *        Earth (constant for this test);
 *      - \f$r\f$ is the radial distance from the center of Earth;
 *      - \f$R\f$ is the radius of Earth's curvature in this area of operations; and
 *      - \f$c(r)\f$ is the speed of sound as a function of radial distance
 *        from the center of Earth.
 *
 * Four horizontal rays are launched from 45N 45W, at a depth of 1000 meters,
 * with azimuths of 0, 30, 60, and 90 degrees, for 1000 seconds, with
 * a time step of 100 milliseconds.  The accuracy of the greate circle routes
 * is computed by converting the latitude, longitude, and altitude of each
 * ray back into a great circle azimuth at the point of origin
 * \f[
 *      \varphi_a(t) = arctan \left[
 *	\frac{  \cos{\chi(t)} \sin(\phi(0)-\phi(t))}{ \cos{\chi(0)} \sin{\chi(t)}
 *      - \sin{\chi(0)} \cos{\chi(t)} \cos(\phi(0)-\phi(t)) } \right]
 * \f]
 * where
 *      - \f$(\chi(t), \phi(t))\f$ are latitude and longitude as a function of time, and
 *      - \f$\varphi_a(t)\f$ is the analytic great circle azimuth for a target
 *        at \f$(\chi(t), \phi(t))\f$.
 *
 * The maximum deviation of any modeled ray from the analytic result
 * is required to be less than 1 millimeter and 0.001 degree.  If this
 * result is true, we'll conclude that the model accurately follows
 * great circle routes.  A BOOST error is thrown if this condition fails.
 *
 * @xref S.M. Reilly, M.S. Goodrich, "Geodetic Acoustic Rays in the
 * Time Domain, Comprehensive Test Results", Alion Science and Technology,
 * Norfolk, VA, September, 2006.
 * @xref E. Williams, "Aviation Formulary V1.43",
 * http://williams.best.vwh.net/avform.htm , July 2010.
 */
BOOST_AUTO_TEST_CASE(refraction_great_circle) {
    cout << "=== refraction_test: refraction_great_circle ===" << endl;

    // initialize propagation model

    const double c0 = 1500.0;
    profile_model* profile = new profile_linear(c0);
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(5000.0);
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    double lat1 = 45.0;
    double lng1 = -45.0;
    double alt1 = -1000.0;

    wposition1 pos(lat1, lng1, alt1);
    seq_linear de(0.0, 1.0, 1);
    seq_linear az(0.0, 30.0, 90.0); // due north, east, and in between

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    // initialize output to spreadsheet file

    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_great_circle.csv";
    std::ofstream os(name);
    cout << "writing tables to " << name << endl;
    os << "time";
    for (size_t n = 0; n < az.size(); ++n) {
        os << ",lat" << az(n) << ",long" << az(n)
           << ",dalt" << az(n) << ",dbear" << az(n);
    }
    os << endl;

    // compute modeled and analytic depth until surface is hit

    lat1 *= M_PI / 180;
    lng1 *= M_PI / 180;
    double max_d_alt = 0.0;
    double max_d_tc1 = 0.0;

    while (wave.time() < 1000.0) {

        // move wavefront to next time step

        wave.step();
        os << wave.time();

        // check answer and write to spreadsheet file

        for (size_t n = 0; n < az.size(); ++n) {

            // compute altitude: is it constant?

            double alt2 = wave.curr()->position.altitude(0, n);
            double d_alt = alt2 - alt1;
            max_d_alt = max(max_d_alt, fabs(d_alt));

            // compute great circle bearing to origin: is it constant?

            double lat2 = wave.curr()->position.latitude(0, n)
                    * M_PI / 180;
            double lng2 = wave.curr()->position.longitude(0, n)
                    * M_PI / 180;

            double tc1 = -atan2(sin(lng1 - lng2) * cos(lat2),
                    cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lng1 - lng2))
                    * 180 / M_PI;
            double d_tc1 = tc1 - az(n);
            max_d_tc1 = max(max_d_tc1, fabs(d_tc1));

            os << "," << wave.curr()->position.latitude(0, n)
               << "," << wave.curr()->position.longitude(0, n)
               << "," << d_alt << "," << d_tc1;
            BOOST_CHECK(fabs(d_alt) < 1e-3);
            BOOST_CHECK(fabs(d_tc1) < 1e-3);
        }
        os << endl;
    }
    cout << "wave propagates for " << wave.time() << " secs" << endl
         << "max error = " << max_d_alt << " meters and "
         << max_d_tc1 << " degrees" << endl;

}

/**
 * Evaluates the model's ray refraction accuracy using a comparison to the
 * analytic solution for a linear profile. Match conditions of the test
 * and output table format to those in the Reilly/Goodrich report.
 *

 *      Ocean:          Linear: c0 = 1500 m/s, g0 = 1.6e-2 m/s/m
 *                      The profile_model::flat_earth() option turned on.
 *      Position:       1000 meters deep at 00:00N 00:00W
 *      D/E Angles:     0 degrees (horizontal)
 *      AZ Angles:      90 degrees (east)
 *      Time Step:      100 msec
 *

 * In Cartesian coordinates, the analytic solution for this profile
 * is defined by the equation:
 * \f[
 *      z(r) = \frac{1}{g_0} \left[ \frac{1}{a_0}
 *             \sqrt{ 1.0 - ( a_0 g_0 r + sin(\delta_0) )^2 }
 *             -c_0 \right]
 * \f]
 * An exception is thrown if the modeled position is not within 1 millimeter
 * of the analytic result.
 *
 * @xref S.M. Reilly, M.S. Goodrich, "Geodetic Acoustic Rays in the
 * Time Domain, Comprehensive Test Results", Alion Science and Technology,
 * Norfolk, VA, September, 2006.
 */
BOOST_AUTO_TEST_CASE(refraction_linear) {
    cout << "=== refraction_test: refraction_linear ===" << endl;

    // initialize propagation model

    const double angle = 0.0;
    const double ang = to_radians( angle );
    const double z0 = 1000.0;
    const double c0 = 1500.0;
    const double g0 = 0.016;
    const double a0 = cos(ang) / (c0 + g0 * z0);
    const double sinT = sin(ang);

    profile_model* profile = new profile_linear(c0, g0);
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(5000.0);
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    wposition1 pos(0.0, 0.0, -z0);
    seq_linear de(angle, 0.0, 1);
    seq_linear az(90.0, 0.0, 1);

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    // initialize output to spreadsheet file

    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_linear.csv";
    std::ofstream os(name);
    cout << "writing tables to " << name << endl;

    os << "t," << "r," << "theta," << "phi," << "z," << "rng(m),"
       << "rd," << "thd," << "phid,"
       << "alpha," << "beta," << "gamma,"
       << "alphad," << "betad," << "gammad,"
       << "c," << "dcdz,"
       << endl;
    os << std::scientific << std::showpoint << std::setprecision(18);

    // compute modeled and analytic depth until surface is hit

    wposition prev(1, 1);
    prev.rho(0, 0, pos.rho());
    prev.theta(0, 0, pos.theta());
    prev.phi(0, 0, pos.phi());

    double max_error = 0.0;
    double r = 0.0;
    double z = -z0;

    while (wave.time() < 9.0) {

        // compute analytic solution for depth vs. range

        r += 0.5 * (wave.curr()->position.rho(0,0) + prev.rho(0,0))
                * (wave.curr()->position.phi(0,0) - prev.phi(0,0));
        double agr = a0 * g0 * r + sinT;
        z = -(sqrt(1.0 - agr * agr) / a0 - c0) / g0;
        prev = wave.curr()->position;

        // write to spreadsheet file

        os << wave.time() << ','
           << wave.curr()->position.rho(0,0) << ','
           << wave.curr()->position.theta(0,0) << ','
           << wave.curr()->position.phi(0,0) << ','
           << z << ',' << r << ','
           << wave.curr()->pos_gradient.rho(0,0) << ','
           << wave.curr()->pos_gradient.theta(0,0) << ','
           << wave.curr()->pos_gradient.phi(0,0) << ','
           << wave.curr()->ndirection.rho(0,0) << ','
           << wave.curr()->ndirection.theta(0,0) << ','
           << wave.curr()->ndirection.phi(0,0) << ','
           << wave.curr()->ndir_gradient.rho(0,0) << ','
           << wave.curr()->ndir_gradient.theta(0,0) << ','
           << wave.curr()->ndir_gradient.phi(0,0) << ','
           << wave.curr()->sound_speed(0,0) << ','
           << wave.curr()->sound_gradient.rho(0,0) << endl;

        // check answer

        double d_alt = fabs(wave.curr()->position.altitude(0,0) - z);
        max_error = max(max_error, d_alt);
        BOOST_CHECK(d_alt < 1e-3);

        // move wavefront to next time step

        wave.step();
    }
    cout << "max error = " << max_error << " meters" << endl;

}

/**
 * Evaluates the model's ray refraction accuracy using a comparison to the
 * analytic solution for a profile where the square of the index of refraction
 * is linear. Match conditions of the test and output table format to those in
 * the Reilly/Goodrich report.  These are also the specific conditions used by
 * Jensen, Kupperman, et. al. in their formulation of the analytic
 * ray tracing solution for the N^2 linear profile.
 *

 *      Ocean:          N^2 Linear: c0 = 1550 m/s, g0 = 1.2 1/sec
 *                      The profile_model::flat_earth() option turned on.
 *      Position:       1000 meters deep at 00:00N 00:00W
 *      D/E Angles:     50 degrees (up)
 *      AZ Angles:      90 degrees (east)
 *      Time Step:      100 msec
 *

 * In Cartesian coordinates, the analytic solution for this profile
 * is defined by the equation:
 * \f[
 *      z(r) = z_0 + \frac{ 2*g_0/c_0 }{ (2 a_0 c_1)^2 } r^2 -r tan(\delta_0)
 * \f]
 * where:
 *      - \f$ r \f$ = horizontal range
 *      - \f$ z(r) \f$ = depth as a function of range
 *      - \f$ z_0 \f$ = initial depth
 *      - \f$ \delta_0 \f$ = launch angle
 *      - \f$ c_1 \f$ = sound speed at initial depth
 *      - \f$ g_0 \f$ =  sound speed gradient at z=0.
 *      - \f$ a_0 \f$ = Snell coefficient = \f$ cos(\delta_0)/c_0 \f$
 *
 * An exception is thrown if the modeled position is not within 200 millimeters
 * of the analytic result.  Note that this threshold is much different
 * than the linear case where the maximum error is significantly
 * less than 1 millimeter.  The maximum error shrinks to about 14 millimeters
 * if a step size of 10 milliseconds is used.  It appears that the larger
 * step size undersamples the sound velocity profile field.
 *
 * @xref S.M. Reilly, M.S. Goodrich, "Geodetic Acoustic Rays in the
 * Time Domain, Comprehensive Test Results", Alion Science and Technology,
 * Norfolk, VA, September, 2006.
 * @xref F.B. Jensen, W.A. Kuperman, M.B. Porter, H. Schmidt,
 * "Computational Ocean Acoustics", equation (3.128).
 */
BOOST_AUTO_TEST_CASE(refraction_n2_linear) {
    cout << "=== refraction_test: refraction_n2_linear ===" << endl;

    // initialize propagation model

    const double angle = 50.0;
    const double ang = to_radians( angle ) ;
    const double z0 = 1000.0;
    const double c0 = 1550.0;
    const double g0 = 1.2 ;
    const double a0 = cos(ang) / (c0 / sqrt(1.0 + 2*g0/c0 * z0));

    profile_model* profile = new profile_n2(c0, g0) ;
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(5000.0);
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    wposition1 pos(0.0, 0.0, -z0);
    seq_linear de(angle, 0.0, 1);
    seq_linear az(90.0, 0.0, 1);

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << endl;

    // initialize output to spreadsheet file

    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_n2_linear.csv";
    std::ofstream os(name);
    cout << "writing tables to " << name << endl;

    os << "t," << "r," << "theta," << "phi," << "z," << "rng(m),"
       << "rd," << "thd," << "phid,"
       << "alpha," << "beta," << "gamma,"
       << "alphad," << "betad," << "gammad,"
       << "c," << "dcdz,"
       << endl;
    os << std::scientific << std::showpoint << std::setprecision(18);

    // compute modeled and analytic depth until max range hit

    wposition prev(1, 1);
    prev.rho(0, 0, pos.rho());
    prev.theta(0, 0, pos.theta());
    prev.phi(0, 0, pos.phi());

    double max_error = 0.0;
    double r = 0.0;
    double z = 0.0;

    while (z > -2000.0) {
        double time = wave.time();

        // compute analytic solution for depth vs. range

        r += 0.5 * (wave.curr()->position.rho(0,0) + prev.rho(0,0))
                * (wave.curr()->position.phi(0,0) - prev.phi(0,0));
        z = -(2*g0/c0 / (4.0 * a0 * a0 * c0 * c0) * r * r
                - r * tan(ang) + z0);
        prev = wave.curr()->position;

        // write to spreadsheet file

        os << time << ','
           << wave.curr()->position.rho(0,0) << ','
           << wave.curr()->position.theta(0,0) << ','
           << wave.curr()->position.phi(0,0) << ','
           << z << ',' << r << ','
           << wave.curr()->pos_gradient.rho(0,0) << ','
           << wave.curr()->pos_gradient.theta(0,0) << ','
           << wave.curr()->pos_gradient.phi(0,0) << ','
           << wave.curr()->ndirection.rho(0,0) << ','
           << wave.curr()->ndirection.theta(0,0) << ','
           << wave.curr()->ndirection.phi(0,0) << ','
           << wave.curr()->ndir_gradient.rho(0,0) << ','
           << wave.curr()->ndir_gradient.theta(0,0) << ','
           << wave.curr()->ndir_gradient.phi(0,0) << ','
           << wave.curr()->sound_speed(0,0) << ','
           << wave.curr()->sound_gradient.rho(0,0) << endl;

        // check answer

        double d_alt = fabs(wave.curr()->position.altitude(0,0) - z);
        max_error = max(max_error, d_alt);
        BOOST_CHECK_SMALL( wave.curr()->position.altitude(0,0)-z, 2.0 ) ;
        // cout << "r=" << r << " z=" << z << " d_alt=" << d_alt << endl ;

        // move wavefront to next time step

        wave.step();
    }
    cout << "wave reaches " << z << " m depth around "
         << wave.time() << " secs" << endl
         << "max error = " << max_error << " meters" << endl;
}

/**
 * Evaluates the model's ray refraction accuracy using a comparison to the
 * a deep sound channel with a hyperbolic cosine profile. Match conditions
 * of the test and output table format to those in the Reilly/Goodrich report.
 *

 *      Ocean:          Catenary: c1 = 1500 m/s, g1 = 1500 m/s
 *                      The profile_model::flat_earth() option turned on.
 *      Position:       1000 meters deep at 00:00N 00:00W
 *      D/E Angles:     -3 degrees (down)
 *      AZ Angles:      90 degrees (east)
 *      Time Step:      100 msec
 *

 * This test is designed to isolate the effects of the radial component
 * of the sound speed gradient.
 * \f[
 *      z(r) = z_0 + c_1 arccosh \left[ sqrt \left( \frac{
 *             sin^2(\delta_0) cos\left( \frac{2r}{c_1} \right)-1 }
 *             {2 cos^2(\delta_0)} \right) \right]
 * \f]
 * where:
 *      - \f$ r \f$ = horizontal range
 *      - \f$ z(r) \f$ = depth as a function of range
 *      - \f$ z_0 \f$ = depth of sound channel axis
 *      - \f$ c_1 \f$ = sound speed at sound channel axis
 *      - \f$ \delta_0 \f$ = launch angle
 *
 * An exception is thrown if the modeled position is not within 2 meters
 * of the analytic result.  Note that this threshold is much different
 * than the linear case where the maximum error is significantly
 * less than 1 millimeter.  The catenary error oscillates between
 * positive and negative and grows with increasing range.  For a -3 degree
 * launch angle, the error has grown to +/- 2 meters by the time the
 * ray reach a range of 100,000 meters.  The maximum error
 * shrinks to about 1.9 millimeters if a step size of 10 milliseconds is
 * used.  It appears that the larger step size undersamples the sound
 * velocity profile field.
 *
 * @xref S.M. Reilly, M.S. Goodrich, "Geodetic Acoustic Rays in the
 * Time Domain, Comprehensive Test Results", Alion Science and Technology,
 * Norfolk, VA, September, 2006.
 */
BOOST_AUTO_TEST_CASE(refraction_catenary) {
    cout << "=== refraction_test: refraction_catenary ===" << endl;

    // initialize propagation model

    const double angle = -3.0;
    const double ang = to_radians( angle );
    const double z0 = 1000.0;
    const double c1 = 1500.0;
    const double g1 = 1500.0;
    const double sinT2 = sin(ang) * sin(ang);
    const double cosT2 = cos(ang) * cos(ang);

    profile_model* profile = new profile_catenary(c1, g1, z0);
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(5000.0);
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    wposition1 pos(0.0, 0.0, -z0);
    seq_linear de(angle, 0.0, 1);
    seq_linear az(90.0, 0.0, 1);

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    wposition prev(1, 1);
    prev.rho(0, 0, pos.rho());
    prev.theta(0, 0, pos.theta());
    prev.phi(0, 0, pos.phi());

    // initialize output to spreadsheet file

    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_catenary.csv";
    std::ofstream os(name);
    cout << "writing tables to " << name << endl;

    os << "t," << "r," << "theta," << "phi," << "z," << "rng(m),"
       << "rd," << "thd," << "phid,"
       << "alpha," << "beta," << "gamma,"
       << "alphad," << "betad," << "gammad,"
       << "c," << "dcdz,"
       << endl;
    os << std::scientific << std::showpoint << std::setprecision(18);

    // compute modeled and analytic depth until surface is hit

    double max_error = 0.0;
    double r = 0.0;
    int n = 1;
    double sign = -1.0;
    while (r < 100000) {
        double time = wave.time();

        // compute analytic solution for depth vs. range

        r += 0.5 * (wave.curr()->position.rho(0,0) + prev.rho(0,0))
                * (wave.curr()->position.phi(0,0) - prev.phi(0,0));
        double t = 2.0 * r / c1;
        if (t >= TWO_PI * n) {
            ++n;
            sign *= -1.0;
        }
        double s = -0.5 * ((sinT2 * cos(t) - 1.0) / cosT2 - 1.0);
        double z = -(z0 - sign * c1 * acosh(sqrt(s)));
        prev = wave.curr()->position;

        // write to spreadsheet file

        os << time << ','
           << wave.curr()->position.rho(0,0) << ','
           << wave.curr()->position.theta(0,0) << ','
           << wave.curr()->position.phi(0,0) << ','
           << z << ',' << r << ','
           << wave.curr()->pos_gradient.rho(0,0) << ','
           << wave.curr()->pos_gradient.theta(0,0) << ','
           << wave.curr()->pos_gradient.phi(0,0) << ','
           << wave.curr()->ndirection.rho(0,0) << ','
           << wave.curr()->ndirection.theta(0,0) << ','
           << wave.curr()->ndirection.phi(0,0) << ','
           << wave.curr()->ndir_gradient.rho(0,0) << ','
           << wave.curr()->ndir_gradient.theta(0,0) << ','
           << wave.curr()->ndir_gradient.phi(0,0) << ','
           << wave.curr()->sound_speed(0,0) << ','
           << wave.curr()->sound_gradient.rho(0,0) << endl;

        // check answer

        double d_alt = fabs(wave.curr()->position.altitude(0,0) - z);
        max_error = max(max_error, d_alt);
        BOOST_CHECK_SMALL( d_alt, 2.0 );
        // cout << "r=" << r << " t=" << t << " s=" << s << " z=" << z << " d_alt=" << d_alt << endl ;

        // move wavefront to next time step

        wave.step();
    }
    cout << "wave propagates for " << wave.time() << " secs" << endl
         << "max error = " << max_error << " meters" << endl;
}

/**
 * Compares modeled ray paths to an analytic solution for the Munk profile.
 * The profile_munk model creates an idealized representation
 * of a deep sound (SOFAR) channel.
 *

 *      Ocean:          Munk Profile: z1=1300, B=1300, c1=1500, e=7.37e-3
 *                      The profile_model::flat_earth() option turned on.
 *      Position:       1300 meters deep at 45:00N 45:00W
 *      D/E Angles:     -14 to 14 degrees (avoids surface reflection)
 *      AZ Angles:      0 degrees (north)
 *      Time Step:      100 msec
 *

 * In this test, rays are launched from the deep sound channel axis,
 * pass through two vertices, and then return to the axis (heading in it's
 * original direction). The process is then repeated for a second cycle.
 * The range required to complete each cycle is compared to an analytic
 * solution computed using the munk_range_compute.m routine. Additionally,
 * the logic to detect upper and lower verticies is checked. At the end of
 * each cycle, the upper and lower verticies should both have increased
 * by one. A boost failure is issued if this check fails.
 *
 * Munk derives a power series expression for the cycle range in his
 * 1974 paper. But, it appears that the number of terms in the expansion
 * are too small to model the range accurately enough for this test.
 * Jensen, Kuperman, et. al. use this scenario to look for false caustics
 * in the ray trace plot, with an emphasis toward looking problems in
 * the cubic spline interpolators.  The parameters for this test were selected
 * to match Figure 3.19 in Jensen, Kuperman, et. al. which are actually
 * defined in the KRAKEN Normal Mode Program documentation.
 *
 * @xref W. H. Munk, "Sound channel in an exponentially stratified ocean,
 * with application to SOFAR", J. Acoust. Soc. Amer. (55) (1974) pp. 220-226.
 * @xref F.B. Jensen, W.A. Kuperman, M.B. Porter, H. Schmidt,
 * "Computational Ocean Acoustics", Section 3.6.4.
 * @xref M.B. Porter, "The KRAKEN Normal Mode Program (DRAFT)",
 * Section called "A Deep Water Problem: the Munk Profile", Oct 1997
 * http://oalib.hlsresearch.com/Modes/AcousticsToolbox/manual_html/node8.html
 */
BOOST_AUTO_TEST_CASE(refraction_munk_range) {
    cout << "=== refraction_test: refraction_munk_range ===" << endl;
    const char* ncname_wave = USML_TEST_DIR "/waveq3d/test/refraction_munk_range.nc";
    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_munk_range.csv";

    // analytic solution for cycle ranges for angles -14:14 degrees;
    // computed using the munk_range_compute.m routine

    static const double cycle_ranges[] = {
	64977.771509,
	62686.699943,
	60536.790347,
	58539.834823,
	56706.277890,
	55044.418981,
	53559.948084,
	52255.876772,
	51132.827760,
	50189.572079,
	49423.683193,
	48832.195747,
	48412.185973,
	48161.238557,
	48077.771909,
	48161.238557,
	48412.185973,
	48832.195747,
	49423.683193,
	50189.572079,
	51132.827760,
	52255.876772,
	53559.948084,
	55044.418981,
	56706.277890,
	58539.834823,
	60536.790347,
	62686.699943,
	64977.771509
    } ;

    // initialize propagation model

    profile_munk* profile = new profile_munk();
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(1e4); // infinitely deep
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    double lat1 = 45.0;
    double lng1 = -45.0;
    double alt1 = -1000.0 ;

    wposition1 pos(lat1, lng1, alt1);
    seq_linear de(-14.0,1.0,14.0);
    seq_linear az(0.0, 0.0, 1);

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    // initialize output to spreadsheet file

    std::ofstream os(name);
    cout << "writing error data to " << name << endl;

    os << "t," << "de," << "Rtheory," << "Rtheory," << "diff," << "diff%,"
       << endl;
    os << std::scientific << std::showpoint << std::setprecision(18);

    // compute wavefronts and write them to disk
    // storing wavefronts allow us to compare ray trace plots to Jensen, et. al.

    cout << "writing wavefronts to " << ncname_wave << endl;
    wave.init_netcdf(ncname_wave);  // open a log file for wavefront data
    wave.save_netcdf();             // write ray data to log file

    // Keep track of the current number of upper and lower vertices
    // these values should increase by one for each cycle
    matrix<std::size_t> up( de.size(), 1 ) ;
    matrix<std::size_t> low( de.size(), 1 ) ;
    up.clear() ;
    low.clear() ;
    vector<double> loop( de.size() ) ;
    loop.clear() ;

    double max_error = 0.0 ;
    while (wave.time() < 95.0) {

        // increment wavefront by one time step

        wave.step();
        wave.save_netcdf();         // write ray data to log file

        // compare to analytic solution if crossing axis

        for ( size_t d=0 ; d < de.size() ; ++d ) {

            // look for axis crossings in same direction as launch angle
            // this is how we recognize that one cycle is complete

            const double Hprev = wave.prev()->position.altitude(d,0) - alt1 ;
            const double Hcurr = wave.curr()->position.altitude(d,0) - alt1 ;
            const double Hnext = wave.next()->position.altitude(d,0) - alt1 ;
            if ( Hcurr*Hnext < 0.0 &&
                 wave.curr()->ndirection.rho(d,0) * de(d) > 0.0 )
            {
                // find the analytic result for this launch angle

                loop(d) += 1.0 ;
                const double Rtheory = loop(d) * cycle_ranges[d] ;

                // quadratic interpolation of wavefront range

                const double Rprev = wave.prev()->position.rho(d,0)
                    * to_radians(wave.prev()->position.latitude(d,0)-lat1) ;
                const double Rcurr = wave.curr()->position.rho(d,0)
                    * to_radians(wave.curr()->position.latitude(d,0)-lat1) ;
                const double Rnext = wave.next()->position.rho(d,0)
                    * to_radians(wave.next()->position.latitude(d,0)-lat1) ;

                double h = (Hnext-Hprev) ;
                const double slope = (Rnext-Rprev) / h  ;
                h *= 0.5 ;
                const double curve = (Rnext-2.0*Rcurr+Rprev) / (h*h)  ;
                const double dx = -Hcurr ;
                const double Rmodel = Rcurr + slope*dx + 0.5*curve*dx*dx ;

                // store error to spreadsheet file

                os << wave.time()
                   << "," << de(d) << "," << Rtheory << "," << Rmodel
                   << "," << (Rmodel-Rtheory)
                   << "," << ((Rmodel-Rtheory)/Rtheory*100.0)
                   << endl ;
                BOOST_CHECK_CLOSE( Rtheory, Rmodel, 0.01 ) ;
                std::size_t dup = wave.curr()->upper(d,0) - up(d,0) ;
                std::size_t dlow = wave.curr()->lower(d,0) - low(d,0) ;
                BOOST_CHECK_EQUAL( dup, 1 ) ;
                BOOST_CHECK_EQUAL( dlow, 1 ) ;
                up(d,0) = wave.curr()->upper(d,0) ;
                low(d,0) = wave.curr()->lower(d,0) ;
                max_error = max( max_error, abs(Rmodel-Rtheory) ) ;
            }
        }
    }
    wave.close_netcdf();            // close log file for wavefront data
    cout << "max error = " << max_error << " m" << endl ;
}

/**
 * Compares modeled ray paths to an analytic solution for the Pedersen profile.
 * The profile_pedersen model creates an idealized representation
 * of a deep sound (SOFAR) channel.
 *

 *      Ocean:          N^2 Linear Profile: c0=1550, g0=1.2
 *                      The profile_model::flat_earth() option turned on.
 *      Position:       1000 meters deep at 45:00N 45:00W
 *      D/E Angles:     20 to 50 degrees (avoids surface reflection)
 *      AZ Angles:      0 degrees (north)
 *      Time Step:      100 msec
 *

 * In this test, rays are launched from a deep source, they pass through
 * one vertex, and then return to the axis. The range required to complete
 * one cycle is compared to an analytic solution computed using the
 * pedersen_range_compute.m routine.
 *
 * The profile and source depth parameters were chosen to force the
 * creation of a caustic for launch angles > 44 deg. As Pedersen notes,
 * this profile is not physically realistic at depths greater than 61 meters.
 * But it has been used by many authors, including those listed in the
 * references, to stress propagation loss models at the edge of a shadow zone.
 *
 * @xref M. A. Pedersen, D. F. Gordon, "Normal-Mode and Ray Theory Applied
 * to Underwater Acoustic conditions of Extreme Downward Refraction",
 * J. Acoust. Soc. Am. 51 (1B), 323-368 (June 1972).
 * @xref H. Weinberg, R. E. Keenan, Gaussian ray bundles for modeling high-frequency
 * propagation loss under shallow-water conditions,
 * J. Acoust. Soc. Amer. 100 (1996) 1421.
 * @xref M. B. Porter, H. P. Bucker, Gaussian beam tracing for computing
 * ocean acoustic fields, J. Acoust. Soc. Amer. 93 (1987) 1349.
 */
BOOST_AUTO_TEST_CASE(refraction_pedersen_range) {
    cout << "=== refraction_test: refraction_pedersen_range ===" << endl;
    const char* ncname_wave = USML_TEST_DIR "/waveq3d/test/refraction_pedersen_range.nc";
    const char* name = USML_TEST_DIR "/waveq3d/test/refraction_pedersen_range.csv";

    // analytic solution for cycle ranges for angles 20:2:50 degrees;
    // computed using the pedersen_range_compute.m routine

    static const double cycle_ranges[] = {
	2115.799965,
	2286.528610,
	2446.115431,
	2593.782977,
	2728.811865,
	2850.544337,
	2958.387468,
	3051.815999,
	3130.374953,
	3193.681828,
	3241.428421,
	3273.382397,
	3289.388370,
	3289.368623,
	3273.323545,
	3241.331594,
    } ;

    // initialize propagation model

    const double c0 = 1550 ;
    const double g0 = 1.2 ;
    profile_model* profile = new profile_n2( c0, g0 );
    boundary_model* surface = new boundary_flat();
    boundary_model* bottom = new boundary_flat(1e4); // infinitely deep
    ocean_model ocean(surface, bottom, profile);
    profile->flat_earth(true);

    double lat1 = 45.0;
    double lng1 = -45.0;
    double alt1 = -1000.0 ;

    wposition1 pos(lat1, lng1, alt1);
    seq_linear de(20.0,2.0,50.0);
    seq_linear az(0.0, 0.0, 1);

    wave_queue wave(ocean, freq, pos, de, az, time_step);
    cout << "time step = " << time_step << " secs"
         << " freq = " << freq(0) << " Hz" << endl;

    // initialize output to spreadsheet file

    std::ofstream os(name);
    cout << "writing error data to " << name << endl;

    os << "t," << "de," << "Rtheory," << "Rtheory," << "diff," << "diff%,"
       << endl;
    os << std::scientific << std::showpoint << std::setprecision(18);

    // compute wavefronts and write them to disk
    // storing wavefronts allow us to compare ray trace plots to Jensen, et. al.

    cout << "writing wavefronts to " << ncname_wave << endl;
    wave.init_netcdf(ncname_wave);  // open a log file for wavefront data
    wave.save_netcdf();             // write ray data to log file

    double max_error = 0.0 ;
    while (wave.time() < 4.0) {

        // increment wavefront by one time step

        wave.step();
        wave.save_netcdf();         // write ray data to log file

        // compare to analytic solution if crossing axis

        for ( size_t d=0 ; d < de.size() ; ++d ) {

            // look for axis crossings
            // this is how we recognize that one cycle is complete

            const double Hprev = wave.prev()->position.altitude(d,0) - alt1 ;
            const double Hcurr = wave.curr()->position.altitude(d,0) - alt1 ;
            const double Hnext = wave.next()->position.altitude(d,0) - alt1 ;
            if ( Hcurr*Hnext < 0.0 ) {

                // find the analytic result for this launch angle

                const double Rtheory = cycle_ranges[d] ;

                // quadratic interpolation of wavefront range

                const double Rprev = wave.prev()->position.rho(d,0)
                    * to_radians(wave.prev()->position.latitude(d,0)-lat1) ;
                const double Rcurr = wave.curr()->position.rho(d,0)
                    * to_radians(wave.curr()->position.latitude(d,0)-lat1) ;
                const double Rnext = wave.next()->position.rho(d,0)
                    * to_radians(wave.next()->position.latitude(d,0)-lat1) ;

                double h = (Hnext-Hprev) ;
                const double slope = (Rnext-Rprev) / h  ;
                h *= 0.5 ;
                const double curve = (Rnext-2.0*Rcurr+Rprev) / (h*h)  ;
                const double dx = -Hcurr ;
                const double Rmodel = Rcurr + slope*dx + 0.5*curve*dx*dx ;

                // store error to spreadsheet file

                os << wave.time()
                   << "," << de(d) << "," << Rtheory << "," << Rmodel
                   << "," << (Rmodel-Rtheory)
                   << "," << ((Rmodel-Rtheory)/Rtheory*100.0)
                   << endl ;
                BOOST_CHECK_CLOSE( Rtheory, Rmodel, 0.12 );
                max_error = max( max_error, abs(Rmodel-Rtheory) ) ;
            }
        }
    }
    wave.close_netcdf();            // close log file for wavefront data
    cout << "max error = " << max_error << " m" << endl ;
}

/**
 * Tests the ability to produce appropriate wavefile data for
 * a surface ducting environment.
 */
BOOST_AUTO_TEST_CASE( surface_duct_test ) {
    cout << "=== refraction_test: surface_duct_test ===" << endl;
    const char* ncname_wave = USML_TEST_DIR "/waveq3d/test/refraction_surface_duct.nc";
    const char* csvname = USML_TEST_DIR "/waveq3d/test/refraction_surface_duct.csv";

    // environmental parameters
    data_grid<double,1>* sound_profile;
    const seq_vector* axis[1];
    axis[0] = new seq_linear( wposition::earth_radius, -0.5, 1000 ) ;
    sound_profile = new data_grid<double,1>(axis) ;
    size_t index[1];
    for(int i=0; i < axis[0]->size(); ++i){
        index[0] = i;
        double value = 1500.0;
        if( (*axis[0])[i] > wposition::earth_radius-150.0 ) { value+=(0.016*i) ;}
        if( (*axis[0])[i] <= wposition::earth_radius-150.0 && (*axis[0])[i] > wposition::earth_radius-250.0 ) { value-=(0.1*(i-300)-4.8) ;}
        if( (*axis[0])[i] <= wposition::earth_radius-250.0 ) { value-=(0.01*(i-500) + 15.2) ; }
        sound_profile->data(index, value);
    }
    sound_profile->interp_type(0, GRID_INTERP_LINEAR);
    sound_profile->edge_limit(0, true);
    profile_model* profile = new profile_grid<double,1>(sound_profile);

    cout << "writing sound speed profile to " << csvname << endl;
    matrix<double> speed(1,1);
    wposition test( 1, 1, 45.0, -45.0, 0.0 );
    std::ofstream file(csvname);
    file << "depth,speed,interp" << endl;
    for(int j=0; j <axis[0]->size(); ++j){
        index[0] = j;
        test.rho( 0, 0, (*axis[0])(j) );
        profile->sound_speed( test, &speed );
        file << (*axis[0])(j)-wposition::earth_radius << "," << sound_profile->data(index) << "," << speed(0,0) << endl;
    }

//    profile_model* profile = new profile_linear(1500.0,0.016);
    profile->flat_earth(true);
    boundary_model* bottom = new boundary_flat(1e4);
    boundary_model* surface = new boundary_flat();
    ocean_model ocean(surface, bottom, profile);

    // test parameters
    double lat = 45.0;
    double lon = -45.0;
    wposition1 source( lat, lon, -40.0 );
//    seq_rayfan de(-10.0, 10.0, 51);
    seq_rayfan de ;
    seq_linear az( 0.0, 0.0, 1 );

    wave_queue wave(ocean, freq, source, de, az, time_step);

    cout << "writing wavefronts to " << ncname_wave << endl;
    wave.init_netcdf(ncname_wave);  // open a log file for wavefront data
    wave.save_netcdf();             // write ray data to log file
    while (wave.time() < 25.0) {
        wave.step();
        wave.save_netcdf();
    }
    wave.close_netcdf();

    delete axis[0] ;
}

/// @}

BOOST_AUTO_TEST_SUITE_END()


//****************************************************************************
// (c) 2008, 2009 by the openOR Team
//****************************************************************************
// The contents of this file are available under the GPL v2.0 license
// or under the openOR comercial license. see
//   /Doc/openOR_free_license.txt or
//   /Doc/openOR_comercial_license.txt
// for Details.
//****************************************************************************
//! OPENOR_INTERFACE_FILE(Image_ImageData)
//****************************************************************************
/**
* @file
* @author Christian Winne
* \ingroup Image_ImageData
*/

#ifndef openOR_Image_Image2DSize_hpp
#define openOR_Image_Image2DSize_hpp

#include <boost/tr1/memory.hpp>

#include <openOR/Plugin/CreateInterface.hpp>
#include <openOR/Math/vector.hpp>
#include <openOR/Math/matrix.hpp>
#include <openOR/Utility/Types.hpp>

namespace openOR {
	namespace Image {

		/**
		* \brief SliceSize
		* \ingroup Image_ImageData
		*/
		struct SliceSize
		{
			virtual Math::Vector2d sizeMM() const = 0;
			virtual void setSizeMM(const Math::Vector2d& sizeMM) = 0;
		};

		/**
		* \brief Interface that describes the size of an image.
		* The image is described by its pixel count and its size in millimeter.
		* \ingroup Image_ImageData
		*/
		struct Image2DSize : SliceSize
		{
			virtual Math::Vector2ui size() const = 0;
			virtual void setSize(const Math::Vector2ui& size) = 0;
		};

	}

	/*
	* Getter for the voxel width in mm
	* @return The voxel width in mm
	*/
	inline const double pixelWidthMM(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->sizeMM()(0) / pImageSize->size()(0); }

	/**
	* Getter for the voxel height in mm
	* @return The voxel height in mm
	*/
	inline const double pixelHeightMM(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->sizeMM()(1) / pImageSize->size()(1); }

	inline Math::Vector2d pixelSizeMM(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize)
	{
		Math::Vector2d size;
		size(0) = pixelWidthMM(pImageSize);
		size(1) = pixelHeightMM(pImageSize);
		return size;
	}

	/**
	* Getter for the width of the whole volume object in mm
	* @return The width of the volume object
	*/
	inline const double getMMWidth(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->sizeMM()(0); }

	/**
	* Getter for the height of the whole volume object in mm
	* @return The height of the volume object
	*/
	inline const double getMMHeight(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->sizeMM()(1); }

	inline Math::Vector2d getMMSize(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->sizeMM(); }

	/**
	* Getter for the width of the whole volume object in indices
	* @return The width of the volume object
	*/
	inline const uint getWidth(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->size()(0); }

	/**
	* Getter for the height of the whole volume object in indices
	* @return The height of the volume object
	*/
	inline const uint getHeight(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->size()(1); }

	/**
	* Getter for the depth of the whole volume object in indices
	* @return The depth of the volume object
	*/
	inline Math::Vector2ui getSize(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize) { return pImageSize->size(); }

	/**
	* Checks whether given relative mm-based point is outside of volume
	* @param  vecPoint The point to check for
	* @return  True, if the point is outside the volume
	*/
	inline const bool isOutside(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2d&) {

		assert(true);
		return true;
	}

	/**
	* Checks whether given relative index-based point is outside of volume
	* @param vecPoint The point to check for
	* @return  True, if the point is outside the volume
	*/
	inline const bool isOutside(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2i&) {

		assert(true);
		return true;
	}

	/**
	* Returns the absolute BoundingBox around the volume.
	* @return   The absolute BoundingBox around the volume
	*/
	//Math::BoundingBoxD getBoundingBox(std::tr1::shared_ptr<const Volume> pVolume) {}

	/**
	* Returns index position corresponding to the given mm position.
	* @return The index position
	*/

	inline uint convertToIndex(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2ui& vecPosition)
	{
		return vecPosition(1) * pImageSize->size()(0) + vecPosition(0);
	}

	inline uint convertToIndex(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2i& vecPosition)
	{
		assert(vecPosition(0) >= 0 && vecPosition(0) < static_cast<int>(pImageSize->size()(0)) &&
			vecPosition(1) >= 0 && vecPosition(1) < static_cast<int>(pImageSize->size()(1)));

		Math::Vector2ui vec;
		vec(0) = static_cast<uint>(vecPosition(0));
		vec(1) = static_cast<uint>(vecPosition(1));
		return convertToIndex(pImageSize, vec);
	}

	inline Math::Vector2i convertToIndexPosition(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2d& vecPosition)
	{
		Math::Vector2i result;
		result(0) = static_cast<int>(vecPosition(0) * pImageSize->size()(0) / pImageSize->sizeMM()(0));
		result(1) = static_cast<int>(vecPosition(1) * pImageSize->size()(1) / pImageSize->sizeMM()(1));
		return result;
	}

	/**
	* Returns internal memory index corresponding to the given index/mm position.
	* @return The index
	*/
	inline uint convertToIndex(std::tr1::shared_ptr<const Image::Image2DSize> pImageSize, const Math::Vector2d& vecPosition)
	{
		return convertToIndex(pImageSize, convertToIndexPosition(pImageSize, vecPosition));
	}

}

OPENOR_CREATE_INTERFACE(openOR::Image::SliceSize)
	Math::Vector2d sizeMM() const { return adaptee()->sizeMM(); }
void setSizeMM(const Math::Vector2d& size) { adaptee()->setSizeMM(size); }
OPENOR_CREATE_INTERFACE_END

	OPENOR_CREATE_INTERFACE(openOR::Image::Image2DSize)
	Math::Vector2d sizeMM() const { return adaptee()->sizeMM(); }
void setSizeMM(const Math::Vector2d& size) { adaptee()->setSizeMM(size); }
Math::Vector2ui size() const { return adaptee()->size(); }
void setSize(const Math::Vector2ui& size) { adaptee()->setSize(size); }
OPENOR_CREATE_INTERFACE_END

#endif


// Copyright (c) 2012-2014 The Bitcoin Core developers
// Copyright (c) 2017 The PIVX developers
// Copyright (c) 2019 The CryptoDev developers
// Copyright (c) 2019 The CryptoHashTank developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "libzerocoin/bignum.h"
#include "script/script.h"
#include <boost/test/unit_test.hpp>
#include <limits.h>
#include <stdint.h>
BOOST_AUTO_TEST_SUITE(scriptnum_tests)

static const long values[] = \
{ 0, 1, CHAR_MIN, CHAR_MAX, UCHAR_MAX, SHRT_MIN, USHRT_MAX, INT_MIN, INT_MAX, static_cast<long>UINT_MAX, LONG_MIN, LONG_MAX };
static const long offsets[] = { 1, 0x79, 0x80, 0x81, 0xFF, 0x7FFF, 0x8000, 0xFFFF, 0x10000};

static bool verify(const CBigNum& bignum, const CScriptNum& scriptnum)
{
    return bignum.getvch() == scriptnum.getvch() && bignum.getint() == scriptnum.getint();
}

static void CheckCreateVch(const long& num)
{
    CBigNum bignum(num);
    CScriptNum scriptnum(num);
    BOOST_CHECK(verify(bignum, scriptnum));

    CBigNum bignum2(bignum.getvch());
    CScriptNum scriptnum2(scriptnum.getvch(), false);
    BOOST_CHECK(verify(bignum2, scriptnum2));

    CBigNum bignum3(scriptnum2.getvch());
    CScriptNum scriptnum3(bignum2.getvch(), false);
    BOOST_CHECK(verify(bignum3, scriptnum3));
}

static void CheckCreateInt(const long& num)
{
    CBigNum bignum(num);
    CScriptNum scriptnum(num);
    BOOST_CHECK(verify(bignum, scriptnum));
    BOOST_CHECK(verify(bignum.getint(), CScriptNum(scriptnum.getint())));
    BOOST_CHECK(verify(scriptnum.getint(), CScriptNum(bignum.getint())));
    BOOST_CHECK(verify(CBigNum(scriptnum.getint()).getint(), CScriptNum(CScriptNum(bignum.getint()).getint())));
}

static void CheckAdd(const long& num1, const long& num2)
{
    const CBigNum bignum1(num1);
    const CBigNum bignum2(num2);
    const CScriptNum scriptnum1(num1);
    const CScriptNum scriptnum2(num2);
    CBigNum bignum3(num1);
    CBigNum bignum4(num1);
    CScriptNum scriptnum3(num1);
    CScriptNum scriptnum4(num1);

    // int64_t overflow is undefined.
    bool invalid = (((num2 > 0) && (num1 > (std::numeric_limits<long>::max() - num2))) ||
                    ((num2 < 0) && (num1 < (std::numeric_limits<long>::min() - num2))));
    if (!invalid)
    {
        BOOST_CHECK(verify(bignum1 + bignum2, scriptnum1 + scriptnum2));
        BOOST_CHECK(verify(bignum1 + bignum2, scriptnum1 + num2));
        BOOST_CHECK(verify(bignum1 + bignum2, scriptnum2 + num1));
    }
}

static void CheckNegate(const long& num)
{
    const CBigNum bignum(num);
    const CScriptNum scriptnum(num);

    // -INT64_MIN is undefined
    if (num != std::numeric_limits<long>::min())
        BOOST_CHECK(verify(-bignum, -scriptnum));
}

static void CheckSubtract(const long& num1, const long& num2)
{
    const CBigNum bignum1(num1);
    const CBigNum bignum2(num2);
    const CScriptNum scriptnum1(num1);
    const CScriptNum scriptnum2(num2);
    bool invalid = false;

    // int64_t overflow is undefined.
    invalid = ((num2 > 0 && num1 < std::numeric_limits<long>::min() + num2) ||
               (num2 < 0 && num1 > std::numeric_limits<long>::max() + num2));
    if (!invalid)
    {
        BOOST_CHECK(verify(bignum1 - bignum2, scriptnum1 - scriptnum2));
        BOOST_CHECK(verify(bignum1 - bignum2, scriptnum1 - num2));
    }

    invalid = ((num1 > 0 && num2 < std::numeric_limits<long>::min() + num1) ||
               (num1 < 0 && num2 > std::numeric_limits<long>::max() + num1));
    if (!invalid)
    {
        BOOST_CHECK(verify(bignum2 - bignum1, scriptnum2 - scriptnum1));
        BOOST_CHECK(verify(bignum2 - bignum1, scriptnum2 - num1));
    }
}

static void CheckCompare(const long& num1, const long& num2)
{
    const CBigNum bignum1(num1);
    const CBigNum bignum2(num2);
    const CScriptNum scriptnum1(num1);
    const CScriptNum scriptnum2(num2);

    BOOST_CHECK((bignum1 == bignum1) == (scriptnum1 == scriptnum1));
    BOOST_CHECK((bignum1 != bignum1) ==  (scriptnum1 != scriptnum1));
    BOOST_CHECK((bignum1 < bignum1) ==  (scriptnum1 < scriptnum1));
    BOOST_CHECK((bignum1 > bignum1) ==  (scriptnum1 > scriptnum1));
    BOOST_CHECK((bignum1 >= bignum1) ==  (scriptnum1 >= scriptnum1));
    BOOST_CHECK((bignum1 <= bignum1) ==  (scriptnum1 <= scriptnum1));

    BOOST_CHECK((bignum1 == bignum1) == (scriptnum1 == num1));
    BOOST_CHECK((bignum1 != bignum1) ==  (scriptnum1 != num1));
    BOOST_CHECK((bignum1 < bignum1) ==  (scriptnum1 < num1));
    BOOST_CHECK((bignum1 > bignum1) ==  (scriptnum1 > num1));
    BOOST_CHECK((bignum1 >= bignum1) ==  (scriptnum1 >= num1));
    BOOST_CHECK((bignum1 <= bignum1) ==  (scriptnum1 <= num1));

    BOOST_CHECK((bignum1 == bignum2) ==  (scriptnum1 == scriptnum2));
    BOOST_CHECK((bignum1 != bignum2) ==  (scriptnum1 != scriptnum2));
    BOOST_CHECK((bignum1 < bignum2) ==  (scriptnum1 < scriptnum2));
    BOOST_CHECK((bignum1 > bignum2) ==  (scriptnum1 > scriptnum2));
    BOOST_CHECK((bignum1 >= bignum2) ==  (scriptnum1 >= scriptnum2));
    BOOST_CHECK((bignum1 <= bignum2) ==  (scriptnum1 <= scriptnum2));

    BOOST_CHECK((bignum1 == bignum2) ==  (scriptnum1 == num2));
    BOOST_CHECK((bignum1 != bignum2) ==  (scriptnum1 != num2));
    BOOST_CHECK((bignum1 < bignum2) ==  (scriptnum1 < num2));
    BOOST_CHECK((bignum1 > bignum2) ==  (scriptnum1 > num2));
    BOOST_CHECK((bignum1 >= bignum2) ==  (scriptnum1 >= num2));
    BOOST_CHECK((bignum1 <= bignum2) ==  (scriptnum1 <= num2));
}

static void RunCreate(const long& num)
{
    CheckCreateInt(num);
    CScriptNum scriptnum(num);
    if (scriptnum.getvch().size() <= CScriptNum::nDefaultMaxNumSize)
        CheckCreateVch(num);
    else
    {
        BOOST_CHECK_THROW (CheckCreateVch(num), scriptnum_error);
    }
}

static void RunOperators(const long& num1, const int64_t& num2)
{
    CheckAdd(num1, num2);
    CheckSubtract(num1, num2);
    CheckNegate(num1);
    CheckCompare(num1, num2);
}

BOOST_AUTO_TEST_CASE(creation)
{
    for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
    {
        for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
        {
            RunCreate(values[i]);
            RunCreate(values[i] + offsets[j]);
            RunCreate(values[i] - offsets[j]);
        }
    }
}

BOOST_AUTO_TEST_CASE(operators)
{
    for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i)
    {
        for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j)
        {
            RunOperators(values[i], values[i]);
            RunOperators(values[i], -values[i]);
            RunOperators(values[i], values[j]);
            RunOperators(values[i], -values[j]);
            RunOperators(values[i] + values[j], values[j]);
            RunOperators(values[i] + values[j], -values[j]);
            RunOperators(values[i] - values[j], values[j]);
            RunOperators(values[i] - values[j], -values[j]);
            RunOperators(values[i] + values[j], values[i] + values[j]);
            RunOperators(values[i] + values[j], values[i] - values[j]);
            RunOperators(values[i] - values[j], values[i] + values[j]);
            RunOperators(values[i] - values[j], values[i] - values[j]);
        }
    }
}

BOOST_AUTO_TEST_SUITE_END()


//Backend for the instanton finder in the AdS/Flat spacetime case.
#define DLL_EXPORT
#define INSTANTON_SOLVER_DLL_API //Comment this out if we wish to import these
//from a DLL instead.
#include "project_specific.h"

#include "multi_precision_definitions.h"
#include <boost/array.hpp>//For boost arrays (needed for ode_types)
#include "Flat.h"

//------------------------------------------------------------------------------
//Main function to export:
template< class value_type , class time_type , class solution_type,
          class solution_type_action >
void odeSolveFlatFixedBackground
    (value_type false_vacuum , value_type true_vacuum , value_type barrier ,
     potential< value_type >& V , time_type chimax , int odeSolverToUse,
     value_type RelTol, value_type AbsTol, value_type stepError, value_type xi,
     value_type lowerBound , value_type upperBound ,
     solution_grid< time_type , solution_type_action , value_type >& solOut,
     value_type& DSout,value_type precision,std::ostream& outStream,
     bool useSimpleBC,time_type epsilon_step)
{
//INPUTS:
    /*prhs[0] = a in GeV
     *prhs[1] = b (should be -1 < b < 1 to have a barrier)
     *prhs[2] = g (dimensionless coupling strength)
     *prhs[3] = chimax ("infinity" - ie, maximum time we allow)
     *prhs[4] = odeSolver, ie, which ode integration routine to use.
     *  odeSolver = 0 -> (Default) Cash-Karp method (Runge-Kutta)
     *  odeSolver = 1 -> Cash-Karp method (Runge-Kutta)
     *  odeSolver = 2 -> Dormand-Prince algorithm (Runge-Kutta)
     *  odeSolver = 3 -> Fehlberg algorithm (Runge-Kutta)
     *  odeSolver = 4 -> Bulirsch-Stoer algorithm (multi-step method)
     *prhs[5] = RelTol, optional (defaults to 1e-15)
     *prhs[6] = AbsTol, optional (defaults to 1e-15)
     *prhs[7] - stepError - optional. Fraction of Taylor series validity bound
     *          to use for first step. Defaults to 1e-15
     *prhs[8] - xi, non-minimal coupling. (optional - default to 0).
        NB - this won't actually affect the fixed background case in flat
            space!!!
     *prhs[9] - Lower bound on range to search (optional, defaults to barrier)
     *prhs[10] - Upper bound on range to search (optional, defaults to
                                                    true-vacuum)
     *prhs[11] - solOut - solution grid to store final result in.
     *prhs[12] - DSout - multi in which to store computed decay exponent.
     prhs[13] - precision - difference desired between upper and lower bounds
                on initial value of solution.
     */

    //Ok. we proceed by bisecting until we have a solution which overshoots
    //beyond
    //chimax, which stands in for infinity.

    //Overshoots occur when the solution crosses zero before chimax.
    //Undershoots when
    //its derivative crosses zero before then.

    //Instantiate the events function, state-type, dynamic BCs etc...
    //typedef boost::array< value_type , 2 > FlatSolType;//solution without
    //action, for speed.
    //typedef boost::array< value_type , 5 > AdSFlatSolTypeAction;
    typedef solution_type FlatSolType;
    typedef solution_type_action FlatSolTypeAction;
    typedef odeFlat< FlatSolType , time_type , value_type > odeRHSFlat;
    typedef eventsFlat< FlatSolType , time_type , value_type > evFlat;
    typedef dynamicBCsFlatFixed_taylor< value_type , FlatSolType , time_type >
            dynBCFlat;
    typedef odeFlatAction< FlatSolTypeAction , time_type , value_type >
            odeRHSFlatAction;
    typedef eventsFlat< FlatSolTypeAction , time_type , value_type >
            evFlatAction;
    typedef dynamicBCsFlatFixed_taylor< value_type , FlatSolTypeAction ,
                                        time_type > dynBCFlatAction;

    //Instantiation for overshoot/undershoot:
	value_type h = value_type(1.0);//h = phi_scale/M_p so h = 1 is Planck units.
	value_type V0 = value_type(0.0);//V at phi = 0.
    odeRHSFlat odeToSolve(V,xi, h, V0); //ode to solve (gravitational instanton
    //equation)
    //Events function - choose upper and lower bounds 5% outside false and true
    // vacuum, to leave us some leeway for numerical
    //fluctuations near to the nominal boundaries.
    evFlat events(false_vacuum,true_vacuum,false_vacuum,barrier,true_vacuum,
                  outStream); //events function.
        //Checks for events labelled IE:
        //IE = 1 -> y crosses zero (terminal event, => overshoot)
        //IE = 2 -> y' crosses zero (terminal event, => undershoot)
        //IE = 3 -> a crosses zero (non-terminal event, for reference only)
        //IE = 4 -> y crosses lowerBound (usually 0, but could be different -
                                          //terminal event)
        //IE = 5 -> y crosses upperBound (terminal event, indicates bad
                                          //initial condition or perhaps a->0)
    dynBCFlat bcAdjuster(V,stepError,1);//Performs analytic first step using a
                                        // taylor series approximation.
        //stepError - determines size of initial step.
        //version (last argument) - specifies which version of the ode we are
            //using.
            //version = 0 -> normal ode, no action.
            //version = 1 -> ode with action
            //version = 2 -> linearised ode.
    //bcFlat bcAdjusterSimple(V,xi,h,V0);

    //Search loop:
    int nMax = 200;
    int counter = 0;
    //Current bounds on location of bounce:
    value_type lower = lowerBound;//Barrier
    value_type upper = upperBound;//True vacuum
    //outStream << "Lower initial = " << lower << std::endl;
    //outStream << "Upper initial = " << upper << std::endl;
    value_type guess;
    multi diff = abs(upper - lower);
    while(diff > precision)
    {
        //Bisect to get initial guess:
        guess = (lower + upper)/multi(2.0);

        //Check for when the solution passes through its FWHM:
        events.value_to_check = (guess + false_vacuum)/value_type(2.0);

        //Formulate initial conditions vector:
        FlatSolType y0 = {{ guess , value_type(0.0) }};
        //Step away to avoid co-ordinate singularity at chi = 0:
        FlatSolType y01step;
        time_type epsilon;
        /*
        if(!useSimpleBC)
        {
            epsilon = bcAdjuster(y0,y01step,0);//Copies solution after step of
            //epsilon into y01step. Last argument tells us whether
        }
        else
        {
            epsilon = epsilon_step;
            y01step = bcAdjusterSimple(epsilon,y0);
        }
        */
        epsilon = bcAdjuster(y0,y01step);//Copies solution after step of
        //epsilon into y01step. Last argument tells us whether
            //we step forwards (0) or backwards (1) in time.
        //Set up time range:
        time_type tspanArray[2] = { epsilon , chimax };
        std::vector< time_type > tspan;
        tspan.assign(tspanArray,tspanArray + 2);
        //Solve the ode:
        solution_grid< time_type , FlatSolType , value_type > solTemp;
        time_type initStepMax = 0.01/time_type(chimax);
        int nSuccess = ode_solve<time_type,solution_type,value_type>
        (odeToSolve,y01step,tspan,events,odeSolverToUse,solTemp,RelTol,AbsTol,
         initStepMax);
        if(nSuccess != 1)
        {
            outStream << "nSuccess = " << nSuccess << " when solving for "
                      << "AdS-Flat instantons." << std::endl;
            throw "Integration failure.";
        }
        //Now check whether we found an overshoot or an undershoot:
        bool overshoots = false;
        bool undershoots = false;
        for(int j = 0; j < int(solTemp.IE.size());j++)
        {
            switch(solTemp.IE[j])
            {
            case 1:
                //overshoot.
                //outStream << "Overshoot." << std::endl;
                //reportToCaller("Overshoot.\n");
                overshoots = true;
                upper = guess;
                break;
            case 2:
                //undershoot, IF it occurs on the opposite side of the barrier
                //to the true vacuum (theoretically impossible to occur on the
                                      //other
                //side, so must be a numerical artefact if it does).
                if((true_vacuum - barrier)*(solTemp.YE[j][0] - barrier)
                   < value_type(0.0))
                {
                    //outStream << "Undershoot." << std::endl;
                    //reportToCaller("Undershoot.\n");
                    undershoots = true;
                    lower = guess;
                }
                break;
            case 3:
                //FWHM. No need to do anything.
                break;
            case 4:
                //Crossed lower bound. Indicates a bad initial condition.
                outStream << "Upper bound crossed during ode integration. "
                          << "Possibly a bad initial condition (wrong side of "
                          << "true vacuum) or solution undershot and event "
                          << "detection failed to detect this." << std::endl;
                //reportToCaller("Upper bound crossed during ode integration.
                //Possibly a bad initial condition (wrong side of true vacuum)
                //or solution undershot and event detection failed to detect
                //this.\n");
                throw "Unexpected ode-event encountered during integration.";
                break;
            case 5:
                //Crossed upper bound. Indicates a bad initial condition.
                outStream << "Lower bound crossed during ode integration. "
                          << "Possibly a bad initial condition (wrong side of "
                          << "true vacuum) or solution overshot and event"
                          << " detection failed to detect this." << std::endl;
                //reportToCaller("Upper bound crossed during ode integration.
                                 //Possibly a bad initial condition
                                 //(wrong side of true vacuum) or solution
                                 //undershot and event detection failed to
                                 //detect this.\n");
                throw "Unexpected ode-event encountered during integration.";
            default:
                outStream << "Unrecognised ode-event." << std::endl;
                //reportToCaller("Unrecognised ode-event.\n");
                throw "Unexpected ode-event encountered during integration.";
            }
            if(overshoots || undershoots)
            {
                break;
            }
        }
        counter++;
        diff = abs(lower - upper);

        if(overshoots)
        {
            //outStream << "Overshoot.\n" << std::endl;
        }
        else if(undershoots)
        {
            //outStream << "Undershoot.\n" << std::endl;
        }

        //Dump all available data if we get neither an overshoot nor an
        //undershoot, as this is
        //theoretically impossible so we want to understand what went wrong:
        else if(!(overshoots||undershoots))
        {
            outStream << "Neither undershoot nor overshoot detected. "
                      << "(Perhaps integration range should be extended?) "
                      << "Relevant data:\n";
            outStream << "no. of events = " << int(solTemp.IE.size())
                      << std::endl;
            for(int i = 0; i < int(solTemp.IE.size());i++)
            {
                outStream << "IE[" << i << "] = " << solTemp.IE[i] << std::endl;
                outStream << "YE[" << i << "] = " << std::endl;
                for(int j = 0;j < 2;j++)
                {
                    outStream << solTemp.YE[i][j] << std::endl;
                }
                outStream << "TE[" << i << "] = " << solTemp.TE[i] << std::endl;
            }
            outStream << "y0 = \n";
            for(int i = 0;i < 2;i++)
            {
                outStream << y01step[i] << std::endl;
            }
            outStream << "yend = \n";
            for(int i = 0;i < 2;i++)
            {
                outStream << solTemp.Y[int(solTemp.Y.size() - 1)][i]
                          << std::endl;
            }
            outStream << "Tend = " << solTemp.T[int(solTemp.T.size()) - 1]
                      << std::endl;
            //Now exit, throwing an error:
            throw "Error - Neither overshoot nor undershoot detected.\n";
        }

    }
    //We found the bounce. Are we within precision?
    if(diff > precision)
    {
        outStream << "Loop exited before finding bounce." << std::endl;
        //reportToCaller("Loop exited before finding bounce.\n");
    }

    //Compute action:
    odeRHSFlatAction odeToSolveAction(V,xi,value_type(1.0),value_type(0.0));
    evFlatAction eventsAction(false_vacuum,true_vacuum,false_vacuum,barrier,
                              true_vacuum , outStream);
    dynBCFlatAction bcAdjusterAction(V,stepError,2);

    //Check for when the solution passes through its FWHM:
    eventsAction.value_to_check = (guess + false_vacuum)/value_type(2.0);
    //Disable termination so that we can see the full extent of the solution:
    //eventsAction.terminate_on = false;
    //eventsAction.dynamicSwitch[0] = false;
    //eventsAction.dynamicSwitch[1] = false;

    FlatSolTypeAction y0Action = {{ guess , value_type(0.0) ,
                                    value_type(0.0) }};
    FlatSolTypeAction y0Action1step;
    time_type epsAction;
    /*
    if(!useSimpleBC)
    {
        epsAction = bcAdjusterAction(y0Action,y0Action1step,0);
        //Copies solution after step of epsilon into y01step.
        //Last argument tells us whether
    }
    else
    {
        epsAction = epsilon_step;
        y0Action1step = bcAdjusterSimple(epsAction,y0Action);
    }
    */
    epsAction = bcAdjusterAction(y0Action,y0Action1step);
    time_type tspanArray[2] = { epsAction , chimax };
    std::vector< time_type > tspan;
    tspan.assign(tspanArray,tspanArray + 2);
    time_type initStepMax = 0.01/time_type(chimax);
    int nSuccess = ode_solve<time_type,solution_type_action,value_type>
    (odeToSolveAction,y0Action1step,tspan,eventsAction,odeSolverToUse,solOut,
     RelTol,AbsTol,initStepMax);
    if(nSuccess != 1)
    {
        outStream << "nSuccess = " << nSuccess << " when solving for AdS-Flat "
                  << "instantons." << std::endl;
        throw "Integration failure.";
    }
    DSout = solOut.Y[int(solOut.T.size()) - 1][2];
    //Done.
}
//------------------------------------------------------------------------------
//Export an ode solver:
//Main function to export:
template< class value_type , class time_type , class solution_type,
          class solution_type_action >
void odeSolveFlatFixedBackgroundSingle
    (value_type y0,value_type false_vacuum , value_type true_vacuum ,
     value_type barrier , potential< value_type >& V , time_type chimax ,
     int odeSolverToUse, value_type RelTol, value_type AbsTol,
     value_type stepError, value_type xi, value_type lowerBound ,
     value_type upperBound , solution_grid< time_type , solution_type_action ,
     value_type >& solOut,value_type& DSout,value_type precision,
     std::ostream& outStream)
{
//INPUTS:
    /*prhs[0] = a in GeV
     *prhs[1] = b (should be -1 < b < 1 to have a barrier)
     *prhs[2] = g (dimensionless coupling strength)
     *prhs[3] = chimax ("infinity" - ie, maximum time we allow)
     *prhs[4] = odeSolver, ie, which ode integration routine to use.
     *  odeSolver = 0 -> (Default) Cash-Karp method (Runge-Kutta)
     *  odeSolver = 1 -> Cash-Karp method (Runge-Kutta)
     *  odeSolver = 2 -> Dormand-Prince algorithm (Runge-Kutta)
     *  odeSolver = 3 -> Fehlberg algorithm (Runge-Kutta)
     *  odeSolver = 4 -> Bulirsch-Stoer algorithm (multi-step method)
     *prhs[5] = RelTol, optional (defaults to 1e-15)
     *prhs[6] = AbsTol, optional (defaults to 1e-15)
     *prhs[7] - stepError - optional. Fraction of Taylor series validity bound
     *          to use for first step. Defaults to 1e-15
     *prhs[8] - xi, non-minimal coupling. (optional - default to 0).
        NB - this won't actually affect the fixed background case in
        flat space!!!
     *prhs[9] - Lower bound on range to search (optional, defaults to barrier)
     *prhs[10] - Upper bound on range to search (optional, defaults to
                                                 true-vacuum)
     *prhs[11] - solOut - solution grid to store final result in.
     *prhs[12] - DSout - multi in which to store computed decay exponent.
     prhs[13] - precision - difference desired between upper and lower bounds
        on initial value of solution.
     */

    //Ok. we proceed by bisecting until we have a solution which overshoots
    //beyond
    //chimax, which stands in for infinity.

    //Overshoots occur when the solution crosses zero before chimax.
    // Undershoots when
    //its derivative crosses zero before then.

    //Instantiate the events function, state-type, dynamic BCs etc...
    //typedef boost::array< value_type , 2 > FlatSolType;//solution without
    //action, for speed.
    //typedef boost::array< value_type , 5 > AdSFlatSolTypeAction;
    typedef solution_type FlatSolType;
    typedef solution_type_action FlatSolTypeAction;
    typedef odeFlat< FlatSolType , time_type , value_type > odeRHSFlat;
    typedef eventsFlat< FlatSolType , time_type , value_type > evFlat;
    typedef dynamicBCsFlatFixed_taylor< value_type , FlatSolType , time_type >
            dynBCFlat;
    typedef odeFlatAction< FlatSolTypeAction , time_type , value_type >
            odeRHSFlatAction;
    typedef eventsFlat< FlatSolTypeAction , time_type , value_type >
            evFlatAction;
    typedef dynamicBCsFlatFixed_taylor< value_type , FlatSolTypeAction ,
                                         time_type > dynBCFlatAction;

    //Instantiation for overshoot/undershoot:
    outStream << "Setup ode..." << std::endl;
	value_type h = value_type(1.0);//h = phi_scale/M_p so h = 1 is Planck units.
	value_type V0 = value_type(0.0);//V at phi = 0.
    odeRHSFlat odeToSolve(V,xi, h, V0); //ode to solve (gravitational
                                                        //instanton equation)

    //Compute action:
    odeRHSFlatAction odeToSolveAction(V,xi,value_type(1.0),value_type(0.0));
    evFlatAction eventsAction(false_vacuum*value_type(1.05),true_vacuum*
                              value_type(1.05),false_vacuum,barrier,
                              true_vacuum , outStream);
    dynBCFlatAction bcAdjusterAction(V,stepError,2);

    outStream << "Setup initial conditions..." << std::endl;
    FlatSolTypeAction y0Action = {{ y0 , value_type(0.0) , value_type(0.0) }};
    outStream << "V'(y0) = " << V.d(y0) << std::endl;
    FlatSolTypeAction y0Action1step;
    time_type epsAction = bcAdjusterAction(y0Action,y0Action1step);
    outStream << "y0Action1step = \n" << y0Action1step[0] << "\n"
              << y0Action1step[1] << "\n" << y0Action1step[2] << std::endl;
    outStream << "Setup timespan..." << std::endl;
    time_type tspanArray[2] = { epsAction , chimax };
    std::vector< time_type > tspan;
    tspan.assign(tspanArray,tspanArray + 2);
    outStream << "Integrating..." << std::endl;
    time_type initStepMax = 0.01/time_type(chimax);
    int nSuccess = ode_solve<time_type,solution_type_action,value_type>
    (odeToSolveAction,y0Action1step,tspan,eventsAction,odeSolverToUse,solOut,
     RelTol,AbsTol,initStepMax);
    if(nSuccess != 1)
    {
        outStream << "nSuccess = " << nSuccess << " when solving for AdS-Flat "
                  << "instantons." << std::endl;
        throw "Integration failure.";
    }
    outStream << "Compute action..." << std::endl;
    DSout = solOut.Y[int(solOut.T.size()) - 1][2];
    //Done.
    outStream << "Done." << std::endl;
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//Explicit instantiation and export of this function:
template DLL_EXPORT void odeSolveFlatFixedBackground
    < multi , multi , boost::array< multi , 2 > , boost::array< multi , 3 > >
    (multi , multi , multi , potential< multi >& , multi , int, multi, multi,
     multi, multi, multi , multi ,
     solution_grid< multi , boost::array< multi , 3 > , multi >& ,
     multi&,multi,std::ostream&,bool,multi);
//------------------------------------------------------------------------------
template DLL_EXPORT void odeSolveFlatFixedBackgroundSingle
    < multi , multi , boost::array< multi , 2 > , boost::array< multi , 3 > >
    (multi,multi , multi , multi , potential< multi >& , multi , int,
     multi, multi, multi, multi,multi , multi ,
     solution_grid< multi , boost::array< multi , 3 > , multi >& ,
     multi&,multi,std::ostream&);
//------------------------------------------------------------------------------


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SCALAR_IDIVCEIL_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SCALAR_IDIVCEIL_HPP_INCLUDED

#include <boost/simd/toolbox/arithmetic/functions/idivceil.hpp>
#include <boost/simd/include/functions/scalar/divs.hpp>
#include <boost/simd/include/functions/scalar/iceil.hpp>
#include <boost/simd/include/functions/scalar/ceil.hpp>
#include <boost/simd/include/constants/one.hpp>
#include <boost/simd/include/constants/valmin.hpp>
#include <boost/simd/include/constants/valmax.hpp>
#include <boost/dispatch/meta/as_floating.hpp>
#include <boost/dispatch/meta/as_integer.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::idivceil_, tag::cpu_
                            , (A0)
                            , (scalar_< arithmetic_<A0> >)(scalar_< arithmetic_<A0> >)
                            )
  {

    typedef A0 result_type;

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      typedef typename boost::dispatch::meta::as_floating<result_type>::type ftype;
      ftype r = ceil(ftype(a0)/ftype(a1));
      if (r > Valmax<result_type>()) return Valmax<result_type>();
      else if (r <  Valmin<result_type>()) return Valmin<result_type>();
      else return result_type(r);
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::idivceil_, tag::cpu_
                            , (A0)
                            , (scalar_< unsigned_<A0> >)(scalar_< unsigned_<A0> >)
                            )
  {

    typedef A0 result_type;

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return (a1) ? rdivide(A0(a0+(a1-One<result_type>())), a1) : Valmax<result_type>();
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::idivceil_, tag::cpu_
                            , (A0)
                            , (scalar_< floating_<A0> >)(scalar_< floating_<A0> > )
                            )
  {
    typedef A0 typ;
    typedef typename dispatch::meta::as_integer<typ>::type result_type;

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return iceil(a0/a1);
    }
  };
} } }

#endif


/*
 *  testPolyF.cpp
 *  Trogdor6
 *
 *  Created by Paul Hansen on 4/7/10.
 *  Copyright 2010 Stanford University. All rights reserved.
 *
 */

#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Test3D

#include <boost/test/unit_test.hpp>

#include <iostream>
#include <cstdlib>
#include "Polynomial.h"

using namespace std;

typedef Polynomial<float> PolyF;

BOOST_AUTO_TEST_CASE( TestConstructors )
{
    PolyF zed;
    BOOST_CHECK_EQUAL(zed.value(2342.13), 0.0);
    BOOST_CHECK_EQUAL(zed.order(), 0);
    BOOST_CHECK_EQUAL(zed[0], 0.0);
    BOOST_CHECK_EQUAL(zed[1], 0.0);
    BOOST_CHECK_EQUAL(zed[2], 0.0);

    PolyF uno(1.0);
    BOOST_CHECK_EQUAL(uno.value(5.2), 1.0);
    BOOST_CHECK_EQUAL(uno.order(), 0);
    BOOST_CHECK_EQUAL(uno[0], 1.0);
    BOOST_CHECK_EQUAL(uno[1], 0.0);
    BOOST_CHECK_EQUAL(uno[2], 0.0);

    PolyF secondOne(uno);
    PolyF secondZero(zed);
    BOOST_CHECK(secondOne == uno);
    BOOST_CHECK(secondZero == zed);
    BOOST_CHECK_EQUAL(secondOne(3.1), 1.0);
    BOOST_CHECK_EQUAL(secondZero(-12), 0.0);
    BOOST_CHECK_EQUAL(secondOne.order(), uno.order());
    BOOST_CHECK_EQUAL(secondZero.order(), zed.order());

    PolyF linear(1.0, 2.0);
    BOOST_CHECK_EQUAL(linear.order(), 1);
    BOOST_CHECK_EQUAL(linear[0], 1.0);
    BOOST_CHECK_EQUAL(linear[1], 2.0);
    BOOST_CHECK_EQUAL(linear[2], 0.0);
    BOOST_CHECK_EQUAL(linear[3], 0.0);

    PolyF quadratic(2.0, 3.0, -4.0);
    BOOST_CHECK_EQUAL(quadratic.order(), 2);
    BOOST_CHECK_EQUAL(quadratic[0], 2.0);
    BOOST_CHECK_EQUAL(quadratic[1], 3.0);
    BOOST_CHECK_EQUAL(quadratic[2], -4.0);
    BOOST_CHECK_EQUAL(quadratic[3], 0.0);
    BOOST_CHECK_EQUAL(quadratic[4], 0.0);

    PolyF cubic(-0.5, 0.5, 1.5, 2.5);
    BOOST_CHECK_EQUAL(cubic.order(), 3);
    BOOST_CHECK_EQUAL(cubic[0], -0.5);
    BOOST_CHECK_EQUAL(cubic[1], 0.5);
    BOOST_CHECK_EQUAL(cubic[2], 1.5);
    BOOST_CHECK_EQUAL(cubic[3], 2.5);
    BOOST_CHECK_EQUAL(cubic[4], 0.0);
    BOOST_CHECK_EQUAL(cubic[cubic.maximumOrder()], 0.0);
}

BOOST_AUTO_TEST_CASE( Value )
{
    PolyF p;
    p.coefficient(0, 1.0);
    p.coefficient(1, 2.0);
    p.coefficient(2, -2.0);
    p.coefficient(3, -1.0);
    p.coefficient(4, 1.0);

    BOOST_CHECK_EQUAL(p(0.0), p[0]);

    for (double xx = -4.0; xx <= 4.0; xx += 1.0)
    {
        double val = 1.0 + 2.0*xx - 2.0*xx*xx - 1.0*xx*xx*xx
            + 1.0*xx*xx*xx*xx;
        BOOST_CHECK_EQUAL(p(xx), val);
    }
}

BOOST_AUTO_TEST_CASE( ChangeOrder )
{
    PolyF p(1, 2, 3, 4);
    BOOST_CHECK_EQUAL(p.order(), 3);
    BOOST_CHECK_EQUAL(p[4], 0.0);
    p.coefficient(1,0);
    BOOST_CHECK_EQUAL(p.order(), 3);
    p.coefficient(2,0);
    BOOST_CHECK_EQUAL(p.order(), 3);
    p.coefficient(3,0);
    BOOST_CHECK_EQUAL(p.order(), 0);
}

BOOST_AUTO_TEST_CASE( AccessCoefficients )
{
    PolyF f;
    for (int nn = 0; nn <= 5; nn++)
        f.coefficient(nn, -1.0 * nn);

    BOOST_CHECK_EQUAL(f[0], 0.0);
    BOOST_CHECK_EQUAL(f[1], -1.0);
    BOOST_CHECK_EQUAL(f[2], -2.0);
    BOOST_CHECK_EQUAL(f[3], f.coefficient(3));
    BOOST_CHECK_EQUAL(f[4], f.coefficient(4));
    BOOST_CHECK_EQUAL(f[5], f.coefficient(5));
}

BOOST_AUTO_TEST_CASE( SumDifference )
{
    PolyF p, q, r;

    p.coefficient(0, 1.0);
    p.coefficient(1, -1.0);
    p.coefficient(2, 2.0);
    BOOST_CHECK_EQUAL(p.order(), 2);

    q.coefficient(5.0);
    BOOST_CHECK_EQUAL(q.order(), 0);

    r.coefficient(0, -1.0);
    r.coefficient(1, 2.0);
    BOOST_CHECK_EQUAL(r.order(), 1);

    PolyF sumPQ = p + q;
    BOOST_CHECK_EQUAL(sumPQ.order(), 2);
    BOOST_CHECK_EQUAL(sumPQ[0], p[0] + q[0]);
    BOOST_CHECK_EQUAL(sumPQ[1], p[1]);
    BOOST_CHECK_EQUAL(sumPQ[2], p[2]);
    BOOST_CHECK_EQUAL(sumPQ(3.0), p(3.0) + q(3.0));
    PolyF sumQP = q + p;
    BOOST_CHECK(sumQP == sumPQ);

    PolyF diffQR = q - r;
    BOOST_CHECK_EQUAL(diffQR.order(), 1);
    BOOST_CHECK_EQUAL(diffQR[0], q[0] - r[0]);
    BOOST_CHECK_EQUAL(diffQR[1], -r[1]);
    BOOST_CHECK_EQUAL(diffQR(-4.0), q(-4.0) - r(-4.0));
    PolyF diffRQ = r - q;
    BOOST_CHECK(diffRQ == -1.0f*diffQR);
    BOOST_CHECK(diffRQ == diffQR*(-1.0f));

    PolyF sumPQR = p + q + r;
    BOOST_CHECK_EQUAL(sumPQR(-10), p(-10) + q(-10) + r(-10));

    PolyF diffPQR = p - q - r;
    BOOST_CHECK_EQUAL(diffPQR(-5), p(-5) - q(-5) - r(-5));

    PolyF p2(p);
    p2 += q;
    BOOST_CHECK(p2 == sumPQ);

    PolyF q2(q);
    q2 -= r;
    BOOST_CHECK(q2 == diffQR);
}

BOOST_AUTO_TEST_CASE( Product )
{
    PolyF p, q, r;

    p.coefficient(0, 1.0);
    p.coefficient(1, -1.0);
    p.coefficient(2, 2.0);

    q.coefficient(0, 5.0);

    r.coefficient(0, -1.0);
    r.coefficient(1, 2.0);

    PolyF prodPQ = p*q;
    BOOST_CHECK_EQUAL(prodPQ(0.0), prodPQ[0]);
    BOOST_CHECK_EQUAL(prodPQ(0.0), p(0.0)*q(0.0));
    BOOST_CHECK_EQUAL(prodPQ.order(), 2);
    BOOST_CHECK_EQUAL(prodPQ[0], p[0]*q[0]);
    BOOST_CHECK_EQUAL(prodPQ[1], p[1]*q[0]);
    BOOST_CHECK_EQUAL(prodPQ[2], p[2]*q[0]);
    PolyF prodQP = q*p;
    BOOST_CHECK(prodPQ == prodQP);

    PolyF prodPR = p*r;
    BOOST_CHECK_EQUAL(prodPR.order(), p.order() + r.order());
    BOOST_CHECK_EQUAL(prodPR[0], p[0]*r[0]);
    BOOST_CHECK_EQUAL(prodPR[1], p[1]*r[0] + p[0]*r[1]);
    BOOST_CHECK_EQUAL(prodPR[2], p[2]*r[0] + p[1]*r[1]);

    BOOST_CHECK_EQUAL(prodPR(0.0), p(0.0)*r(0.0));
    BOOST_CHECK_EQUAL(prodPR(1.0), p(1.0)*r(1.0));
    BOOST_CHECK_EQUAL(prodPR(-2.0), p(-2.0)*r(-2.0));

    PolyF prodRP = r*p;
    BOOST_CHECK(prodPR == prodRP);

    PolyF prodPQR = p*q*r;
    BOOST_CHECK_EQUAL(prodPQR(22), p(22)*q(22)*r(22));

    // test in-place multiplication
    PolyF p2(p);
    p2 *= q;
    BOOST_CHECK(p2 == prodPQ);
    PolyF p3(p);
    p3 *= r;
    BOOST_CHECK(p3 == prodPR);
}

BOOST_AUTO_TEST_CASE( TestPrint )
{
    PolyF nil;
    PolyF zed(0.0);
    PolyF uno(1.0);

    BOOST_CHECK(true);
    BOOST_TEST_MESSAGE("Default zero polynomial: " << nil);
    BOOST_TEST_MESSAGE("Zero polynomial: " << zed);
    BOOST_TEST_MESSAGE("Unit polynomial: " << uno);
}


/* Copyright (C) 2012-2019 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */
#include <NTL/ZZ.h>
#include <helib/norms.h>
#include <helib/helib.h>

#ifdef DEBUG_PRINTOUT
#include <helib/debugging.h>
#endif

#include "gtest/gtest.h"
#include "test_common.h"

namespace {
struct Parameters {
    const long m;
    const long r;

    Parameters(long m, long r) :
        m(m),
        r(r)
    {};

    friend std::ostream& operator<<(std::ostream& os, const Parameters& params)
    {
        return os << "{" <<
            "m=" << params.m << "," <<
            "r=" << params.r <<
            "}";
    };
};

class GTestEaCx : public ::testing::TestWithParam<Parameters> {
    protected:

        const long m;
        const long r;
        helib::Context context;
        const helib::EncryptedArrayCx& eacx;

        GTestEaCx() :
            m(GetParam().m),
            r(GetParam().r),
            context(m, /*p=*/-1, r),
            eacx((helib::buildModChain(context, 5, 2), context.ea->getCx()))
        {}

        virtual void SetUp() override
        {
            if (!helib_test::noPrint) {
                std::vector<long> f;
                helib::factorize(f,m);
                std::cout << "r="<<r<<", factoring "<<m<<" gives [";
                for (unsigned long i=0; i<f.size(); i++)
                    std::cout << f[i] << " ";
                std::cout << "]\n";

                eacx.getPAlgebra().printout();

#ifdef DEBUG_PRINTOUT
                std::vector<std::complex<double>> vc1;
                eacx.random(vc1);
                std::cout << "random complex vc1=";
                helib::printVec(std::cout,vc1,8)<<std::endl;

                std::vector<double> vd;
                eacx.random(vd);
                std::cout << "random real vd=";
                helib::printVec(std::cout,vd,8)<<std::endl;
#endif
            }
        }

        virtual void TearDown() override
        {
#ifdef DEBUG_PRINTOUT
          helib::cleanupGlobals();
#endif
        }
};

TEST_P(GTestEaCx, encodingWorksCorrectly)
{
    std::vector<double> vl;
    eacx.random(vl);
    vl[1] = -1; // ensure that this is not the zero vector
#ifdef DEBUG_PRINTOUT
    std::cout << "random int v=";
    helib::printVec(std::cout,vl,8)<<std::endl;
#endif

    helib::zzX poly;
    double factor = eacx.encode(poly, vl, 1.0);
    if (!helib_test::noPrint) {
      NTL::ZZX poly2;
      helib::convert(poly2, poly);
      std::cout << "  encoded into a degree-"<<NTL::deg(poly2)<<" polynomial\n";
    }

    std::vector<double> vd2;
    eacx.decode(vd2, poly, factor);
#ifdef DEBUG_PRINTOUT
    std::cout << "  decoded into vd2=";
    helib::printVec(std::cout,vd2,8)<<std::endl;
#endif
    EXPECT_EQ(helib::lsize(vl), helib::lsize(vd2));

    double maxDiff = 0.0;
    for (long i=0; i<helib::lsize(vl); i++) {
        double diffAbs = std::abs(vl[i]-vd2[i]);
        if (diffAbs > maxDiff)
            maxDiff = diffAbs;
    }
    EXPECT_LE(maxDiff, 0.1) <<
        "max |v-vd2|_{infty}=" << maxDiff;

}

INSTANTIATE_TEST_SUITE_P(smallParameters, GTestEaCx, ::testing::Values(
            Parameters(16, 8)
            ));

} // namespace


/*=============================================================================
Copyright 2020 Syed Ali Hasan <alihasan9922@gmail.com>

Distributed under the Boost Software License, Version 1.0. (See accompanying
file License.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

#define BOOST_TEST_MODULE equatorial_ra_coord

#include <iostream>
#include <boost/units/io.hpp>
#include <boost/units/systems/angle/degrees.hpp>
#include <boost/astronomy/coordinate/coord_sys/equatorial_ra_coord.hpp>

#include <boost/test/unit_test.hpp>

using namespace boost::units;
using namespace boost::units::si;
using namespace boost::astronomy::coordinate;

namespace bud = boost::units::degree;

BOOST_AUTO_TEST_SUITE(equatorial_ra_coord_constructors)

BOOST_AUTO_TEST_CASE(equatorial_ra_coord_default_constructor) {
    equatorial_ra_coord<
            double,
            quantity<bud::plane_angle>,
            quantity<bud::plane_angle>>
            era;

    //Check set_ra_dec
        era.set_ra_dec(45.0 * bud::degrees, 18.0 * bud::degrees);

    //Check values
    BOOST_CHECK_CLOSE(era.get_ra().value(), 45.0, 0.001);
    BOOST_CHECK_CLOSE(era.get_dec().value(), 18.0, 0.001);

    //Quantities stored as expected?
    BOOST_TEST((std::is_same<decltype(era.get_ra()), quantity<bud::plane_angle>>::value));
    BOOST_TEST((std::is_same<decltype(era.get_dec()), quantity<bud::plane_angle>>::value));
}

BOOST_AUTO_TEST_CASE(equatorial_ra_coord_quantities_constructor) {
    //Make Equatorial Coordinate Check
    auto era1 = make_equatorial_ra_coord
            (15.0 * bud::degrees, 39.0 * bud::degrees);
    BOOST_CHECK_CLOSE(era1.get_ra().value(), 15.0, 0.001);
    BOOST_CHECK_CLOSE(era1.get_dec().value(), 39.0, 0.001);

    //Quantities stored as expected?
    BOOST_TEST((std::is_same<decltype(era1.get_ra()), quantity<bud::plane_angle>>::value));
    BOOST_TEST((std::is_same<decltype(era1.get_dec()), quantity<bud::plane_angle>>::value));

    //Equatorial Coordinate constructor
    equatorial_ra_coord<double, quantity<bud::plane_angle>, quantity<bud::plane_angle>>
            era2(1.5 * bud::degrees, 9.0 * bud::degrees);
    BOOST_CHECK_CLOSE(era2.get_ra().value(), 1.5, 0.001);
    BOOST_CHECK_CLOSE(era2.get_dec().value(), 9.0, 0.001);

    //Quantities stored as expected?
    BOOST_TEST((std::is_same<decltype(era2.get_ra()), quantity<bud::plane_angle>>::value));
    BOOST_TEST((std::is_same<decltype(era2.get_dec()), quantity<bud::plane_angle>>::value));
}

BOOST_AUTO_TEST_SUITE_END()


#include <iostream>
#include "document.hpp"
#include <boost/unordered_map.hpp>
#include <vector>
#include <cmath>
#if DEBUG
  #include <iostream>
#endif

using namespace doc;

int Word::maxId;

void Word::idInit() {
  maxId = 0;
}

Word::Word( std::string word) {
  this->id = maxId;
  maxId++;
  this->word = word;
}

Word::~Word() {
  delete this;
}

bool Word::operator==( Word* other) {
  return this->id == other->id;
}

Document::Document( std::vector<Word*>* words) {
  this->words = *words;
  this->score = .0f;
  this->documentNorm = .0f;

  for (auto const& word: this->words) {
    this->countMap[word->id] = this->countMap.count( word->id) == 0 ?
      1 : this->countMap[word->id] + 1;
  }

  for (auto const& pair: this->countMap) {
    this->documentNorm += pair.second ^ 2;
  }
  this->documentNorm = sqrt( this->documentNorm);
}

Document::~Document() {
  this->words.clear();
  delete this;
}

void Document::getScore( Document* other) {
  float partialScore = .0f;
  float score;
  if (this->documentNorm == .0f || other->documentNorm == .0f)
    return;
  for (auto const& pair: this->countMap) {
    int count = other->countMap.count( pair.first);
    partialScore += pair.second * count;
  }
  this->score = partialScore / this->documentNorm / other->documentNorm;
}