// Ogonek
//
// Written in 2012-2013 by Martinho Fernandes <martinho.fernandes@gmail.com>
//
// To the extent possible under law, the author(s) have dedicated all copyright and related
// and neighboring rights to this software to the public domain worldwide. This software is
// distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

// UTF-8 encoding form

#ifndef OGONEK_ENCODING_UTF8_HPP
#define OGONEK_ENCODING_UTF8_HPP

#include <ogonek/encoding/iterator.h++>
#include <ogonek/types.h++>
#include <ogonek/error.h++>
#include <ogonek/error/unicode_error.h++>
#include <ogonek/detail/ranges.h++>
#include <ogonek/detail/constants.h++>
#include <ogonek/detail/container/partial_array.h++>
#include <ogonek/detail/container/encoded_character.h++>

#include <taussig/primitives.h++>

#include <boost/range/iterator_range.hpp>
#include <boost/range/sub_range.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/empty.hpp>

#include <array>
#include <utility>

namespace ogonek {
    struct utf8 {
    private:
        static constexpr auto last_1byte_value = 0x7Fu;
        static constexpr auto last_2byte_value = 0x7FFu;
        static constexpr auto last_3byte_value = 0xFFFFu;

        static constexpr auto start_2byte_mask = 0x80u;
        static constexpr auto start_3byte_mask = 0xE0u;
        static constexpr auto start_4byte_mask = 0xF0u;

        static constexpr auto continuation_mask = 0xC0u;
        static constexpr auto continuation_signature = 0x80u;

        static constexpr int sequence_length(byte b) {
            return (b & start_2byte_mask) == 0? 1
                 : (b & start_3byte_mask) != start_3byte_mask? 2
                 : (b & start_4byte_mask) != start_4byte_mask? 3
                 : 4;
        }

        static constexpr code_point decode(byte b0, byte b1) {
            return ((b0 & 0x1F) << 6) | (b1 & 0x3F);
        }
        static constexpr code_point decode(byte b0, byte b1, byte b2) {
            return ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F);
        }
        static constexpr code_point decode(byte b0, byte b1, byte b2, byte b3) {
            return ((b0 & 0x07) << 18) | ((b1 & 0x3F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F);
        }

    public:
        using code_unit = char;
        static constexpr bool is_fixed_width = false;
        static constexpr std::size_t max_width = 4;
        static constexpr bool is_self_synchronizing = true;
        struct state {};

        template <typename ErrorHandler>
        static detail::encoded_character<utf8> encode_one(code_point u, state&, ErrorHandler) {
            if(u <= last_1byte_value) {
                return { static_cast<code_unit>(u) };
            } else if(u <= last_2byte_value) {
                return {
                    static_cast<code_unit>(0xC0 | ((u & 0x7C0) >> 6)),
                    static_cast<code_unit>(0x80 | (u & 0x3F)),
                };
            } else if(u <= last_3byte_value) {
                return {
                    static_cast<code_unit>(0xE0 | ((u & 0xF000) >> 12)),
                    static_cast<code_unit>(0x80 | ((u & 0xFC0) >> 6)),
                    static_cast<code_unit>(0x80 | (u & 0x3F)),
                };
            }
            return {
                static_cast<code_unit>(0xF0 | ((u & 0x1C0000) >> 18)),
                static_cast<code_unit>(0x80 | ((u & 0x3F000) >> 12)),
                static_cast<code_unit>(0x80 | ((u & 0xFC0) >> 6)),
                static_cast<code_unit>(0x80 | (u & 0x3F)),
            };
        }
        template <typename Range>
        static boost::sub_range<Range> decode_one(Range const& r, code_point& out, state&, assume_valid_t) {
            auto first = boost::begin(r);
            byte b0 = *first++;
            auto length = sequence_length(b0);
            if(length == 1) {
                out = b0;
                return { first, boost::end(r) };
            }
            byte b1 = *first++;
            if(length == 2) {
                out = decode(b0, b1);
                return { first, boost::end(r) };
            }
            byte b2 = *first++;
            if(length == 3) {
                out = decode(b0, b1, b2);
                return { first, boost::end(r) };
            }
            byte b3 = *first++;
            out = decode(b0, b1, b2, b3);
            return { first, boost::end(r) };
        }
        template <typename Range, typename ErrorHandler>
        static boost::sub_range<Range> decode_one(Range const& r, code_point& out, state& s, ErrorHandler) {
            auto first = boost::begin(r);
            byte b0 = *first++;
            auto length = sequence_length(b0);

            if(length == 1) {
                out = b0;
                return { first, boost::end(r) };
            }

            auto is_invalid = [](byte b) { return b == 0xC0 || b == 0xC1 || b > 0xF4; };
            auto is_continuation = [](byte b) {
                return (b & continuation_mask) == continuation_signature;
            };

            if(is_invalid(b0) || is_continuation(b0)) {
                return ErrorHandler::template apply_decode<utf8>(r, s, out);
            }

            std::array<byte, 4> b = {{ b0, }};
            for(int i = 1; i < length; ++i) {
                b[i] = *first++;
                if(!is_continuation(b[i])) {
                    return ErrorHandler::template apply_decode<utf8>(r, s, out);
                }
            }

            if(length == 2) {
                out = decode(b[0], b[1]);
            } else if(length == 3) {
                out = decode(b[0], b[1], b[2]);
            } else {
                out = decode(b[0], b[1], b[2], b[3]);
            }

            auto is_overlong = [](code_point u, int bytes) {
                return u <= last_1byte_value
                    || (u <= last_2byte_value && bytes > 2)
                    || (u <= last_3byte_value && bytes > 3);
            };
            if(is_overlong(out, length)) {
                return ErrorHandler::template apply_decode<utf8>(r, s, out);
            }
            if(detail::is_surrogate(out) || out > detail::last_code_point) {
                return ErrorHandler::template apply_decode<utf8>(r, s, out);
            }
            return { first, boost::end(r) };
        }
        template <typename Sequence>
        static std::pair<Sequence, code_point> decode_one_ex(Sequence s, state&, assume_valid_t) {
            byte b0 = seq::front(s);
            seq::pop_front(s);
            auto length = sequence_length(b0);

            if(length == 1) {
                return { s, b0 };
            }

            std::array<byte, 4> b = {{ b0, }};
            for(int i = 1; i < length; ++i) {
                b[i] = seq::front(s);
                seq::pop_front(s);
            }

            code_point decoded;
            if(length == 2) {
                decoded = decode(b[0], b[1]);
            } else if(length == 3) {
                decoded = decode(b[0], b[1], b[2]);
            } else {
                decoded = decode(b[0], b[1], b[2], b[3]);
            }

            return { s, decoded };
        }
        template <typename Sequence, typename ErrorHandler>
        static std::pair<Sequence, code_point> decode_one_ex(Sequence s, state& state, ErrorHandler handler) {
            byte b0 = seq::front(s);
            seq::pop_front(s);
            auto length = sequence_length(b0);

            if(length == 1) {
                return { s, b0 };
            }

            auto is_invalid = [](byte b) { return b == 0xC0 || b == 0xC1 || b > 0xF4; };
            auto is_continuation = [](byte b) {
                return (b & continuation_mask) == continuation_signature;
            };

            if(is_invalid(b0) || is_continuation(b0)) {
                decode_error<Sequence, utf8> error { s, state };
                wheels::optional<code_point> u;
                std::tie(s, state, u) = handler.handle(error);
                return { s, *u };
            }

            std::array<byte, 4> b = {{ b0, }};
            for(int i = 1; i < length; ++i) {
                b[i] = seq::front(s);
                if(!is_continuation(b[i])) {
                    decode_error<Sequence, utf8> error { s, state };
                    wheels::optional<code_point> u;
                    std::tie(s, state, u) = handler.handle(error);
                    return { s, *u };
                }
                seq::pop_front(s);
            }

            code_point decoded;
            if(length == 2) {
                decoded = decode(b[0], b[1]);
            } else if(length == 3) {
                decoded = decode(b[0], b[1], b[2]);
            } else {
                decoded = decode(b[0], b[1], b[2], b[3]);
            }

            auto is_overlong = [](code_point u, int bytes) {
                return u <= last_1byte_value
                    || (u <= last_2byte_value && bytes > 2)
                    || (u <= last_3byte_value && bytes > 3);
            };
            if(is_overlong(decoded, length)) {
                decode_error<Sequence, utf8> error { s, state };
                wheels::optional<code_point> u;
                std::tie(s, state, u) = handler.handle(error);
                return { s, *u };
            }
            if(detail::is_surrogate(decoded) || decoded > detail::last_code_point) {
                decode_error<Sequence, utf8> error { s, state };
                wheels::optional<code_point> u;
                std::tie(s, state, u) = handler.handle(error);
                return { s, *u };
            }
            return { s, decoded };
        }
    };
} // namespace ogonek

#endif // OGONEK_ENCODING_UTF8_HPP


#include "modules/bio_base/kmer.h"
#include "modules/bio_mapred/align_kmer.h"
#include "modules/bio_base/astar.h"
#include "modules/io/log.h"
#include <boost/operators.hpp>

static int best_pow(int loc)
{
	for(int i = 0; i < 16; i++)
	{
		if ((loc & (1 << i)) != 0)
			return i;
	}
	return 16;
}

struct kmer_astar_state
	: boost::less_than_comparable<kmer_astar_state>
	, boost::equality_comparable<kmer_astar_state>
{
	kmer_astar_state(int _start, int _end, kmer_t _ks, kmer_t _ke)
		: start(_start)
		, end(_end)
		, ks(_ks)
		, ke(_ke)
	{
		pow2s = best_pow(start);
		pow2e = best_pow(end);
		min_pow = std::min(pow2s, pow2e);
		max_pow = std::max(pow2s, pow2e);
	}

	int start;
	int end;
	kmer_t ks;
	kmer_t ke;
	int pow2s;
	int pow2e;
	int min_pow;
	int max_pow;
	bool operator<(const kmer_astar_state& rhs) const
	{
		if (min_pow != rhs.min_pow)
			return min_pow < rhs.min_pow;
		if (max_pow != rhs.max_pow)
			return max_pow < rhs.max_pow;
		if (start != rhs.start)
			return start < rhs.start;
		if (end != rhs.end)
			return end < rhs.end;
		return false;
	}
	bool operator==(const kmer_astar_state& rhs) const
	{
		return (start == rhs.start && end == rhs.end);
	}
};

struct kmer_astar_context
{
	typedef kmer_astar_state location_t;
	typedef double dist_t;
	kmer_astar_context(const dna_sequence& read, const std::string& qual, const kmer_set& kmers, double min_base_quality)
		: m_read(read)
		, m_qual(qual)
		, m_kmers(kmers)
		, m_min_base_quality(min_base_quality)
		, m_ks(kmers.kmer_size())
	{}

	double estimate(const kmer_astar_state& a, const kmer_astar_state& b) const { return 0.0; }

	std::vector<std::pair<double, kmer_astar_state> > nearby(const kmer_astar_state& loc) const
	{
		std::vector<std::pair<double, kmer_astar_state> > r;
		if (loc.start == loc.end)
		{
			// Initial state, find all matching kmers
			for(size_t i = 0; i <= m_read.size() - m_ks; i++)
			{
				kmer_t k = make_kmer(m_read.begin() + i, m_ks);
				if (m_kmers.count(canonicalize(k, m_ks)))
					r.push_back(std::make_pair(0.0,
						kmer_astar_state(i, i + m_ks, k, k)));
			}
		}
		else
		{
			//SPLOG("Processing rs=%d (%d:%d) %s:%s", m_read.size(), loc.start, loc.end,
			//	dna_sequence(loc.ks, m_ks).as_string().c_str(),
			//	dna_sequence(loc.ke, m_ks).as_string().c_str());
			if (loc.pow2s < loc.pow2e && loc.start > 0)
			{
				// Try adding in reverse direction
				kmer_t k = loc.ks;
				for(int base = 0; base < 4; base++)
				{
					kmer_t k2 = append(base, left(k, m_ks, m_ks-1), m_ks-1);
					if (m_kmers.count(canonicalize(k2, m_ks)))
					{
						double cost = (dna_base(base) == m_read[loc.start - 1]) ? 0.0
							: std::max(m_min_base_quality, double(m_qual[loc.start - 1] - 33));
						r.push_back(std::make_pair(cost,
							kmer_astar_state(loc.start - 1, loc.end, k2, loc.ke)));
					}
				}
			}
			else if (loc.end < (int) m_read.size())
			{
				// Try adding in forward direction
				kmer_t k = loc.ke;
				for(int base = 0; base < 4; base++)
				{
					kmer_t k2 = append(right(k, m_ks-1), base, 1);
					if (m_kmers.count(canonicalize(k2, m_ks)))
					{
						double cost = (dna_base(base) == m_read[loc.end]) ? 0.0
							: std::max(m_min_base_quality, double(m_qual[loc.end] - 33));
						r.push_back(std::make_pair(cost,
							kmer_astar_state(loc.start, loc.end +1, loc.ks, k2)));
					}
				}
			}
			else if (loc.start == 0 && loc.end == (int) m_read.size() && loc.ks && loc.ke)
			{
				r.push_back(std::make_pair(0.0, kmer_astar_state(0, m_read.size() + 1, 0, 0)));
			}
		}
		return r;
	}

	const dna_sequence& m_read;
	std::string m_qual;
	const kmer_set& m_kmers;
	double m_min_base_quality;
	size_t m_ks;
};

unsigned verify_kmers(const dna_sequence& read, const kmer_set& kmers) {
  size_t kmer_size = kmers.kmer_size();
  if (read.size() < kmer_size) return 0;
  for (size_t i = 0; i <= read.size() - kmer_size; i++) {
    kmer_t k = make_kmer(read.begin() + i, kmer_size);
    k = canonicalize(k, kmer_size);
    if (!kmers.count(k)) {
      return i + kmer_size - 1;
    }
  }
  return read.size();
  return true;
}

double align_kmer(std::vector<kmer_t>& out, const dna_sequence& read, const std::string& qual,
		const kmer_set& kmers, double min_base_quality, double max_cost)
{
	if (read.size() < kmers.kmer_size()) return max_cost;

	kmer_astar_context ctx(read, qual, kmers, min_base_quality);
	kmer_astar_state start(0, 0, 0, 0);
	kmer_astar_state end(0, read.size() + 1, 0, 0);
	astar_state<kmer_astar_context> as(ctx, start, end, max_cost);
	double cost = as.run();
	if (cost >= max_cost)
		return max_cost;
	std::vector<kmer_astar_state> r;
	as.get_path(r);
	out.resize(read.size() - kmers.kmer_size() + 1);
	for(size_t i = 0; i < r.size(); i++)
	{
		if (r[i].end == 0 || r[i].end > (int) read.size()) continue;
		//printf("Adding (%d, %d) = (%s, %s)\n", r[i].start, r[i].end,
		//		dna_sequence(r[i].ks, kmers.kmer_size()).as_string().c_str(), dna_sequence(r[i].ke, kmers.kmer_size()).as_string().c_str());
		out[r[i].start] = r[i].ks;
		out[r[i].end - kmers.kmer_size()] = r[i].ke;
	}
	return cost;
}

dna_sequence get_corrected(const std::vector<kmer_t>& in, size_t kmer_size)
{
	dna_sequence start(in[0], kmer_size);
	for(size_t i = 1; i < in.size(); i++)
	{
		start.push_back(dna_base((int) right(in[i], 1)));
	}
	return start;
}


// __BEGIN_LICENSE__
// Copyright (C) 2006-2011 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

// What's this file supposed to do ?
//
// (Pho)tometry (It)eration (Error) Update
//
// Equation:
//
// e = Sumk( Sumij( ((I - A*T*R)*S)^2 ))
//
// If there's no reflectance, don't multiply by it.

#include <vw/Image.h>
#include <photk/Macros.h>
#include <photk/Common.h>
#include <photk/RemoteProjectFile.h>
#include <photk/ErrorAccumulators.h>
using namespace vw;
using namespace vw::platefile;
using namespace photk;

#include <boost/program_options.hpp>
namespace po = boost::program_options;

using namespace std;

struct Options : photk::BaseOptions {
  Url ptk_url;

  // For spawning multiple jobs
  int job_id, num_jobs, level;
};

void update_error( Options& opt ) {
  RemoteProjectFile remote_ptk( opt.ptk_url );
  ProjectMeta project_info;
  remote_ptk.get_project( project_info );

  // Load platefile
  boost::shared_ptr<PlateFile> drg_plate, albedo_plate, reflect_plate;

  // Deciding what cameras
  int minidx, maxidx;
  if (opt.num_jobs > 0) {
    minidx = float(project_info.num_cameras()*opt.job_id)/float(opt.num_jobs);
    maxidx = float(project_info.num_cameras()*(opt.job_id+1))/float(opt.num_jobs);

    remote_ptk.get_platefiles(drg_plate,albedo_plate,reflect_plate);

    if (opt.level < 0 )
      opt.level = drg_plate->num_levels() - 1;
  } else {
    minidx = 0;
    maxidx = 0;
  }

  for(int j = minidx; j < maxidx; j++) {
    CameraMeta cam_info;
    remote_ptk.get_camera(j, cam_info);

    ErrorNRAccumulatorFunc<double,Vector2i> funcProto(opt.level, j+1, cam_info.exposure_t(), drg_plate, albedo_plate);

    int32 full = 1 << opt.level;
    BBox2i affected_tiles(0,0,full-1,full-1);

    RecursiveBBoxAccumulator<ErrorNRAccumulatorFunc<double,Vector2i> > accum(32, funcProto);

    ErrorNRAccumulatorFunc<double,Vector2i> funcResult = accum(affected_tiles);

    vw_out() << "cam[" << j << "] error=[" << funcResult.value() << "]\n";

    // Once I have the API, call funcResult.value() and
    // add it to the existing value in the current camera
    if (project_info.current_iteration() == 0) {
      cam_info.set_init_error(funcResult.value());
    } else {
      cam_info.set_last_error(cam_info.curr_error());
      cam_info.set_curr_error(funcResult.value());
    }

    remote_ptk.set_camera(j, cam_info);
  }

  if (opt.num_jobs <= 0) {
    // Save last error before we overwrite it
    float32 initError = remote_ptk.get_init_error();
    float32 lastError = remote_ptk.get_last_error();
    float32 currError = remote_ptk.get_curr_error();

    // Compare init error and most recent error:
    vw_out() << "Init error=[" << initError << "]\n";
    vw_out() << "Last error=[" << lastError << "]\n";
    vw_out() << "Curr error=[" << currError << "]\n";
  }
}

void handle_arguments( int argc, char *argv[], Options& opt ) {
  po::options_description general_options("");
  general_options.add_options()
    ("level,l", po::value(&opt.level)->default_value(-1), "Default is to process lowest level.")
    ("job_id,j", po::value(&opt.job_id)->default_value(0), "")
    ("num_jobs,n", po::value(&opt.num_jobs)->default_value(1), "If num_jobs is 0, don't process anything, just output the error values for the full plate");
  general_options.add( photk::BaseOptionsDescription(opt) );

  po::options_description positional("");
  positional.add_options()
    ("ptk_url",  po::value(&opt.ptk_url),  "Input PTK Url");

  po::positional_options_description positional_desc;
  positional_desc.add("ptk_url", 1);

  std::ostringstream usage;
  usage << "Usage: " << argv[0] << " <ptk-url>\n";

  po::variables_map vm =
    photk::check_command_line( argc, argv, opt, general_options,
                             positional, positional_desc, usage.str() );

  if ( opt.ptk_url == Url() )
    vw_throw( ArgumentErr() << "Missing project file url!\n"
              << usage.str() << general_options );
}

int main( int argc, char *argv[] ) {

  Options opt;
  try {
    handle_arguments( argc, argv, opt );
    update_error( opt );
  } PHOTK_STANDARD_CATCHES;

  return 0;
}


/*
 Copyright (C) 2016 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include "utilities.hpp"

#include "toplevelfixture.hpp"
#include <boost/test/unit_test.hpp>
#include <qle/models/cdsoptionhelper.hpp>
#include <qle/models/cpicapfloorhelper.hpp>
#include <qle/models/crlgm1fparametrization.hpp>
#include <qle/models/crossassetanalytics.hpp>
#include <qle/models/crossassetanalyticsbase.hpp>
#include <qle/models/crossassetmodel.hpp>
#include <qle/models/crossassetmodelimpliedeqvoltermstructure.hpp>
#include <qle/models/crossassetmodelimpliedfxvoltermstructure.hpp>
#include <qle/models/dkimpliedyoyinflationtermstructure.hpp>
#include <qle/models/dkimpliedzeroinflationtermstructure.hpp>
#include <qle/models/eqbsconstantparametrization.hpp>
#include <qle/models/eqbsparametrization.hpp>
#include <qle/models/eqbspiecewiseconstantparametrization.hpp>
#include <qle/models/fxbsconstantparametrization.hpp>
#include <qle/models/fxbsparametrization.hpp>
#include <qle/models/fxbspiecewiseconstantparametrization.hpp>
#include <qle/models/fxeqoptionhelper.hpp>
#include <qle/models/gaussian1dcrossassetadaptor.hpp>
#include <qle/models/infdkparametrization.hpp>
#include <qle/models/irlgm1fconstantparametrization.hpp>
#include <qle/models/irlgm1fparametrization.hpp>
#include <qle/models/irlgm1fpiecewiseconstanthullwhiteadaptor.hpp>
#include <qle/models/irlgm1fpiecewiseconstantparametrization.hpp>
#include <qle/models/irlgm1fpiecewiselinearparametrization.hpp>
#include <qle/models/lgm.hpp>
#include <qle/models/lgmimplieddefaulttermstructure.hpp>
#include <qle/models/lgmimpliedyieldtermstructure.hpp>
#include <qle/models/linkablecalibratedmodel.hpp>
#include <qle/models/parametrization.hpp>
#include <qle/models/piecewiseconstanthelper.hpp>
#include <qle/models/pseudoparameter.hpp>
#include <qle/pricingengines/analyticcclgmfxoptionengine.hpp>
#include <qle/pricingengines/analyticdkcpicapfloorengine.hpp>
#include <qle/pricingengines/analyticlgmcdsoptionengine.hpp>
#include <qle/pricingengines/analyticlgmswaptionengine.hpp>
#include <qle/pricingengines/analyticxassetlgmeqoptionengine.hpp>
#include <qle/pricingengines/blackcdsoptionengine.hpp>
#include <qle/pricingengines/crossccyswapengine.hpp>
#include <qle/pricingengines/depositengine.hpp>
#include <qle/pricingengines/discountingcommodityforwardengine.hpp>
#include <qle/pricingengines/discountingcurrencyswapengine.hpp>
#include <qle/pricingengines/discountingequityforwardengine.hpp>
#include <qle/pricingengines/discountingfxforwardengine.hpp>
#include <qle/pricingengines/discountingriskybondengine.hpp>
#include <qle/pricingengines/discountingswapenginemulticurve.hpp>
#include <qle/pricingengines/midpointcdsengine.hpp>
#include <qle/pricingengines/numericlgmswaptionengine.hpp>
#include <qle/pricingengines/oiccbasisswapengine.hpp>
#include <qle/pricingengines/paymentdiscountingengine.hpp>

#include <ql/currencies/europe.hpp>
#include <ql/indexes/swap/euriborswap.hpp>
#include <ql/instruments/makeswaption.hpp>
#include <ql/math/array.hpp>
#include <ql/math/comparison.hpp>
#include <ql/models/shortrate/onefactormodels/gsr.hpp>
#include <ql/pricingengines/swap/discountingswapengine.hpp>
#include <ql/pricingengines/swaption/fdhullwhiteswaptionengine.hpp>
#include <ql/pricingengines/swaption/gaussian1dswaptionengine.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/time/calendars/target.hpp>

#include <boost/make_shared.hpp>

using namespace QuantLib;
using namespace QuantExt;

namespace {
struct F : public qle::test::TopLevelFixture {
    F() { Settings::instance().evaluationDate() = Date(20, March, 2019); }
    ~F() {}
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_FIXTURE_TEST_SUITE(AnalyticLgmSwaptionEngineTest, F)

BOOST_AUTO_TEST_CASE(testMonoCurve) {

    BOOST_TEST_MESSAGE("Testing analytic LGM swaption engine coupon "
                       "adjustments in mono curve setup...");

    Handle<YieldTermStructure> flatCurve(boost::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));

    const boost::shared_ptr<IrLgm1fConstantParametrization> irlgm1f =
        boost::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), flatCurve, 0.01, 0.01);

    // no curve attached
    boost::shared_ptr<SwapIndex> index_nocurves = boost::make_shared<EuriborSwapIsdaFixA>(10 * Years);

    // forward curve attached
    boost::shared_ptr<SwapIndex> index_monocurve = boost::make_shared<EuriborSwapIsdaFixA>(10 * Years, flatCurve);

    Swaption swaption_nocurves = MakeSwaption(index_nocurves, 10 * Years, 0.02);
    Swaption swaption_monocurve = MakeSwaption(index_monocurve, 10 * Years, 0.02);

    boost::shared_ptr<PricingEngine> engine_nodisc = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f);
    boost::shared_ptr<PricingEngine> engine_monocurve =
        boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f, flatCurve);

    swaption_nocurves.setPricingEngine(engine_nodisc);
    swaption_nocurves.NPV();
    std::vector<Real> fixedAmountCorrections1 = swaption_nocurves.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement1 = swaption_nocurves.result<Real>("fixedAmountCorrectionSettlement");
    swaption_nocurves.setPricingEngine(engine_monocurve);
    swaption_nocurves.NPV();
    std::vector<Real> fixedAmountCorrections2 = swaption_nocurves.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement2 = swaption_nocurves.result<Real>("fixedAmountCorrectionSettlement");
    swaption_monocurve.setPricingEngine(engine_nodisc);
    swaption_monocurve.NPV();
    std::vector<Real> fixedAmountCorrections3 = swaption_nocurves.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement3 = swaption_nocurves.result<Real>("fixedAmountCorrectionSettlement");
    swaption_monocurve.setPricingEngine(engine_monocurve);
    swaption_monocurve.NPV();
    std::vector<Real> fixedAmountCorrections4 = swaption_nocurves.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement4 = swaption_nocurves.result<Real>("fixedAmountCorrectionSettlement");

    if (fixedAmountCorrections1.size() != 10) {
        BOOST_ERROR("fixed coupon adjustment vector 1 should have size 10, "
                    "but actually has size "
                    << fixedAmountCorrections1.size());
    }
    if (fixedAmountCorrections2.size() != 10) {
        BOOST_ERROR("fixed coupon adjustment vector 2 should have size 10, "
                    "but actually has size "
                    << fixedAmountCorrections2.size());
    }
    if (fixedAmountCorrections3.size() != 10) {
        BOOST_ERROR("fixed coupon adjustment vector 3 should have size 10, "
                    "but actually has size "
                    << fixedAmountCorrections3.size());
    }
    if (fixedAmountCorrections4.size() != 10) {
        BOOST_ERROR("fixed coupon adjustment vector 4 should have size 10, "
                    "but actually has size "
                    << fixedAmountCorrections4.size());
    }

    for (Size i = 0; i < 10; ++i) {
        if (!close_enough(fixedAmountCorrections1[i], 0.0)) {
            BOOST_ERROR("fixed coupon adjustment (1) should be zero in mono "
                        "curve setup, but component "
                        << i << " is " << fixedAmountCorrections1[i]);
        }
        if (!close_enough(fixedAmountCorrections2[i], 0.0)) {
            BOOST_ERROR("fixed coupon adjustment (2) should be zero in mono "
                        "curve setup, but component "
                        << i << " is " << fixedAmountCorrections2[i]);
        }
        if (!close_enough(fixedAmountCorrections3[i], 0.0)) {
            BOOST_ERROR("fixed coupon adjustment (3) should be zero in mono "
                        "curve setup, but component "
                        << i << " is " << fixedAmountCorrections3[i]);
        }
        if (!close_enough(fixedAmountCorrections4[i], 0.0)) {
            BOOST_ERROR("fixed coupon adjustment (4) should be zero in mono "
                        "curve setup, but component "
                        << i << " is " << fixedAmountCorrections4[i]);
        }
    }

    if (!close_enough(fixedAmountCorrectionSettlement1, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (1) should be "
                    "zero in mono curve setup, but is "
                    << fixedAmountCorrectionSettlement1);
    }
    if (!close_enough(fixedAmountCorrectionSettlement2, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (2) should be "
                    "zero in mono curve setup, but is "
                    << fixedAmountCorrectionSettlement2);
    }
    if (!close_enough(fixedAmountCorrectionSettlement3, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (3) should be "
                    "zero in mono curve setup, but is "
                    << fixedAmountCorrectionSettlement3);
    }
    if (!close_enough(fixedAmountCorrectionSettlement4, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (4) should be "
                    "zero in mono curve setup, but is "
                    << fixedAmountCorrectionSettlement4);
    }
}

BOOST_AUTO_TEST_CASE(testDualCurve) {

    BOOST_TEST_MESSAGE("Testing analytic LGM swaption engine coupon "
                       "adjustments in dual curve setup...");

    // discounting curve
    Handle<YieldTermStructure> discCurve(boost::make_shared<FlatForward>(0, NullCalendar(), 0.02, Actual365Fixed()));
    // forward (+10bp)
    Handle<YieldTermStructure> forwardCurve1(
        boost::make_shared<FlatForward>(0, NullCalendar(), 0.0210, Actual365Fixed()));
    // forward (-10bp)
    Handle<YieldTermStructure> forwardCurve2(
        boost::make_shared<FlatForward>(0, NullCalendar(), 0.0190, Actual365Fixed()));

    const boost::shared_ptr<IrLgm1fConstantParametrization> irlgm1f =
        boost::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), discCurve, 0.01, 0.01);

    // forward curve attached
    boost::shared_ptr<SwapIndex> index1 = boost::make_shared<EuriborSwapIsdaFixA>(10 * Years, forwardCurve1);
    boost::shared_ptr<SwapIndex> index2 = boost::make_shared<EuriborSwapIsdaFixA>(10 * Years, forwardCurve2);

    Swaption swaption1 = MakeSwaption(index1, 10 * Years, 0.02);
    Swaption swaption2 = MakeSwaption(index2, 10 * Years, 0.02);

    boost::shared_ptr<PricingEngine> engine_a =
        boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f, discCurve, AnalyticLgmSwaptionEngine::nextCoupon);

    boost::shared_ptr<PricingEngine> engine_b =
        boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f, discCurve, AnalyticLgmSwaptionEngine::proRata);

    swaption1.setPricingEngine(engine_a);
    swaption1.NPV();
    std::vector<Real> fixedAmountCorrections1a = swaption1.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement1a = swaption1.result<Real>("fixedAmountCorrectionSettlement");
    swaption2.setPricingEngine(engine_a);
    swaption2.NPV();
    std::vector<Real> fixedAmountCorrections2a = swaption2.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement2a = swaption2.result<Real>("fixedAmountCorrectionSettlement");
    swaption1.setPricingEngine(engine_b);
    swaption1.NPV();
    std::vector<Real> fixedAmountCorrections1b = swaption1.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement1b = swaption1.result<Real>("fixedAmountCorrectionSettlement");
    swaption2.setPricingEngine(engine_b);
    swaption2.NPV();
    std::vector<Real> fixedAmountCorrections2b = swaption2.result<std::vector<Real> >("fixedAmountCorrections");
    Real fixedAmountCorrectionSettlement2b = swaption2.result<Real>("fixedAmountCorrectionSettlement");

    // check corrections on settlement for plausibility

    Real tolerance = 0.000025; // 0.25 bp

    if (!close_enough(fixedAmountCorrectionSettlement1a, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (1) should be "
                    "0 for nextCoupon, but is "
                    << fixedAmountCorrectionSettlement1a);
    }
    if (!close_enough(fixedAmountCorrectionSettlement2a, 0.0)) {
        BOOST_ERROR("fixed amount correction on settlement (2) should be "
                    "0 for nextCoupon, but is "
                    << fixedAmountCorrectionSettlement2a);
    }
    if (std::abs(fixedAmountCorrectionSettlement1b - 0.00025) > tolerance) {
        BOOST_ERROR("fixed amount correction on settlement (1) should be "
                    "close to 2.5bp for proRata, but is "
                    << fixedAmountCorrectionSettlement1b);
    }
    if (std::abs(fixedAmountCorrectionSettlement2b + 0.00025) > tolerance) {
        BOOST_ERROR("fixed amount correction on settlement (2) should be "
                    "close to -2.5bp for proRata, but is "
                    << fixedAmountCorrectionSettlement2b);
    }

    // we can assume that the result vectors have the correct size, this
    // was tested above

    for (Size i = 0; i < 10; ++i) {
        // amount correction should be close to +10bp (-10bp)
        // up to conventions, check this for plausibility
        if (std::abs(fixedAmountCorrections1a[i] - 0.0010) > tolerance) {
            BOOST_ERROR("fixed coupon adjustment (1, nextCoupon) should "
                        "be close to 10bp for "
                        "a 10bp curve spread, but is "
                        << fixedAmountCorrections1a[i] << " for component " << i);
        }
        if (std::abs(fixedAmountCorrections2a[i] + 0.0010) > tolerance) {
            BOOST_ERROR("fixed coupon adjustment (2, nextCoupon) should "
                        "be close to -10bp for "
                        "a -10bp curve spread, but is "
                        << fixedAmountCorrections2a[i] << " for component " << i);
        }
        if (std::abs(fixedAmountCorrections1b[i] - (i == 9 ? 0.00075 : 0.0010)) > tolerance) {
            BOOST_ERROR("fixed coupon adjustment (1, proRata) should "
                        "be close to 10bp (7.5bp for component 9) for "
                        "a 10bp curve spread, but is "
                        << fixedAmountCorrections1b[i] << " for component " << i);
        }
        if (std::abs(fixedAmountCorrections2b[i] + (i == 9 ? 0.00075 : 0.0010)) > tolerance) {
            BOOST_ERROR("fixed coupon adjustment (2, proRata) should "
                        "be close to -10bp (-7.5bp for component 9) for "
                        "a -10bp curve spread, but is "
                        << fixedAmountCorrections2b[i] << " for component " << i);
        }
    }
}

BOOST_AUTO_TEST_CASE(testAgainstOtherEngines) {

    BOOST_TEST_MESSAGE("Testing analytic LGM swaption engine against "
                       "G1d adaptor / Gsr integral and Hull White fd engines...");

    Real discountingRateLevel[] = { -0.0050, 0.01, 0.03, 0.10 };
    Real forwardingRateLevel[] = { -0.0100, 0.01, 0.04, 0.12 };

    // Hull White only allows for positive reversion levels
    Real kappa[] = { 0.01, 0.00001, 0.01, 0.05 };

    // the model volatilities are meant to be Hull White volatilities
    // they are fed into the LGM model via the HW adaptor below
    // the rationale is to have another independent model
    // (QuantLib::HullWhite) and pricing engine
    // (QUantLib::FdHullWhiteSwaptionEngine) available for validation

    Real sigma[] = { 0.0001, 0.01, 0.02 };

    Real strikeOffset[] = { -0.05, -0.02, -0.01, 0.0, 0.01, 0.02, 0.05 };

    Size no = 0;

    // tolerance for comparision fd engine vs integral engines
    Real tol1 = 3.0E-4;

    // tolerance for comparision of integral engines based
    // on GSR and LGM model
    Real tol2 = 1.0E-4;

    // tolerance for LGM integral engine and analytical engine
    // in the case of no basis between discounting and forwarding
    Real tol3 = 0.6E-4;

    // tolerance for LGM integral engine and analytical engine
    // in the case of a non zero basis between discounting and
    // forwarding curve (mapping type a and b)
    // this scales with sigma, the tolerances here are
    // for sigma = 0.01
    Real tol4a = 6.0E-4, tol4b = 4.0E-4;

    for (Size i = 0; i < LENGTH(discountingRateLevel); ++i) {
        for (Size k = 0; k < LENGTH(kappa); ++k) {
            for (Size l = 0; l < LENGTH(sigma); ++l) {

                Handle<YieldTermStructure> discountingCurve(
                    boost::make_shared<FlatForward>(0, NullCalendar(), discountingRateLevel[i], Actual365Fixed()));
                Handle<YieldTermStructure> forwardingCurve(
                    boost::make_shared<FlatForward>(0, NullCalendar(), forwardingRateLevel[i], Actual365Fixed()));

                Array times(0);
                Array sigma_a(1, sigma[l]);
                Array kappa_a(1, kappa[k]);
                std::vector<Date> dates(0);
                std::vector<Real> sigma_v(1, sigma[l]);
                std::vector<Real> kappa_v(1, kappa[k]);

                const boost::shared_ptr<IrLgm1fPiecewiseConstantHullWhiteAdaptor> irlgm1f =
                    boost::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(EURCurrency(), discountingCurve, times,
                                                                                sigma_a, times, kappa_a);

                std::vector<boost::shared_ptr<Parametrization> > params;
                params.push_back(irlgm1f);
                Matrix rho(1, 1);
                rho[0][0] = 1.0;
                const boost::shared_ptr<CrossAssetModel> crossasset = boost::make_shared<CrossAssetModel>(params, rho);

                const boost::shared_ptr<Gaussian1dModel> g1d =
                    boost::make_shared<Gaussian1dCrossAssetAdaptor>(0, crossasset);

                const boost::shared_ptr<Gsr> gsr = boost::make_shared<Gsr>(discountingCurve, dates, sigma_v, kappa_v);

                const boost::shared_ptr<HullWhite> hw =
                    boost::make_shared<HullWhite>(discountingCurve, kappa[k], sigma[l]);

                boost::shared_ptr<PricingEngine> engine_map_a = boost::make_shared<AnalyticLgmSwaptionEngine>(
                    irlgm1f, discountingCurve, AnalyticLgmSwaptionEngine::nextCoupon);
                boost::shared_ptr<PricingEngine> engine_map_b = boost::make_shared<AnalyticLgmSwaptionEngine>(
                    irlgm1f, discountingCurve, AnalyticLgmSwaptionEngine::proRata);

                boost::shared_ptr<PricingEngine> engine_g1d =
                    boost::make_shared<Gaussian1dSwaptionEngine>(g1d, 128, 7.0, true, false, discountingCurve);

                boost::shared_ptr<PricingEngine> engine_gsr =
                    boost::make_shared<Gaussian1dSwaptionEngine>(gsr, 128, 7.0, true, false, discountingCurve);

                boost::shared_ptr<PricingEngine> engine_fd =
                    boost::make_shared<FdHullWhiteSwaptionEngine>(hw, 400, 400, 0, 1.0E-8);

                boost::shared_ptr<SwapIndex> index =
                    boost::make_shared<EuriborSwapIsdaFixA>(10 * Years, forwardingCurve, discountingCurve);
                Real atmStrike = index->fixing(TARGET().advance(Settings::instance().evaluationDate(), 5 * Years));

                for (Size s = 0; s < LENGTH(strikeOffset); ++s) {

                    // we have to ensure positive effective fixed flows for
                    // the analytic engine (this is checked there, but we
                    // want to avoid exceptions thrown during testing)
                    if (atmStrike + strikeOffset[s] - (forwardingRateLevel[i] - discountingRateLevel[i]) < 0.0001) {
                        continue;
                    }

                    Swaption swaption =
                        MakeSwaption(index, 5 * Years, atmStrike + strikeOffset[s])
                            .withUnderlyingType(strikeOffset[s] > 0.0 ? VanillaSwap::Payer : VanillaSwap::Receiver);

                    swaption.setPricingEngine(engine_map_a);
                    Real npv_map_a = swaption.NPV();
                    swaption.setPricingEngine(engine_map_b);
                    Real npv_map_b = swaption.NPV();
                    swaption.setPricingEngine(engine_g1d);
                    Real npv_g1d = swaption.NPV();
                    swaption.setPricingEngine(engine_gsr);
                    Real npv_gsr = swaption.NPV();
                    swaption.setPricingEngine(engine_fd);
                    Real npv_fd = swaption.NPV();

                    if (std::abs(npv_fd - npv_gsr) > tol1) {
                        BOOST_ERROR("inconsistent swaption npvs (fd="
                                    << npv_fd << ", gsr=" << npv_gsr << ") for case #" << no
                                    << " with discounting rate=" << discountingRateLevel[i]
                                    << ", forwarding rate=" << forwardingRateLevel[i] << ", kappa=" << kappa[k]
                                    << ", sigma=" << sigma[l] << ", strike offset=" << strikeOffset[s]);
                    }

                    if (std::abs(npv_gsr - npv_g1d) > tol2) {
                        BOOST_ERROR("inconsistent swaption npvs (gsr="
                                    << npv_gsr << ", npv_g1d=" << npv_g1d << ") for case #" << no
                                    << " with discounting rate=" << discountingRateLevel[i]
                                    << ", forwarding rate=" << forwardingRateLevel[i] << ", kappa=" << kappa[k]
                                    << ", sigma=" << sigma[l] << ", strike offset=" << strikeOffset[s]);
                    }

                    Real tolTmpA = 0.0, tolTmpB = 0.0;
                    if (std::abs(discountingRateLevel[i] - forwardingRateLevel[i]) < 1.0E-6) {
                        tolTmpA = tolTmpB = tol3;
                    } else {
                        tolTmpA = tol4a * std::max(sigma[l], 0.01) / 0.01; // see above
                        tolTmpB = tol4b * std::max(sigma[l], 0.01) / 0.01; // see above
                    }

                    if (std::abs(npv_g1d - npv_map_a) > tolTmpA) {
                        BOOST_ERROR("inconsistent swaption npvs (g1d="
                                    << npv_g1d << ", map_a=" << npv_map_a << "), tolerance is " << tolTmpA
                                    << ", for case #" << no << " with discounting rate=" << discountingRateLevel[i]
                                    << ", forwarding rate=" << forwardingRateLevel[i] << ", kappa=" << kappa[k]
                                    << ", sigma=" << sigma[l] << ", strike offset=" << strikeOffset[s]);
                    }

                    if (std::abs(npv_g1d - npv_map_b) > tolTmpB) {
                        BOOST_ERROR("inconsistent swaption npvs (g1d="
                                    << npv_g1d << ", map_b=" << npv_map_b << "), tolerance is " << tolTmpB
                                    << ", for case #" << no << " with discounting rate=" << discountingRateLevel[i]
                                    << ", forwarding rate=" << forwardingRateLevel[i] << ", kappa=" << kappa[k]
                                    << ", sigma=" << sigma[l] << ", strike offset=" << strikeOffset[s]);
                    }

                    no++;
                }
            }
        }
    }
} // testAgainstOtherEngines

BOOST_AUTO_TEST_CASE(testLgmInvariances) {

    BOOST_TEST_MESSAGE("Testing LGM model invariances in the analytic LGM "
                       "swaption engine...");

    Real shift[] = { -2.0, -1.0, 0.0, 1.0, 2.0 };
    Real scaling[] = { 5.0, 2.0, 1.0, 0.1, 0.01, -0.01, -0.1, -1.0, -2.0, -5.0 };

    Handle<YieldTermStructure> discountingCurve(
        boost::make_shared<FlatForward>(0, NullCalendar(), 0.03, Actual365Fixed()));
    Handle<YieldTermStructure> forwardingCurve(
        boost::make_shared<FlatForward>(0, NullCalendar(), 0.05, Actual365Fixed()));

    Array times(0);
    Array sigma_a(1, 0.01);
    Array alpha_a(1, 0.01);
    Array kappa_a(1, 0.01);

    boost::shared_ptr<SwapIndex> index =
        boost::make_shared<EuriborSwapIsdaFixA>(10 * Years, forwardingCurve, discountingCurve);
    Swaption swaption = MakeSwaption(index, 5 * Years, 0.07); // otm

    for (Size i = 0; i < LENGTH(shift); ++i) {
        for (Size j = 0; j < LENGTH(scaling); ++j) {

            const boost::shared_ptr<IrLgm1fParametrization> irlgm1f0 =
                boost::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), discountingCurve, 0.01, 0.01);

            const boost::shared_ptr<IrLgm1fParametrization> irlgm1fa =
                boost::make_shared<IrLgm1fConstantParametrization>(EURCurrency(), discountingCurve, 0.01, 0.01);
            irlgm1fa->shift() = shift[i];
            irlgm1fa->scaling() = scaling[j];

            const boost::shared_ptr<IrLgm1fParametrization> irlgm1fb =
                boost::make_shared<IrLgm1fPiecewiseConstantParametrization>(EURCurrency(), discountingCurve, times,
                                                                            alpha_a, times, kappa_a);
            irlgm1fb->shift() = shift[i];
            irlgm1fb->scaling() = scaling[j];

            const boost::shared_ptr<IrLgm1fParametrization> irlgm1f0c =
                boost::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(EURCurrency(), discountingCurve, times,
                                                                             sigma_a, times, kappa_a);

            const boost::shared_ptr<IrLgm1fParametrization> irlgm1fc =
                boost::make_shared<IrLgm1fPiecewiseConstantHullWhiteAdaptor>(EURCurrency(), discountingCurve, times,
                                                                             sigma_a, times, kappa_a);
            irlgm1fc->shift() = shift[i];
            irlgm1fc->scaling() = scaling[j];

            const boost::shared_ptr<LinearGaussMarkovModel> lgm0 = boost::make_shared<LinearGaussMarkovModel>(irlgm1f0);
            const boost::shared_ptr<LinearGaussMarkovModel> lgma = boost::make_shared<LinearGaussMarkovModel>(irlgm1fa);
            const boost::shared_ptr<LinearGaussMarkovModel> lgmb = boost::make_shared<LinearGaussMarkovModel>(irlgm1fb);
            const boost::shared_ptr<LinearGaussMarkovModel> lgm0c =
                boost::make_shared<LinearGaussMarkovModel>(irlgm1f0c);
            const boost::shared_ptr<LinearGaussMarkovModel> lgmc = boost::make_shared<LinearGaussMarkovModel>(irlgm1fc);

            boost::shared_ptr<PricingEngine> engine0 = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f0);
            boost::shared_ptr<PricingEngine> enginea = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1fa);
            boost::shared_ptr<PricingEngine> engineb = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1fb);
            boost::shared_ptr<PricingEngine> engine0c = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1f0c);
            boost::shared_ptr<PricingEngine> enginec = boost::make_shared<AnalyticLgmSwaptionEngine>(irlgm1fc);

            swaption.setPricingEngine(engine0);
            Real npv0 = swaption.NPV();
            swaption.setPricingEngine(enginea);
            Real npva = swaption.NPV();
            swaption.setPricingEngine(engineb);
            Real npvb = swaption.NPV();
            swaption.setPricingEngine(engine0c);
            Real npv0c = swaption.NPV();
            swaption.setPricingEngine(enginec);
            Real npvc = swaption.NPV();

            Real tol = 1.0E-10;
            if (std::fabs(npva - npv0) > tol) {
                BOOST_ERROR("price is not invariant under (shift,scaling)=(" << shift[i] << "," << scaling[i]
                                                                             << "), difference is " << (npva - npv0)
                                                                             << " (constant parametrization)");
            }
            if (std::fabs(npvb - npv0) > tol) {
                BOOST_ERROR("price is not invariant under (shift,scaling)=("
                            << shift[i] << "," << scaling[i] << "), difference is " << (npvb - npv0)
                            << " (piecewise constant parametrization)");
            }
            if (std::fabs(npvc - npv0c) > tol) {
                BOOST_ERROR("price is not invariant under (shift,scaling)=("
                            << shift[i] << "," << scaling[i] << "), difference is " << (npvc - npv0c)
                            << " (hull white adaptor parametrization)");
            }
        }
    }
} // testInvariances

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()