/*
 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 <ored/portfolio/builders/capfloorediborleg.hpp>
#include <ored/portfolio/builders/capflooredyoyleg.hpp>
#include <ored/portfolio/builders/cms.hpp>
#include <ored/portfolio/builders/cmsspread.hpp>
#include <ored/portfolio/legdata.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/to_string.hpp>

#include <boost/make_shared.hpp>
#include <ql/cashflow.hpp>
#include <ql/cashflows/averagebmacoupon.hpp>
#include <ql/cashflows/capflooredcoupon.hpp>
#include <ql/cashflows/capflooredinflationcoupon.hpp>
#include <ql/cashflows/cpicoupon.hpp>
#include <ql/cashflows/cpicouponpricer.hpp>
#include <ql/cashflows/digitalcoupon.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/iborcoupon.hpp>
#include <ql/cashflows/overnightindexedcoupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/errors.hpp>
#include <ql/experimental/coupons/cmsspreadcoupon.hpp>
#include <ql/experimental/coupons/digitalcmsspreadcoupon.hpp>
#include <ql/experimental/coupons/strippedcapflooredcoupon.hpp>
#include <ql/version.hpp>
#include <qle/cashflows/averageonindexedcoupon.hpp>
#include <qle/cashflows/averageonindexedcouponpricer.hpp>
#include <qle/cashflows/brlcdicouponpricer.hpp>
#include <qle/cashflows/equitycoupon.hpp>
#include <qle/cashflows/floatingannuitycoupon.hpp>
#include <qle/cashflows/strippedcapflooredyoyinflationcoupon.hpp>
#include <qle/cashflows/subperiodscoupon.hpp>
#include <qle/cashflows/subperiodscouponpricer.hpp>
#include <qle/indexes/bmaindexwrapper.hpp>
#include <qle/cashflows/couponpricer.hpp>

using namespace QuantLib;
using namespace QuantExt;

namespace ore {
namespace data {

void CashflowData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    amounts_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Cashflow", "Amount", "Date", dates_);
}

XMLNode* CashflowData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Cashflow", "Amount", amounts_, "Date", dates_);
    return node;
}
void FixedLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    rates_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Rates", "Rate", "startDate", rateDates_, true);
}

XMLNode* FixedLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Rates", "Rate", rates_, "startDate", rateDates_);
    return node;
}

void ZeroCouponFixedLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    rates_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Rates", "Rate", "startDate", rateDates_, true);
    XMLNode* compNode = XMLUtils::getChildNode(node, "Compounding");
    if (compNode)
        compounding_ = XMLUtils::getChildValue(node, "Compounding", true);
    else
        compounding_ = "Compounded";
    QL_REQUIRE(compounding_ == "Compounded" || compounding_ == "Simple", "Compounding method " << compounding_ << " not supported");
}

XMLNode* ZeroCouponFixedLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Rates", "Rate", rates_, "startDate", rateDates_);
    XMLUtils::addChild(doc, node, "Compounding", compounding_);
    return node;
}

void FloatingLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    index_ = XMLUtils::getChildValue(node, "Index", true);
    indices_.insert(index_);
    spreads_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Spreads", "Spread", "startDate", spreadDates_, true);
    // These are all optional
    isInArrears_ = isAveraged_ = hasSubPeriods_ = includeSpread_ = false;
    if(XMLNode* arrNode = XMLUtils::getChildNode(node, "IsInArrears"))
        isInArrears_ = parseBool(XMLUtils::getNodeValue(arrNode));
    if(XMLNode* avgNode = XMLUtils::getChildNode(node, "IsAveraged"))
        isAveraged_ = parseBool(XMLUtils::getNodeValue(avgNode));
    if(XMLNode* spNode = XMLUtils::getChildNode(node, "HasSubPeriods"))
        hasSubPeriods_ = parseBool(XMLUtils::getNodeValue(spNode));
    if(XMLNode* incSpNode = XMLUtils::getChildNode(node, "IncludeSpread"))
        includeSpread_ = parseBool(XMLUtils::getNodeValue(incSpNode));
    fixingDays_ = XMLUtils::getChildValueAsInt(node, "FixingDays"); // defaults to 0
    caps_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Caps", "Cap", "startDate", capDates_);
    floors_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Floors", "Floor", "startDate", floorDates_);
    gearings_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Gearings", "Gearing", "startDate", gearingDates_);
    if (XMLUtils::getChildNode(node, "NakedOption"))
        nakedOption_ = XMLUtils::getChildValueAsBool(node, "NakedOption", false);
    else
        nakedOption_ = false;
}

XMLNode* FloatingLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "Index", index_);
    XMLUtils::addChild(doc, node, "IsInArrears", isInArrears_);
    XMLUtils::addChild(doc, node, "IsAveraged", isAveraged_);
    XMLUtils::addChild(doc, node, "HasSubPeriods", hasSubPeriods_);
    XMLUtils::addChild(doc, node, "IncludeSpread", includeSpread_);
    XMLUtils::addChild(doc, node, "FixingDays", static_cast<int>(fixingDays_));
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Caps", "Cap", caps_, "startDate", capDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Floors", "Floor", floors_, "startDate", floorDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Gearings", "Gearing", gearings_, "startDate",
            gearingDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Spreads", "Spread", spreads_, "startDate", spreadDates_);
    XMLUtils::addChild(doc, node, "NakedOption", nakedOption_);
    return node;
}

void CPILegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    index_ = XMLUtils::getChildValue(node, "Index", true);
    indices_.insert(index_);
    baseCPI_ = XMLUtils::getChildValueAsDouble(node, "BaseCPI", true);
    observationLag_ = XMLUtils::getChildValue(node, "ObservationLag", true);
    interpolated_ = XMLUtils::getChildValueAsBool(node, "Interpolated", true);
    XMLNode* subNomNode = XMLUtils::getChildNode(node, "SubtractInflationNotionnal");
    if (subNomNode)
        subtractInflationNominal_ = XMLUtils::getChildValueAsBool(node, "SubtractInflationNotional", true);
    else
        subtractInflationNominal_ = false;
    rates_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Rates", "Rate", "startDate", rateDates_, true);
}

XMLNode* CPILegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "Index", index_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Rates", "Rate", rates_, "startDate", rateDates_);
    XMLUtils::addChild(doc, node, "BaseCPI", baseCPI_);
    XMLUtils::addChild(doc, node, "ObservationLag", observationLag_);
    XMLUtils::addChild(doc, node, "Interpolated", interpolated_);
    XMLUtils::addChild(doc, node, "SubtractInflationNotional", subtractInflationNominal_);
    return node;
}

void YoYLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    index_ = XMLUtils::getChildValue(node, "Index", true);
    indices_.insert(index_);
    fixingDays_ = XMLUtils::getChildValueAsInt(node, "FixingDays", true);
    observationLag_ = XMLUtils::getChildValue(node, "ObservationLag", true);
    gearings_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Gearings", "Gearing", "startDate", gearingDates_);
    spreads_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Spreads", "Spread", "startDate", spreadDates_);
    caps_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Caps", "Cap", "startDate", capDates_);
    floors_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Floors", "Floor", "startDate", floorDates_);
    if (XMLUtils::getChildNode(node, "NakedOption"))
        nakedOption_ = XMLUtils::getChildValueAsBool(node, "NakedOption", false);
    else
        nakedOption_ = false;
}

XMLNode* YoYLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "Index", index_);
    XMLUtils::addChild(doc, node, "ObservationLag", observationLag_);
    XMLUtils::addChild(doc, node, "FixingDays", static_cast<int>(fixingDays_));
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Gearings", "Gearing", gearings_, "startDate",
                                                gearingDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Spreads", "Spread", spreads_, "startDate", spreadDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Caps", "Cap", caps_, "startDate", capDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Floors", "Floor", floors_, "startDate", floorDates_);
    XMLUtils::addChild(doc, node, "NakedOption", nakedOption_);
    return node;
}

XMLNode* CMSLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "Index", swapIndex_);
    XMLUtils::addChild(doc, node, "IsInArrears", isInArrears_);
    XMLUtils::addChild(doc, node, "FixingDays", fixingDays_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Caps", "Cap", caps_, "startDate", capDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Floors", "Floor", floors_, "startDate", floorDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Gearings", "Gearing", gearings_, "startDate",
                                                gearingDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Spreads", "Spread", spreads_, "startDate", spreadDates_);
    XMLUtils::addChild(doc, node, "NakedOption", nakedOption_);
    return node;
}

void CMSLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    swapIndex_ = XMLUtils::getChildValue(node, "Index", true);
    indices_.insert(swapIndex_);
    spreads_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Spreads", "Spread", "startDate", spreadDates_, true);
    // These are all optional
    XMLNode* arrNode = XMLUtils::getChildNode(node, "IsInArrears");
    if (arrNode)
        isInArrears_ = XMLUtils::getChildValueAsBool(node, "IsInArrears", true);
    else
        isInArrears_ = false;                                       // default to fixing-in-advance
    fixingDays_ = XMLUtils::getChildValueAsInt(node, "FixingDays"); // defaults to 0
    caps_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Caps", "Cap", "startDate", capDates_);
    floors_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Floors", "Floor", "startDate", floorDates_);
    gearings_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Gearings", "Gearing", "startDate", gearingDates_);
    if (XMLUtils::getChildNode(node, "NakedOption"))
        nakedOption_ = XMLUtils::getChildValueAsBool(node, "NakedOption", false);
    else
        nakedOption_ = false;
}

XMLNode* CMSSpreadLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "Index1", swapIndex1_);
    XMLUtils::addChild(doc, node, "Index2", swapIndex2_);
    XMLUtils::addChild(doc, node, "IsInArrears", isInArrears_);
    XMLUtils::addChild(doc, node, "FixingDays", fixingDays_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Spreads", "Spread", spreads_, "startDate", spreadDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Caps", "Cap", caps_, "startDate", capDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Floors", "Floor", floors_, "startDate", floorDates_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Gearings", "Gearing", gearings_, "startDate",
                                                gearingDates_);
    XMLUtils::addChild(doc, node, "NakedOption", nakedOption_);
    return node;
}

void CMSSpreadLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    swapIndex1_ = XMLUtils::getChildValue(node, "Index1", true);
    swapIndex2_ = XMLUtils::getChildValue(node, "Index2", true);
    indices_.insert(swapIndex1_);
    indices_.insert(swapIndex2_);
    spreads_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Spreads", "Spread", "startDate", spreadDates_, true);
    // These are all optional
    XMLNode* arrNode = XMLUtils::getChildNode(node, "IsInArrears");
    if (arrNode)
        isInArrears_ = XMLUtils::getChildValueAsBool(node, "IsInArrears", true);
    else
        isInArrears_ = false;                                       // default to fixing-in-advance
    fixingDays_ = XMLUtils::getChildValueAsInt(node, "FixingDays"); // defaults to 0
    caps_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Caps", "Cap", "startDate", capDates_);
    floors_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Floors", "Floor", "startDate", floorDates_);
    gearings_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Gearings", "Gearing", "startDate", gearingDates_);
    if (XMLUtils::getChildNode(node, "NakedOption"))
        nakedOption_ = XMLUtils::getChildValueAsBool(node, "NakedOption", false);
    else
        nakedOption_ = false;
}

XMLNode* DigitalCMSSpreadLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::appendNode(node, underlying_->toXML(doc));

    if (callStrikes_.size() > 0) {
        XMLUtils::addChild(doc, node, "CallPosition", to_string(callPosition_));
        XMLUtils::addChild(doc, node, "IsCallATMIncluded", isCallATMIncluded_);
        XMLUtils::addChildren(doc, node, "CallStrikes", "Strike", callStrikes_);
        XMLUtils::addChildren(doc, node, "CallPayoffs", "Payoff", callPayoffs_);
    }

    if (putStrikes_.size() > 0) {
        XMLUtils::addChild(doc, node, "PutPosition", to_string(putPosition_));
        XMLUtils::addChild(doc, node, "IsPutATMIncluded", isPutATMIncluded_);
        XMLUtils::addChildren(doc, node, "PutStrikes", "Strike", putStrikes_);
        XMLUtils::addChildren(doc, node, "PutPayoffs", "Payoff", putPayoffs_);
    }

    return node;
}

void DigitalCMSSpreadLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());

    XMLNode* underlyingNode = XMLUtils::getChildNode(node, "CMSSpreadLegData");
    underlying_ = boost::make_shared<CMSSpreadLegData>();
    underlying_->fromXML(underlyingNode);
    indices_ = underlying_->indices();

    callStrikes_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "CallStrikes", "Strike", "startDate",
                                                                      callStrikeDates_, false);
    if (callStrikes_.size() > 0) {
        string cp = XMLUtils::getChildValue(node, "CallPosition", true);
        callPosition_ = parsePositionType(cp);
        isCallATMIncluded_ = XMLUtils::getChildValueAsBool(node, "IsCallATMIncluded", true);
        callPayoffs_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "CallPayoffs", "Payoff", "startDate",
                                                                      callPayoffDates_, false);
    }

    putStrikes_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "PutStrikes", "Strike", "startDate",
                                                                     putStrikeDates_, false);
    if (putStrikes_.size() > 0) {
        string pp = XMLUtils::getChildValue(node, "PutPosition", true);
        putPosition_ = parsePositionType(pp);
        isPutATMIncluded_ = XMLUtils::getChildValueAsBool(node, "IsPutATMIncluded", true);
        putPayoffs_ = XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "PutPayoffs", "Payoff", "startDate",
                                                                     putPayoffDates_, false);
    }
}

void EquityLegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, legNodeName());
    returnType_ = XMLUtils::getChildValue(node, "ReturnType");
    if (returnType_ == "Total" && XMLUtils::getChildNode(node, "DividendFactor"))
        dividendFactor_ = XMLUtils::getChildValueAsDouble(node, "DividendFactor", true);
    else
        dividendFactor_ = 1.0;
    eqName_ = XMLUtils::getChildValue(node, "Name");
    indices_.insert("EQ-" + eqName_);
    if (XMLUtils::getChildNode(node, "InitialPrice"))
        initialPrice_ = XMLUtils::getChildValueAsDouble(node, "InitialPrice");
    else
        initialPrice_ = Real();
    fixingDays_ = XMLUtils::getChildValueAsInt(node, "FixingDays");
    XMLNode* tmp = XMLUtils::getChildNode(node, "ValuationSchedule");
    if (tmp)
        valuationSchedule_.fromXML(tmp);
    if (XMLUtils::getChildNode(node, "NotionalReset"))
        notionalReset_ = XMLUtils::getChildValueAsBool(node, "NotionalReset");
    else
        notionalReset_ = false;
}

XMLNode* EquityLegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode(legNodeName());
    XMLUtils::addChild(doc, node, "ReturnType", returnType_);
    if (returnType_ == "Total") {
        XMLUtils::addChild(doc, node, "DividendFactor", dividendFactor_);
    }
    XMLUtils::addChild(doc, node, "Name", eqName_);
    if (initialPrice_)
        XMLUtils::addChild(doc, node, "InitialPrice", initialPrice_);
    XMLUtils::addChild(doc, node, "NotionalReset", notionalReset_);

    if (valuationSchedule_.hasData())
        XMLUtils::appendNode(node, valuationSchedule_.toXML(doc));
    else
        XMLUtils::addChild(doc, node, "FixingDays", static_cast<Integer>(fixingDays_));
    return node;
}

void AmortizationData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, "AmortizationData");
    type_ = XMLUtils::getChildValue(node, "Type");
    value_ = XMLUtils::getChildValueAsDouble(node, "Value");
    startDate_ = XMLUtils::getChildValue(node, "StartDate");
    endDate_ = XMLUtils::getChildValue(node, "EndDate", false);     // optional
    frequency_ = XMLUtils::getChildValue(node, "Frequency", false); // optional
    underflow_ = XMLUtils::getChildValueAsBool(node, "Underflow");
    initialized_ = true;
}

XMLNode* AmortizationData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode("AmortizationData");
    XMLUtils::addChild(doc, node, "Type", type_);
    XMLUtils::addChild(doc, node, "Value", value_);
    XMLUtils::addChild(doc, node, "StartDate", startDate_);
    if (endDate_ != "")
        XMLUtils::addChild(doc, node, "EndDate", endDate_);
    if (frequency_ != "")
        XMLUtils::addChild(doc, node, "Frequency", frequency_);
    XMLUtils::addChild(doc, node, "Underflow", underflow_);
    return node;
}

LegData::LegData(const boost::shared_ptr<LegAdditionalData>& concreteLegData, bool isPayer, const string& currency,
                 const ScheduleData& scheduleData, const string& dayCounter, const std::vector<double>& notionals,
                 const std::vector<string>& notionalDates, const string& paymentConvention,
                 const bool notionalInitialExchange, const bool notionalFinalExchange,
                 const bool notionalAmortizingExchange, const bool isNotResetXCCY, const string& foreignCurrency,
                 const double foreignAmount, const string& fxIndex, int fixingDays, const string& fixingCalendar,
                 const std::vector<AmortizationData>& amortizationData, const int paymentLag)
    : concreteLegData_(concreteLegData), isPayer_(isPayer), currency_(currency), schedule_(scheduleData),
      dayCounter_(dayCounter), notionals_(notionals), notionalDates_(notionalDates),
      paymentConvention_(paymentConvention), notionalInitialExchange_(notionalInitialExchange),
      notionalFinalExchange_(notionalFinalExchange), notionalAmortizingExchange_(notionalAmortizingExchange),
      isNotResetXCCY_(isNotResetXCCY), foreignCurrency_(foreignCurrency), foreignAmount_(foreignAmount),
      fxIndex_(fxIndex), fixingDays_(fixingDays), fixingCalendar_(fixingCalendar), amortizationData_(amortizationData),
      paymentLag_(paymentLag) {

    indices_ = concreteLegData_->indices();
    if (!fxIndex_.empty())
        indices_.insert(fxIndex_);
}

void LegData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, "LegData");
    string legType = XMLUtils::getChildValue(node, "LegType", true);
    isPayer_ = XMLUtils::getChildValueAsBool(node, "Payer");
    currency_ = XMLUtils::getChildValue(node, "Currency", true);
    dayCounter_ = XMLUtils::getChildValue(node, "DayCounter"); // optional
    paymentConvention_ = XMLUtils::getChildValue(node, "PaymentConvention");
    paymentLag_ = XMLUtils::getChildValueAsInt(node, "PaymentLag");
    // if not given, default of getChildValueAsBool is true, which fits our needs here
    notionals_ =
        XMLUtils::getChildrenValuesAsDoublesWithAttributes(node, "Notionals", "Notional", "startDate", notionalDates_);
    XMLNode* tmp = XMLUtils::getChildNode(node, "Notionals");
    isNotResetXCCY_ = true;
    notionalInitialExchange_ = false;
    notionalFinalExchange_ = false;
    notionalAmortizingExchange_ = false;
    if (tmp) {
        XMLNode* fxResetNode = XMLUtils::getChildNode(tmp, "FXReset");
        if (fxResetNode) {
            isNotResetXCCY_ = false;
            foreignCurrency_ = XMLUtils::getChildValue(fxResetNode, "ForeignCurrency", true);
            foreignAmount_ = XMLUtils::getChildValueAsDouble(fxResetNode, "ForeignAmount", true);
            fxIndex_ = XMLUtils::getChildValue(fxResetNode, "FXIndex", true);
            indices_.insert(fxIndex_);
            fixingDays_ = XMLUtils::getChildValueAsInt(fxResetNode, "FixingDays");
            fixingCalendar_ = XMLUtils::getChildValue(fxResetNode, "FixingCalendar"); // may be empty string
            // TODO add schedule
        }
        XMLNode* exchangeNode = XMLUtils::getChildNode(tmp, "Exchanges");
        if (exchangeNode) {
            notionalInitialExchange_ = XMLUtils::getChildValueAsBool(exchangeNode, "NotionalInitialExchange");
            notionalFinalExchange_ = XMLUtils::getChildValueAsBool(exchangeNode, "NotionalFinalExchange");
            if (XMLUtils::getChildNode(exchangeNode, "NotionalAmortizingExchange"))
                notionalAmortizingExchange_ = XMLUtils::getChildValueAsBool(exchangeNode, "NotionalAmortizingExchange");
        }
    }

    XMLNode* amortizationParentNode = XMLUtils::getChildNode(node, "Amortizations");
    if (amortizationParentNode) {
        auto adNodes = XMLUtils::getChildrenNodes(amortizationParentNode, "AmortizationData");
        for (auto const& a : adNodes) {
            amortizationData_.push_back(AmortizationData());
            amortizationData_.back().fromXML(a);
        }
    }

    tmp = XMLUtils::getChildNode(node, "ScheduleData");
    if (tmp)
        schedule_.fromXML(tmp);

    concreteLegData_ = initialiseConcreteLegData(legType);
    concreteLegData_->fromXML(XMLUtils::getChildNode(node, concreteLegData_->legNodeName()));

    indices_.insert(concreteLegData_->indices().begin(), concreteLegData_->indices().end());
}

boost::shared_ptr<LegAdditionalData> LegData::initialiseConcreteLegData(const string& legType) {
    if (legType == "Fixed") {
        return boost::make_shared<FixedLegData>();
    } else if (legType == "ZeroCouponFixed") {
        return boost::make_shared<ZeroCouponFixedLegData>();
    } else if (legType == "Floating") {
        return boost::make_shared<FloatingLegData>();
    } else if (legType == "Cashflow") {
        return boost::make_shared<CashflowData>();
    } else if (legType == "CPI") {
        return boost::make_shared<CPILegData>();
    } else if (legType == "YY") {
        return boost::make_shared<YoYLegData>();
    } else if (legType == "CMS") {
        return boost::make_shared<CMSLegData>();
    } else if (legType == "CMSSpread") {
        return boost::make_shared<CMSSpreadLegData>();
    } else if (legType == "DigitalCMSSpread") {
        return boost::make_shared<DigitalCMSSpreadLegData>();
    } else if (legType == "Equity") {
        return boost::make_shared<EquityLegData>();
    } else {
        QL_FAIL("Unkown leg type " << legType);
    }
}

XMLNode* LegData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode("LegData");
    QL_REQUIRE(node, "Failed to create LegData node");
    XMLUtils::addChild(doc, node, "LegType", legType());
    XMLUtils::addChild(doc, node, "Payer", isPayer_);
    XMLUtils::addChild(doc, node, "Currency", currency_);
    if (paymentConvention_ != "")
        XMLUtils::addChild(doc, node, "PaymentConvention", paymentConvention_);
    if (paymentLag_ != 0)
        XMLUtils::addChild(doc, node, "PaymentLag", paymentLag_);
    if (dayCounter_ != "")
        XMLUtils::addChild(doc, node, "DayCounter", dayCounter_);
    XMLUtils::addChildrenWithOptionalAttributes(doc, node, "Notionals", "Notional", notionals_, "startDate",
                                                notionalDates_);
    XMLNode* notionalsNodePtr = XMLUtils::getChildNode(node, "Notionals");

    if (!isNotResetXCCY_) {
        XMLNode* resetNode = doc.allocNode("FXReset");
        XMLUtils::addChild(doc, resetNode, "ForeignCurrency", foreignCurrency_);
        XMLUtils::addChild(doc, resetNode, "ForeignAmount", foreignAmount_);
        XMLUtils::addChild(doc, resetNode, "FXIndex", fxIndex_);
        XMLUtils::addChild(doc, resetNode, "FixingDays", fixingDays_);
        XMLUtils::addChild(doc, resetNode, "FixingCalendar", fixingCalendar_);
        XMLUtils::appendNode(notionalsNodePtr, resetNode);
    }

    XMLNode* exchangeNode = doc.allocNode("Exchanges");
    XMLUtils::addChild(doc, exchangeNode, "NotionalInitialExchange", notionalInitialExchange_);
    XMLUtils::addChild(doc, exchangeNode, "NotionalFinalExchange", notionalFinalExchange_);
    XMLUtils::addChild(doc, exchangeNode, "NotionalAmortizingExchange", notionalAmortizingExchange_);
    XMLUtils::appendNode(notionalsNodePtr, exchangeNode);

    XMLUtils::appendNode(node, schedule_.toXML(doc));

    if (!amortizationData_.empty()) {
        XMLNode* amortisationsParentNode = doc.allocNode("Amortizations");
        for (auto& amort : amortizationData_) {
            if (amort.initialized()) {
                XMLUtils::appendNode(amortisationsParentNode, amort.toXML(doc));
            }
        }
        XMLUtils::appendNode(node, amortisationsParentNode);
    }

    XMLUtils::appendNode(node, concreteLegData_->toXML(doc));
    return node;
}

// Functions
Leg makeSimpleLeg(const LegData& data) {
    boost::shared_ptr<CashflowData> cashflowData = boost::dynamic_pointer_cast<CashflowData>(data.concreteLegData());
    QL_REQUIRE(cashflowData, "Wrong LegType, expected CashFlow, got " << data.legType());

    const vector<double>& amounts = cashflowData->amounts();
    const vector<string>& dates = cashflowData->dates();
    QL_REQUIRE(amounts.size() == dates.size(), "Amounts / Date size mismatch in makeSimpleLeg."
                                                   << "Amounts:" << amounts.size() << ", Dates:" << dates.size());
    Leg leg;
    for (Size i = 0; i < dates.size(); i++) {
        Date d = parseDate(dates[i]);
        leg.push_back(boost::shared_ptr<CashFlow>(new SimpleCashFlow(amounts[i], d)));
    }
    return leg;
}

Leg makeFixedLeg(const LegData& data) {
    boost::shared_ptr<FixedLegData> fixedLegData = boost::dynamic_pointer_cast<FixedLegData>(data.concreteLegData());
    QL_REQUIRE(fixedLegData, "Wrong LegType, expected Fixed, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    Calendar paymentCalendar = schedule.calendar();
    vector<double> rates = buildScheduledVector(fixedLegData->rates(), fixedLegData->rateDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);
    Natural paymentLag = data.paymentLag();
    applyAmortization(notionals, data, schedule, true, rates);
    Leg leg = FixedRateLeg(schedule)
                  .withNotionals(notionals)
                  .withCouponRates(rates, dc)
                  .withPaymentAdjustment(bdc)
                  .withPaymentLag(paymentLag)
                  .withPaymentCalendar(paymentCalendar);
    return leg;
}

Leg makeZCFixedLeg(const LegData& data) {
    boost::shared_ptr<ZeroCouponFixedLegData> zcFixedLegData =
        boost::dynamic_pointer_cast<ZeroCouponFixedLegData>(data.concreteLegData());
    QL_REQUIRE(zcFixedLegData, "Wrong LegType, expected Zero Coupon Fixed, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    // check we have a single notional and two dates in the schedule
    vector<double> notionals = data.notionals();
    int numNotionals = notionals.size();
    int numRates = zcFixedLegData->rates().size();
    int numDates = schedule.size();
    QL_REQUIRE(numDates >= 2, "Incorrect number of schedule dates entered, expected at least 2, got " << numDates);
    QL_REQUIRE(numNotionals == 1, "Incorrect number of notional values entered, expected 1, got " << numNotionals);
    QL_REQUIRE(numRates == 1, "Incorrect number of rate values entered, expected 1, got " << numRates);

    // coupon
    Rate fixedRate = zcFixedLegData->rates()[0];
    Real fixedAmount = notionals[0];
    vector<Date> dates = schedule.dates();

    Compounding comp = parseCompounding(zcFixedLegData->compounding());
    QL_REQUIRE(comp == QuantLib::Compounded || comp == QuantLib::Simple, "Compounding method " << zcFixedLegData->compounding() << " not supported");

    // we loop over the dates in the schedule, computing the compound factor.
    // For the Compounded rule:
    // (1+r)^dcf_0 *  (1+r)^dcf_1 * ... = (1+r)^(dcf_0 + dcf_1 + ...)
    // So we compute the sum of all DayCountFractions in the loop.
    // For the Simple rule:
    // (1 + r * dcf_0) * (1 + r * dcf_1)...
    double totalDCF = 0;
    double compoundFactor = 1;
    for (Size i = 0; i < dates.size() - 1; i++) {
        double dcf = dc.yearFraction(dates[i], dates[i+1]);
        if (comp == QuantLib::Simple)
            compoundFactor *= (1 + fixedRate * dcf);
        else
            totalDCF += dcf;
    }
    if (comp == QuantLib::Compounded)
        compoundFactor = pow(1.0 + fixedRate, totalDCF);
    fixedAmount *= (compoundFactor - 1);
    Date maturity = schedule.endDate();
    Date fixedPayDate = schedule.calendar().adjust(maturity, bdc);

    Leg leg;
    leg.push_back(boost::shared_ptr<CashFlow>(new SimpleCashFlow(fixedAmount, fixedPayDate)));

    return leg;
}

Leg makeIborLeg(const LegData& data, const boost::shared_ptr<IborIndex>& index,
                const boost::shared_ptr<EngineFactory>& engineFactory, const bool attachPricer) {
    boost::shared_ptr<FloatingLegData> floatData = boost::dynamic_pointer_cast<FloatingLegData>(data.concreteLegData());
    QL_REQUIRE(floatData, "Wrong LegType, expected Floating, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());

    bool hasCapsFloors = floatData->caps().size() > 0 || floatData->floors().size() > 0;
    vector<double> notionals = buildScheduledVectorNormalised(data.notionals(), data.notionalDates(), schedule, 0.0);
    vector<double> spreads = buildScheduledVectorNormalised(floatData->spreads(), floatData->spreadDates(), schedule, 0.0);
    vector<double> gearings = buildScheduledVectorNormalised(floatData->gearings(), floatData->gearingDates(), schedule, 1.0);

    applyAmortization(notionals, data, schedule, true);
    // handle float annuity, which is not done in applyAmortization, for this we can only have one block
    if (!data.amortizationData().empty()) {
        AmortizationType amortizationType = parseAmortizationType(data.amortizationData().front().type());
        if (amortizationType == AmortizationType::Annuity) {
            LOG("Build floating annuity notional schedule");
            QL_REQUIRE(!hasCapsFloors, "Caps/Floors not supported in floating annuity coupons");
            QL_REQUIRE(floatData->gearings().size() == 0, "Gearings not supported in floating annuity coupons");
            DayCounter dc = index->dayCounter();
            Date startDate = parseDate(data.amortizationData().front().startDate());
            double annuity = data.amortizationData().front().value();
            bool underflow = data.amortizationData().front().underflow();
            vector<boost::shared_ptr<Coupon>> coupons;
            for (Size i = 0; i < schedule.size() - 1; i++) {
                Date paymentDate = schedule.calendar().adjust(schedule[i+1], bdc);
                if (schedule[i] < startDate || i == 0) {
                    boost::shared_ptr<FloatingRateCoupon> coupon;
                    if(!floatData->hasSubPeriods()) {
                        coupon = boost::make_shared<IborCoupon>(paymentDate, notionals[i], schedule[i], schedule[i + 1],
                                                       floatData->fixingDays(), index, gearings[i], spreads[i], Date(),
                                                       Date(), dc, floatData->isInArrears());
                        coupon->setPricer(boost::make_shared<BlackIborCouponPricer>());
                    }
                    else {
                        coupon = boost::make_shared<SubPeriodsCoupon>(
                            paymentDate, notionals[i], schedule[i], schedule[i + 1], index,
                            floatData->isAveraged() ? SubPeriodsCoupon::Averaging : SubPeriodsCoupon::Compounding,
                            index->businessDayConvention(), spreads[i], dc, floatData->includeSpread(), gearings[i]);
                        coupon->setPricer(boost::make_shared<SubPeriodsCouponPricer>());
                    }
                    coupons.push_back(coupon);
                    LOG("FloatingAnnuityCoupon: " << i << " " << coupon->nominal() << " " << coupon->amount());
                } else {
                    QL_REQUIRE(coupons.size() > 0,
                               "FloatingAnnuityCoupon needs at least one predecessor, e.g. a plain IborCoupon");
                    LOG("FloatingAnnuityCoupon, previous nominal/coupon: " << i << " " << coupons.back()->nominal()
                                                                           << " " << coupons.back()->amount());
                    boost::shared_ptr<QuantExt::FloatingAnnuityCoupon> coupon =
                        boost::make_shared<QuantExt::FloatingAnnuityCoupon>(
                            annuity, underflow, coupons.back(), paymentDate, schedule[i], schedule[i + 1],
                            floatData->fixingDays(), index, gearings[i], spreads[i], Date(), Date(), dc,
                            floatData->isInArrears());
                    coupons.push_back(coupon);
                    LOG("FloatingAnnuityCoupon: " << i << " " << coupon->nominal() << " " << coupon->amount());
                }
            }
            Leg leg;
            for (Size i = 0; i < coupons.size(); i++)
                leg.push_back(coupons[i]);
            LOG("Floating annuity notional schedule done");
            return leg;
        }
    }

    if(floatData->hasSubPeriods()) {
        QL_REQUIRE(floatData->caps().empty() && floatData->floors().empty(),
                   "SubPeriodsLegs does not support caps or floors");
        QL_REQUIRE(!floatData->isInArrears(), "SubPeriodLegs do not support in aarears fixings");
        Leg leg = SubPeriodsLeg(schedule, index)
            .withNotionals(notionals)
            .withPaymentDayCounter(dc)
            .withPaymentAdjustment(bdc)
            .withGearings(gearings)
            .withSpreads(spreads)
            .withType(floatData->isAveraged() ? SubPeriodsCoupon::Averaging : SubPeriodsCoupon::Compounding)
            .includeSpread(floatData->includeSpread());
        QuantExt::setCouponPricer(leg, boost::make_shared<SubPeriodsCouponPricer>());
        return leg;
    }

    IborLeg iborLeg = IborLeg(schedule, index)
                          .withNotionals(notionals)
                          .withSpreads(spreads)
                          .withPaymentDayCounter(dc)
                          .withPaymentAdjustment(bdc)
                          .withFixingDays(floatData->fixingDays())
                          .inArrears(floatData->isInArrears())
                          .withGearings(gearings)
                          .withPaymentLag(data.paymentLag());

    // If no caps or floors or in arrears fixing, return the leg
    if (!hasCapsFloors && !floatData->isInArrears())
        return iborLeg;

    // If there are caps or floors or in arrears fixing, add them and set pricer
    if (floatData->caps().size() > 0)
        iborLeg.withCaps(buildScheduledVector(floatData->caps(), floatData->capDates(), schedule));

    if (floatData->floors().size() > 0)
        iborLeg.withFloors(buildScheduledVector(floatData->floors(), floatData->floorDates(), schedule));

    if (!attachPricer)
        return iborLeg;

    // Get a coupon pricer for the leg
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder("CapFlooredIborLeg");
    QL_REQUIRE(builder, "No builder found for CapFlooredIborLeg");
    boost::shared_ptr<CapFlooredIborLegEngineBuilder> cappedFlooredIborBuilder =
        boost::dynamic_pointer_cast<CapFlooredIborLegEngineBuilder>(builder);
    boost::shared_ptr<FloatingRateCouponPricer> couponPricer = cappedFlooredIborBuilder->engine(index->currency());

    // Loop over the coupons in the leg and set pricer
    Leg tmpLeg = iborLeg;
    QuantLib::setCouponPricer(tmpLeg, couponPricer);

    // build naked option leg if required
    if (floatData->nakedOption()) {
        tmpLeg = StrippedCappedFlooredCouponLeg(tmpLeg);
        // fix for missing registration in ql 1.13
        for (auto const& t : tmpLeg) {
            auto s = boost::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(t);
            if (s != nullptr)
                s->registerWith(s->underlying());
        }
    }
    return tmpLeg;
} // namespace data

Leg makeOISLeg(const LegData& data, const boost::shared_ptr<OvernightIndex>& index) {
    boost::shared_ptr<FloatingLegData> floatData = boost::dynamic_pointer_cast<FloatingLegData>(data.concreteLegData());
    QL_REQUIRE(floatData, "Wrong LegType, expected Floating, got " << data.legType());

    if (floatData->caps().size() > 0 || floatData->floors().size() > 0)
        QL_FAIL("Caps and floors are not supported for OIS legs");

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    Natural paymentLag = data.paymentLag();
    Calendar paymentCalendar = index->fixingCalendar();
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);
    vector<double> spreads = buildScheduledVector(floatData->spreads(), floatData->spreadDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    if (floatData->isAveraged()) {

        boost::shared_ptr<QuantExt::AverageONIndexedCouponPricer> couponPricer =
            boost::make_shared<QuantExt::AverageONIndexedCouponPricer>();
        QuantExt::AverageONLeg leg = QuantExt::AverageONLeg(schedule, index)
                                         .withNotionals(notionals)
                                         .withSpreads(spreads)
                                         .withPaymentDayCounter(dc)
                                         .withPaymentAdjustment(bdc)
                                         .withRateCutoff(2)
                                         .withAverageONIndexedCouponPricer(couponPricer);

        if (floatData->gearings().size() > 0)
            leg.withGearings(buildScheduledVector(floatData->gearings(), floatData->gearingDates(), schedule));

        return leg;

    } else {

        vector<Real> gearings{ 1.0 };
        if (!floatData->gearings().empty())
            gearings = buildScheduledVector(floatData->gearings(), floatData->gearingDates(), schedule);

        Leg leg = OvernightLeg(schedule, index)
                               .withNotionals(notionals)
                               .withSpreads(spreads)
                               .withPaymentDayCounter(dc)
                               .withPaymentAdjustment(bdc)
                               .withPaymentCalendar(paymentCalendar)
                               .withPaymentLag(paymentLag)
                               .withGearings(gearings);

        // If the overnight index is BRL CDI, we need a special coupon pricer
        boost::shared_ptr<BRLCdi> brlCdiIndex = boost::dynamic_pointer_cast<BRLCdi>(index);
        if (brlCdiIndex)
            QuantExt::setCouponPricer(leg, boost::make_shared<BRLCdiCouponPricer>());

        return leg;
    }
}

Leg makeBMALeg(const LegData& data, const boost::shared_ptr<QuantExt::BMAIndexWrapper>& indexWrapper) {
    boost::shared_ptr<FloatingLegData> floatData = boost::dynamic_pointer_cast<FloatingLegData>(data.concreteLegData());
    QL_REQUIRE(floatData, "Wrong LegType, expected Floating, got " << data.legType());
    boost::shared_ptr<BMAIndex> index = indexWrapper->bma();

    if (floatData->caps().size() > 0 || floatData->floors().size() > 0)
        QL_FAIL("Caps and floors are not supported for BMA legs");

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());

    vector<Real> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);
    vector<Real> spreads = buildScheduledVector(floatData->spreads(), floatData->spreadDates(), schedule);
    vector<Real> gearings = buildScheduledVector(floatData->gearings(), floatData->gearingDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    AverageBMALeg leg = AverageBMALeg(schedule, index)
                            .withNotionals(notionals)
                            .withSpreads(spreads)
                            .withPaymentDayCounter(dc)
                            .withPaymentAdjustment(bdc)
                            .withGearings(gearings);

    return leg;
}

Leg makeNotionalLeg(const Leg& refLeg, const bool initNomFlow, const bool finalNomFlow, const bool amortNomFlow) {

    // Assumption - Cashflows on Input Leg are all coupons
    // This is the Leg to be populated
    Leg leg;

    // Initial Flow Amount
    if (initNomFlow) {
        double initFlowAmt = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[0])->nominal();
        Date initDate = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[0])->accrualStartDate();
        if (initFlowAmt != 0)
            leg.push_back(boost::shared_ptr<CashFlow>(new SimpleCashFlow(-initFlowAmt, initDate)));
    }

    // Amortization Flows
    if (amortNomFlow) {
        for (Size i = 1; i < refLeg.size(); i++) {
            Date flowDate = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[i])->accrualStartDate();
            Real initNom = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[i - 1])->nominal();
            Real newNom = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg[i])->nominal();
            Real flow = initNom - newNom;
            if (flow != 0)
                leg.push_back(boost::shared_ptr<CashFlow>(new SimpleCashFlow(flow, flowDate)));
        }
    }

    // Final Nominal Return at Maturity
    if (finalNomFlow) {
        double finalNomFlow = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg.back())->nominal();
        Date finalDate = boost::dynamic_pointer_cast<QuantLib::Coupon>(refLeg.back())->date();
        if (finalNomFlow != 0)
            leg.push_back(boost::shared_ptr<CashFlow>(new SimpleCashFlow(finalNomFlow, finalDate)));
    }

    return leg;
}

Leg makeCPILeg(const LegData& data, const boost::shared_ptr<ZeroInflationIndex>& index) {
    boost::shared_ptr<CPILegData> cpiLegData = boost::dynamic_pointer_cast<CPILegData>(data.concreteLegData());
    QL_REQUIRE(cpiLegData, "Wrong LegType, expected CPI, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    Period observationLag = parsePeriod(cpiLegData->observationLag());
    CPI::InterpolationType interpolationMethod = CPI::Flat;
    if (cpiLegData->interpolated())
        interpolationMethod = CPI::Linear;
    vector<double> rates = buildScheduledVector(cpiLegData->rates(), cpiLegData->rateDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    Leg leg = CPILeg(schedule, index, cpiLegData->baseCPI(), observationLag)
                  .withNotionals(notionals)
                  .withPaymentDayCounter(dc)
                  .withPaymentAdjustment(bdc)
                  .withPaymentCalendar(schedule.calendar())
                  .withFixedRates(rates)
                  .withObservationInterpolation(interpolationMethod)
                  .withSubtractInflationNominal(cpiLegData->subtractInflationNominal());
    Size n = leg.size();
    QL_REQUIRE(n > 0, "Empty CPI Leg");

    // QuantLib CPILeg automatically adds a Notional Cashflow at maturity date on a CPI swap
    // If Notional Exchange set to false, remove the final cashflow.

    if (!data.notionalFinalExchange()) {
        boost::shared_ptr<CPICashFlow> cpicf = boost::dynamic_pointer_cast<CPICashFlow>(leg[n - 1]);
        boost::shared_ptr<Coupon> coupon = boost::dynamic_pointer_cast<Coupon>(leg[n - 2]);
        if (cpicf && (cpicf->date() == coupon->date()))
            leg.pop_back();
    }

    return leg;
}

Leg makeYoYLeg(const LegData& data, const boost::shared_ptr<YoYInflationIndex>& index, const boost::shared_ptr<EngineFactory>& engineFactory) {
    boost::shared_ptr<YoYLegData> yoyLegData = boost::dynamic_pointer_cast<YoYLegData>(data.concreteLegData());
    QL_REQUIRE(yoyLegData, "Wrong LegType, expected YoY, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    Period observationLag = parsePeriod(yoyLegData->observationLag());
    vector<double> gearings = buildScheduledVector(yoyLegData->gearings(), yoyLegData->gearingDates(), schedule);
    vector<double> spreads = buildScheduledVector(yoyLegData->spreads(), yoyLegData->spreadDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    bool couponCap = yoyLegData->caps().size() > 0;
    bool couponFloor = yoyLegData->floors().size() > 0;
    bool couponCapFloor = yoyLegData->caps().size() > 0 || yoyLegData->floors().size() > 0;

    applyAmortization(notionals, data, schedule, false);

    yoyInflationLeg yoyLeg = yoyInflationLeg(schedule, schedule.calendar(), index, observationLag)
                  .withNotionals(notionals)
                  .withPaymentDayCounter(dc)
                  .withPaymentAdjustment(bdc)
                  .withFixingDays(yoyLegData->fixingDays())
                  .withGearings(gearings)
                  .withSpreads(spreads);

    if (couponCap)
        yoyLeg.withCaps(buildScheduledVector(yoyLegData->caps(), yoyLegData->capDates(), schedule));

    if (couponFloor)
        yoyLeg.withFloors(buildScheduledVector(yoyLegData->floors(), yoyLegData->floorDates(), schedule));

    if (couponCapFloor) {
        // get a coupon pricer for the leg
        boost::shared_ptr<EngineBuilder> builder = engineFactory->builder("CapFlooredYYLeg");
        QL_REQUIRE(builder, "No builder found for CapFlooredIborLeg");
        boost::shared_ptr<CapFlooredYoYLegEngineBuilder> cappedFlooredYoYBuilder =
            boost::dynamic_pointer_cast<CapFlooredYoYLegEngineBuilder>(builder);
        string indexname = index->name();
        boost::shared_ptr<InflationCouponPricer> couponPricer =
            cappedFlooredYoYBuilder->engine(indexname.replace(indexname.find(" ", 0), 1, ""));

        // set coupon pricer for the leg
        Leg leg = yoyLeg.operator Leg();
        for (Size i = 0; i < leg.size(); i++) {
            boost::dynamic_pointer_cast<CappedFlooredYoYInflationCoupon>(leg[i])
                ->setPricer(boost::dynamic_pointer_cast<QuantLib::YoYInflationCouponPricer>(couponPricer));
        }

        // build naked option leg if required
        if (yoyLegData->nakedOption()) {
            leg = StrippedCappedFlooredYoYInflationCouponLeg(leg);
            for (auto const& t : leg) {
                auto s = boost::dynamic_pointer_cast<StrippedCappedFlooredYoYInflationCoupon>(t);
                if (s != nullptr)
                    s->registerWith(s->underlying());
            }
        }

        return leg;
    }
    else {
        return yoyLeg;
    }
}

Leg makeCMSLeg(const LegData& data, const boost::shared_ptr<QuantLib::SwapIndex>& swapIndex,
               const boost::shared_ptr<EngineFactory>& engineFactory, const vector<double>& caps,
               const vector<double>& floors, const bool attachPricer) {
    boost::shared_ptr<CMSLegData> cmsData = boost::dynamic_pointer_cast<CMSLegData>(data.concreteLegData());
    QL_REQUIRE(cmsData, "Wrong LegType, expected CMS, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    bool couponCapFloor = cmsData->caps().size() > 0 || cmsData->floors().size() > 0;
    bool nakedCapFloor = caps.size() > 0 || floors.size() > 0;

    vector<double> spreads = ore::data::buildScheduledVector(cmsData->spreads(), cmsData->spreadDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    CmsLeg cmsLeg = CmsLeg(schedule, swapIndex)
                        .withNotionals(notionals)
                        .withSpreads(spreads)
                        .withPaymentDayCounter(dc)
                        .withPaymentAdjustment(bdc)
                        .withFixingDays(cmsData->fixingDays())
                        .inArrears(cmsData->isInArrears());

    if (cmsData->gearings().size() > 0)
        cmsLeg.withGearings(buildScheduledVector(cmsData->gearings(), cmsData->gearingDates(), schedule));

    if (nakedCapFloor) {
        vector<string> capFloorDates;

        if (caps.size() > 0)
            cmsLeg.withCaps(buildScheduledVector(caps, capFloorDates, schedule));

        if (floors.size() > 0)
            cmsLeg.withFloors(buildScheduledVector(floors, capFloorDates, schedule));
    } else if (couponCapFloor) {
        if (cmsData->caps().size() > 0)
            cmsLeg.withCaps(buildScheduledVector(cmsData->caps(), cmsData->capDates(), schedule));

        if (cmsData->floors().size() > 0)
            cmsLeg.withFloors(buildScheduledVector(cmsData->floors(), cmsData->floorDates(), schedule));
    }

    if (!attachPricer)
        return cmsLeg;

    // Get a coupon pricer for the leg
    boost::shared_ptr<EngineBuilder> builder = engineFactory->builder("CMS");
    QL_REQUIRE(builder, "No builder found for CmsLeg");
    boost::shared_ptr<CmsCouponPricerBuilder> cmsSwapBuilder =
        boost::dynamic_pointer_cast<CmsCouponPricerBuilder>(builder);
    boost::shared_ptr<FloatingRateCouponPricer> couponPricer = cmsSwapBuilder->engine(swapIndex->currency());

    // Loop over the coupons in the leg and set pricer
    Leg tmpLeg = cmsLeg;
    QuantLib::setCouponPricer(tmpLeg, couponPricer);

    // build naked option leg if required
    if (cmsData->nakedOption()) {
        tmpLeg = StrippedCappedFlooredCouponLeg(tmpLeg);
        // fix for missing registration in ql 1.13
        for (auto const& t : tmpLeg) {
            auto s = boost::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(t);
            if (s != nullptr)
                s->registerWith(s->underlying());
        }
    }
    return tmpLeg;
}

Leg makeCMSSpreadLeg(const LegData& data, const boost::shared_ptr<QuantLib::SwapSpreadIndex>& swapSpreadIndex,
                     const boost::shared_ptr<EngineFactory>& engineFactory, const bool attachPricer) {
#if QL_HEX_VERSION < 0x011300f0
    WLOG("The CMS Spread implementation in older QL versions has issues (found "
         << QL_VERSION << "), consider upgrading to at least 1.13")
#endif
    boost::shared_ptr<CMSSpreadLegData> cmsSpreadData =
        boost::dynamic_pointer_cast<CMSSpreadLegData>(data.concreteLegData());
    QL_REQUIRE(cmsSpreadData, "Wrong LegType, expected CMSSpread, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    vector<double> spreads =
        ore::data::buildScheduledVector(cmsSpreadData->spreads(), cmsSpreadData->spreadDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    CmsSpreadLeg cmsSpreadLeg = CmsSpreadLeg(schedule, swapSpreadIndex)
                                    .withNotionals(notionals)
                                    .withSpreads(spreads)
                                    .withPaymentDayCounter(dc)
                                    .withPaymentAdjustment(bdc)
                                    .withFixingDays(cmsSpreadData->fixingDays())
                                    .inArrears(cmsSpreadData->isInArrears());

    if (cmsSpreadData->gearings().size() > 0)
        cmsSpreadLeg.withGearings(
            buildScheduledVector(cmsSpreadData->gearings(), cmsSpreadData->gearingDates(), schedule));

    if (cmsSpreadData->caps().size() > 0)
        cmsSpreadLeg.withCaps(buildScheduledVector(cmsSpreadData->caps(), cmsSpreadData->capDates(), schedule));

    if (cmsSpreadData->floors().size() > 0)
        cmsSpreadLeg.withFloors(buildScheduledVector(cmsSpreadData->floors(), cmsSpreadData->floorDates(), schedule));

    if (!attachPricer)
        return cmsSpreadLeg;

    // Get a coupon pricer for the leg
    auto builder1 = engineFactory->builder("CMS");
    QL_REQUIRE(builder1, "No CMS builder found for CmsSpreadLeg");
    auto cmsBuilder = boost::dynamic_pointer_cast<CmsCouponPricerBuilder>(builder1);
    auto cmsPricer = boost::dynamic_pointer_cast<CmsCouponPricer>(cmsBuilder->engine(swapSpreadIndex->currency()));
    QL_REQUIRE(cmsPricer, "Expected CMS Pricer");
    auto builder2 = engineFactory->builder("CMSSpread");
    QL_REQUIRE(builder2, "No CMS Spread builder found for CmsSpreadLeg");
    auto cmsSpreadBuilder = boost::dynamic_pointer_cast<CmsSpreadCouponPricerBuilder>(builder2);
    auto cmsSpreadPricer = cmsSpreadBuilder->engine(swapSpreadIndex->currency(), cmsSpreadData->swapIndex1(),
                                                    cmsSpreadData->swapIndex2(), cmsPricer);
    QL_REQUIRE(cmsSpreadPricer, "Expected CMS Spread Pricer");

    // Loop over the coupons in the leg and set pricer
    Leg tmpLeg = cmsSpreadLeg;
    QuantLib::setCouponPricer(tmpLeg, cmsSpreadPricer);

    // build naked option leg if required
    if (cmsSpreadData->nakedOption()) {
        tmpLeg = StrippedCappedFlooredCouponLeg(tmpLeg);
        // fix for missing registration in ql 1.13
        for (auto const& t : tmpLeg) {
            auto s = boost::dynamic_pointer_cast<StrippedCappedFlooredCoupon>(t);
            if (s != nullptr)
                s->registerWith(s->underlying());
        }
    }
    return tmpLeg;
}

Leg makeDigitalCMSSpreadLeg(const LegData& data, const boost::shared_ptr<QuantLib::SwapSpreadIndex>& swapSpreadIndex,
                            const boost::shared_ptr<EngineFactory>& engineFactory) {
#if QL_HEX_VERSION < 0x011300f0
    WLOG("The CMS Spread implementation in older QL versions has issues (found "
         << QL_VERSION << "), consider upgrading to at least 1.13")
#endif
    auto digitalCmsSpreadData = boost::dynamic_pointer_cast<DigitalCMSSpreadLegData>(data.concreteLegData());
    QL_REQUIRE(digitalCmsSpreadData, "Wrong LegType, expected DigitalCMSSpread");

    auto cmsSpreadData = boost::dynamic_pointer_cast<CMSSpreadLegData>(digitalCmsSpreadData->underlying());
    QL_REQUIRE(cmsSpreadData, "Incomplete DigitalCmsSpread Leg, expected CMSSpread data");

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    vector<double> spreads =
        ore::data::buildScheduledVector(cmsSpreadData->spreads(), cmsSpreadData->spreadDates(), schedule);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    double eps = 1e-4;
    vector<double> callStrikes = ore::data::buildScheduledVector(digitalCmsSpreadData->callStrikes(),
                                                                 digitalCmsSpreadData->callStrikeDates(), schedule);

    for (Size i = 0; i < callStrikes.size(); i++) {
        if (std::fabs(callStrikes[i]) < eps / 2) {
            callStrikes[i] = eps / 2;
        }
    }

    vector<double> callPayoffs = ore::data::buildScheduledVector(digitalCmsSpreadData->callPayoffs(),
                                                                 digitalCmsSpreadData->callPayoffDates(), schedule);

    vector<double> putStrikes = ore::data::buildScheduledVector(digitalCmsSpreadData->putStrikes(),
                                                                digitalCmsSpreadData->putStrikeDates(), schedule);
    vector<double> putPayoffs = ore::data::buildScheduledVector(digitalCmsSpreadData->putPayoffs(),
                                                                digitalCmsSpreadData->putPayoffDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    DigitalCmsSpreadLeg digitalCmsSpreadLeg = DigitalCmsSpreadLeg(schedule, swapSpreadIndex)
                                                  .withNotionals(notionals)
                                                  .withSpreads(spreads)
                                                  .withPaymentDayCounter(dc)
                                                  .withPaymentAdjustment(bdc)
                                                  .withFixingDays(cmsSpreadData->fixingDays())
                                                  .inArrears(cmsSpreadData->isInArrears())
                                                  .withCallStrikes(callStrikes)
                                                  .withLongCallOption(digitalCmsSpreadData->callPosition())
                                                  .withCallATM(digitalCmsSpreadData->isCallATMIncluded())
                                                  .withCallPayoffs(callPayoffs)
                                                  .withPutStrikes(putStrikes)
                                                  .withLongPutOption(digitalCmsSpreadData->putPosition())
                                                  .withPutATM(digitalCmsSpreadData->isPutATMIncluded())
                                                  .withPutPayoffs(putPayoffs)
                                                  .withReplication(boost::make_shared<DigitalReplication>());

    if (cmsSpreadData->gearings().size() > 0)
        digitalCmsSpreadLeg.withGearings(
            buildScheduledVector(cmsSpreadData->gearings(), cmsSpreadData->gearingDates(), schedule));

    if (cmsSpreadData->caps().size() > 0 || cmsSpreadData->floors().size() > 0)
        QL_FAIL("caps/floors not supported in DigitalCMSSpreadOptions");

    // Get a coupon pricer for the leg
    auto builder1 = engineFactory->builder("CMS");
    QL_REQUIRE(builder1, "No CMS builder found for CmsSpreadLeg");
    auto cmsBuilder = boost::dynamic_pointer_cast<CmsCouponPricerBuilder>(builder1);
    auto cmsPricer = boost::dynamic_pointer_cast<CmsCouponPricer>(cmsBuilder->engine(swapSpreadIndex->currency()));
    QL_REQUIRE(cmsPricer, "Expected CMS Pricer");
    auto builder2 = engineFactory->builder("CMSSpread");
    QL_REQUIRE(builder2, "No CMS Spread builder found for CmsSpreadLeg");
    auto cmsSpreadBuilder = boost::dynamic_pointer_cast<CmsSpreadCouponPricerBuilder>(builder2);
    auto cmsSpreadPricer = cmsSpreadBuilder->engine(swapSpreadIndex->currency(), cmsSpreadData->swapIndex1(),
                                                    cmsSpreadData->swapIndex2(), cmsPricer);
    QL_REQUIRE(cmsSpreadPricer, "Expected CMS Spread Pricer");

    // Loop over the coupons in the leg and set pricer
    Leg tmpLeg = digitalCmsSpreadLeg;
    QuantLib::setCouponPricer(tmpLeg, cmsSpreadPricer);

    return tmpLeg;
}

Leg makeEquityLeg(const LegData& data, const boost::shared_ptr<EquityIndex>& equityCurve) {
    boost::shared_ptr<EquityLegData> eqLegData = boost::dynamic_pointer_cast<EquityLegData>(data.concreteLegData());
    QL_REQUIRE(eqLegData, "Wrong LegType, expected Equity, got " << data.legType());

    Schedule schedule = makeSchedule(data.schedule());
    DayCounter dc = parseDayCounter(data.dayCounter());
    BusinessDayConvention bdc = parseBusinessDayConvention(data.paymentConvention());
    bool isTotalReturn = eqLegData->returnType() == "Total";
    Real dividendFactor = eqLegData->dividendFactor();
    Real initialPrice = eqLegData->initialPrice();
    bool notionalReset = eqLegData->notionalReset();
    Natural fixingDays = eqLegData->fixingDays();
    ScheduleData valuationData = eqLegData->valuationSchedule();
    Schedule valuationSchedule;
    if (valuationData.hasData())
        valuationSchedule = makeSchedule(valuationData);
    vector<double> notionals = buildScheduledVector(data.notionals(), data.notionalDates(), schedule);

    applyAmortization(notionals, data, schedule, false);

    Leg leg = EquityLeg(schedule, equityCurve)
                  .withNotionals(notionals)
                  .withPaymentDayCounter(dc)
                  .withPaymentAdjustment(bdc)
                  .withTotalReturn(isTotalReturn)
                  .withDividendFactor(dividendFactor)
                  .withInitialPrice(initialPrice)
                  .withNotionalReset(notionalReset)
                  .withFixingDays(fixingDays)
                  .withValuationSchedule(valuationSchedule);

    QL_REQUIRE(leg.size() > 0, "Empty Equity Leg");

    return leg;
}

Real currentNotional(const Leg& leg) {
    Date today = Settings::instance().evaluationDate();
    // assume the leg is sorted
    // We just take the first coupon::nominal we find, otherwise return 0
    for (auto cf : leg) {
        if (cf->date() >= today) {
            boost::shared_ptr<Coupon> coupon = boost::dynamic_pointer_cast<QuantLib::Coupon>(cf);
            if (coupon)
                return coupon->nominal();
        }
    }
    return 0;
}

// e.g. node is Notionals, get all the children Notional
vector<double> buildScheduledVector(const vector<double>& values, const vector<string>& dates,
                                    const Schedule& schedule) {
    if (values.size() < 2 || dates.size() == 0)
        return values;

    QL_REQUIRE(values.size() == dates.size(), "Value / Date size mismatch in buildScheduledVector."
                                                  << "Value:" << values.size() << ", Dates:" << dates.size());

    // Need to use schedule logic
    // Length of data will be 1 less than schedule
    //
    // Notional 100
    // Notional {startDate 2015-01-01} 200
    // Notional {startDate 2016-01-01} 300
    //
    // Given schedule June, Dec from 2014 to 2016 (6 dates, 5 coupons)
    // we return 100, 100, 200, 200, 300

    // The first node must not have a date.
    // If the second one has a date, all the rest must have, and we process
    // If the second one does not have a date, none of them must have one
    // and we return the vector uneffected.
    QL_REQUIRE(dates[0] == "", "Invalid date " << dates[0] << " for first node");
    if (dates[1] == "") {
        // They must all be empty and then we return values
        for (Size i = 2; i < dates.size(); i++) {
            QL_REQUIRE(dates[i] == "", "Invalid date " << dates[i] << " for node " << i
                                                       << ". Cannot mix dates and non-dates attributes");
        }
        return values;
    }

    // We have nodes with date attributes now
    Size len = schedule.size() - 1;
    vector<double> data(len);
    Size j = 0, max_j = dates.size() - 1; // j is the index of date/value vector. 0 to max_j
    Date d = parseDate(dates[j + 1]);     // The first start date
    for (Size i = 0; i < len; i++) {      // loop over data vector and populate it.
        // If j == max_j we just fall through and take the final value
        while (schedule[i] >= d && j < max_j) {
            j++;
            if (j < max_j) {
                QL_REQUIRE(dates[j + 1] != "", "Cannot have empty date attribute for node " << j + 1);
                d = parseDate(dates[j + 1]);
            }
        }
        data[i] = values[j];
    }

    return data;
}

vector<double> normaliseToSchedule(const vector<double>& values, const Schedule& schedule, const Real defaultValue) {
    vector<double> res = values;
    if (res.size() < schedule.size() - 1)
        res.resize(schedule.size() - 1, res.size() == 0 ? defaultValue : res.back());
    return res;
}

vector<double> buildScheduledVectorNormalised(const vector<double>& values, const vector<string>& dates,
                                              const Schedule& schedule, const Real defaultValue) {
    return normaliseToSchedule(buildScheduledVector(values, dates, schedule), schedule, defaultValue);
}

vector<double> buildAmortizationScheduleFixedAmount(const vector<double>& notionals, const Schedule& schedule,
                                                    const AmortizationData& data) {
    LOG("Build fixed amortization notional schedule");
    vector<double> nominals = normaliseToSchedule(notionals, schedule);
    Date startDate = parseDate(data.startDate());
    Date endDate = data.endDate() == "" ? Date::maxDate() : parseDate(data.endDate());
    bool underflow = data.underflow();
    Period amortPeriod = parsePeriod(data.frequency());
    double amort = data.value();
    Date lastAmortDate = Date::minDate();
    for (Size i = 0; i < schedule.size() - 1; i++) {
        if (i > 0 && schedule[i] > lastAmortDate + amortPeriod - 4 * Days && schedule[i] >= startDate &&
            schedule[i] < endDate) { // FIXME: tolerance
            nominals[i] = nominals[i - 1] - amort;
            lastAmortDate = schedule[i];
        } else if (i > 0 && schedule[i] >= endDate) {
            nominals[i] = nominals[i - 1];
        }
        if (amort > nominals[i] && underflow == false)
            amort = std::max(nominals[i], 0.0);
    }
    LOG("Fixed amortization notional schedule done");
    return nominals;
}

vector<double> buildAmortizationScheduleRelativeToInitialNotional(const vector<double>& notionals,
                                                                  const Schedule& schedule,
                                                                  const AmortizationData& data) {
    LOG("Build notional schedule with amortizations expressed as percentages of initial notional");
    vector<double> nominals = normaliseToSchedule(notionals, schedule);
    Date startDate = parseDate(data.startDate());
    Date endDate = data.endDate() == "" ? Date::maxDate() : parseDate(data.endDate());
    bool underflow = data.underflow();
    Period amortPeriod = parsePeriod(data.frequency());
    double fraction = data.value(); // first difference to "FixedAmount"
    QL_REQUIRE(fraction >= 0.0 && fraction <= 1.0,
               "amortization fraction " << fraction << " out of range"); // second difference to "FixedAmount"
    double amort = fraction * nominals.front();                          // third difference to "FixedAmount"
    Date lastAmortDate = Date::minDate();
    for (Size i = 0; i < schedule.size() - 1; i++) {
        if (i > 0 && schedule[i] > lastAmortDate + amortPeriod - 4 * Days && schedule[i] >= startDate &&
            schedule[i] < endDate) { // FIXME: tolerance
            nominals[i] = nominals[i - 1] - amort;
            lastAmortDate = schedule[i];
        } else if (i > 0 && schedule[i] >= endDate)
            nominals[i] = nominals[i - 1];
        if (amort > nominals[i] && underflow == false) {
            amort = std::max(nominals[i], 0.0);
        }
    }
    LOG("Fixed amortization notional schedule done");
    return nominals;
}

vector<double> buildAmortizationScheduleRelativeToPreviousNotional(const vector<double>& notionals,
                                                                   const Schedule& schedule,
                                                                   const AmortizationData& data) {
    LOG("Build notional schedule with amortizations expressed as percentages of previous notionals");
    vector<double> nominals = normaliseToSchedule(notionals, schedule);
    Date startDate = parseDate(data.startDate());
    Date endDate = data.endDate() == "" ? Date::maxDate() : parseDate(data.endDate());
    Period amortPeriod = parsePeriod(data.frequency());
    double fraction = data.value();
    QL_REQUIRE(fraction >= 0.0 && fraction <= 1.0, "amortization fraction " << fraction << " out of range");
    Date lastAmortDate = Date::minDate();
    for (Size i = 0; i < schedule.size() - 1; i++) {
        if (i > 0 && schedule[i] > lastAmortDate + amortPeriod - 4 * Days && schedule[i] >= startDate &&
            schedule[i] < endDate) { // FIXME: tolerance
            nominals[i] = nominals[i - 1] * (1.0 - fraction);
            lastAmortDate = schedule[i];
        } else if (i > 0 && schedule[i] >= endDate)
            nominals[i] = nominals[i - 1];
    }
    LOG("Fixed amortization notional schedule done");
    return nominals;
}

vector<double> buildAmortizationScheduleFixedAnnuity(const vector<double>& notionals, const vector<double>& rates,
                                                     const Schedule& schedule, const AmortizationData& data,
                                                     const DayCounter& dc) {
    LOG("Build fixed annuity notional schedule");
    vector<double> nominals = normaliseToSchedule(notionals, schedule);
    Date startDate = parseDate(data.startDate());
    Date endDate = data.endDate() == "" ? Date::maxDate() : parseDate(data.endDate());
    bool underflow = data.underflow();
    double annuity = data.value();
    Real amort = 0.0;
    for (Size i = 0; i < schedule.size() - 1; i++) {
        if (i > 0 && schedule[i] >= startDate && schedule[i] < endDate)
            nominals[i] = nominals[i - 1] - amort;
        else if (i > 0 && schedule[i] >= endDate)
            nominals[i] = nominals[i - 1];
        Real dcf = dc.yearFraction(schedule[i], schedule[i + 1]);
        Real rate = i < rates.size() ? rates[i] : rates.back();
        amort = annuity - rate * nominals[i] * dcf;
        if (amort > nominals[i] && underflow == false) {
            amort = std::max(nominals[i], 0.0);
        }
    }
    LOG("Fixed Annuity notional schedule done");
    return nominals;
}

void applyAmortization(std::vector<Real>& notionals, const LegData& data, const Schedule& schedule,
                       const bool annuityAllowed, const std::vector<Real>& rates) {
    Date lastEndDate = Date::minDate();
    for (auto const& amort : data.amortizationData()) {
        if (!amort.initialized())
            continue;
        Date startDate = parseDate(amort.startDate());
        QL_REQUIRE(startDate >= lastEndDate, "Amortization start date ("
                                                 << startDate << ") is earlier than last end date (" << lastEndDate
                                                 << ")");
        lastEndDate = parseDate(amort.endDate());
        AmortizationType amortizationType = parseAmortizationType(amort.type());
        if (amortizationType == AmortizationType::FixedAmount)
            notionals = buildAmortizationScheduleFixedAmount(notionals, schedule, amort);
        else if (amortizationType == AmortizationType::RelativeToInitialNotional)
            notionals = buildAmortizationScheduleRelativeToInitialNotional(notionals, schedule, amort);
        else if (amortizationType == AmortizationType::RelativeToPreviousNotional)
            notionals = buildAmortizationScheduleRelativeToPreviousNotional(notionals, schedule, amort);
        else if (amortizationType == AmortizationType::Annuity) {
            QL_REQUIRE(annuityAllowed, "Amortization type Annuity not allowed for leg type " << data.legType());
            if (!rates.empty())
                notionals = buildAmortizationScheduleFixedAnnuity(notionals, rates, schedule, amort,
                                                                  parseDayCounter(data.dayCounter()));
        } else
            QL_FAIL("AmortizationType " << amort.type() << " not supported");
        // check that for a floating leg we only have one amortization block, if the type is annuity
        // we recognise a floating leg by an empty (fixed) rates vector
        if (rates.empty() && amortizationType == AmortizationType::Annuity) {
            QL_REQUIRE(data.amortizationData().size() == 1,
                       "Floating Leg supports only one amortisation block of type Annuity");
        }
    }
}

} // namespace data
} // namespace ore


#include <pinocchio/algorithm/aba.hpp>
#include <pinocchio/algorithm/crba.hpp>
#include <pinocchio/algorithm/joint-configuration.hpp>
#include <pinocchio/algorithm/rnea.hpp>

#include "pinocchio/parsers/urdf.hpp"
#include "pinocchio/parsers/urdf/model.hxx"
#include "pinocchio/multibody/joint/fwd.hpp"

#include "pinocchio/parsers/utils.hpp"
#include "pinocchio/parsers/urdf/geometry.hxx"

#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include <kdl/treefksolverpos_recursive.hpp>
#include <kdl/treeiksolvervel_wdls.hpp>
#include <kdl/treeiksolverpos_nr_jl.hpp>

#include <cmath>
#include <cstdlib>
#include <algorithm>

#include "articulated_model.h"
#include "urdf_utils.h"

#define DEFINE_TEMPLATE_AM(DATATYPE) template class ArticulatedModelTpl<DATATYPE>;

DEFINE_TEMPLATE_AM(float)

DEFINE_TEMPLATE_AM(double)

template<typename DATATYPE>
ArticulatedModelTpl<DATATYPE>::
ArticulatedModelTpl(std::string const &urdf_filename, std::string const &srdf_filename, Vector3 const &gravity,
                    std::vector<std::string> const &joint_names, std::vector<std::string> const &link_names,
                    bool const &verbose, bool const& convex) :verbose(verbose),
                                          pinocchio_model(urdf_filename, gravity, verbose),
                                          fcl_model(urdf_filename, verbose, convex) {
    //std::cout << "Verbose" << verbose << std::endl;
    user_link_names = link_names;
    user_joint_names = joint_names;
    pinocchio_model.setLinkOrder(link_names);
    pinocchio_model.setJointOrder(joint_names);
    fcl_model.setLinkOrder(link_names);
    fcl_model.removeCollisionPairsFromSrdf(srdf_filename);
    current_qpos = VectorX::Constant(pinocchio_model.getModel().nv, 0);
    setMoveGroup(user_link_names);
}

template<typename DATATYPE>
void ArticulatedModelTpl<DATATYPE>::setMoveGroup(std::string const &end_effector) {
    std::vector<std::string> end_effectors = {end_effector};
    setMoveGroup(end_effectors);
}

template<typename DATATYPE>
void ArticulatedModelTpl<DATATYPE>::setMoveGroup(std::vector<std::string> const &end_effectors) {
    move_group_end_effectors = end_effectors;
    move_group_user_joints = {};
    for (auto end_effector: end_effectors) {
        auto joint_i = pinocchio_model.getChainJointIndex(end_effector);
        move_group_user_joints.insert(move_group_user_joints.begin(), joint_i.begin(), joint_i.end());
    }
    std::sort(move_group_user_joints.begin(), move_group_user_joints.end());
    auto end_unique = std::unique(move_group_user_joints.begin(), move_group_user_joints.end());
    move_group_user_joints.erase(end_unique, move_group_user_joints.end());
    qpos_dim = 0;
    for (auto i: move_group_user_joints)
        qpos_dim += pinocchio_model.getJointDim(i);
}

template<typename DATATYPE>
std::vector<std::string> ArticulatedModelTpl<DATATYPE>::getMoveGroupJointName(void) {
    std::vector<std::string> ret;
    for (auto i: move_group_user_joints)
        ret.push_back(user_joint_names[i]);
    return ret;
}

template<typename DATATYPE>
void ArticulatedModelTpl<DATATYPE>::setQpos(VectorX const &qpos, bool const& full) {
    if (full)
        current_qpos = qpos;
    else {
        ASSERT(qpos.size() == qpos_dim,
               "Length is not correct, Dim of Q: " + std::to_string(qpos_dim) + " ,Len of qpos: " +
               std::to_string(qpos.size()));
        size_t len = 0;
        for (auto i: move_group_user_joints) {
            auto start_idx = pinocchio_model.getJointId(i), dim_i = pinocchio_model.getJointDim(i);
            for (size_t j = 0; j < dim_i; j++)
                current_qpos[start_idx + j] = qpos[len++];
        }
    }
    pinocchio_model.computeForwardKinematics(current_qpos);
    //std::cout << "current_qpos " << current_qpos << std::endl;
    std::vector<Transform3> link_pose;
    for (size_t i = 0; i < user_link_names.size(); i++) {
        Vector7 pose_i = pinocchio_model.getLinkPose(i);
        Transform3 tmp_i;
        tmp_i.linear() = Quaternion(pose_i[3], pose_i[4], pose_i[5], pose_i[6]).matrix();
        tmp_i.translation() = pose_i.head(3);
        //std::cout << pose_i << std::endl;
        link_pose.push_back(tmp_i);
    }
    fcl_model.updateCollisionObjects(link_pose);
}


#pragma once

/** @file MinimizeScalingFactors.hpp
 * @brief
 * @author C.D. Clark III
 * @date 10/13/20
 */

#include <utility>

#include <boost/math/tools/minima.hpp>
using boost::math::tools::brent_find_minima;

#include <boost/math/tools/roots.hpp>
using boost::math::tools::bracket_and_solve_root;
using boost::math::tools::eps_tolerance;

#include "../../ThresholdCalculator.hpp"

namespace libArrhenius {

  /** @class MinimizeScalingFactors
   * @brief
   * @author C.D. Clark III
   */
  template<typename Real>
    class ArrheniusFit<Real,MinimizeScalingFactors> : public ArrheniusFitBase<Real>
    {
      protected:

      public:
        ArrheniusFit() {};
        virtual ~ArrheniusFit() {};

        typedef typename ArrheniusFitBase<Real>::Return Return;

        Return
          exec() const
          {
            BOOST_LOG_TRIVIAL(trace) << "MinimizeScalignFactors: Executing Fit";
            // find Ea and A that minimizes scaling factor error

            Return ret;
            ArrheniusIntegral<Real> integrator;
            ThresholdCalculator<decltype(integrator)> calc;
            std::vector<Real*> const &t = this->t;
            std::vector<Real*> const &T = this->T;
            std::vector<size_t> const &N = this->N;

            // We need to get a range for Ea and A before we can run the minimization
            // function. So, we'll do a quick scan for the minimum.
            // If the caller has specified a bound use it.
            // otherwise, calculate one
            Real Ea_lb = 5e5;
            Real Ea_ub = 9e5;
            Real A_lb = 1e99;
            Real A_ub = 5e99;

            /* int prec; */
            /* boost::uintmax_t maxit; */

            /* prec = std::numeric_limits<Real>::digits - 3; */
            /* maxit = 1000; */

            // the cost function calculates and returns the sum of squared deviations of the scaling factors from 1
            auto cost = [&](Real Ea, Real A){
              // cost is equal to the sum of squared deviations (i.e. proportional to variance)
              calc.setEa( Ea );
              calc.setA( A );

              std::vector<Real> thresholds(N.size());
              for(size_t i = 0; i < N.size(); ++i)
                thresholds[i] = calc(N[i], t[i], T[i]);

              // calculate the sum of squared deviations
              Real devs = 0;
              for(size_t i = 0; i < N.size(); ++i)
                devs += (thresholds[i] - 1)*(thresholds[i] - 1);

              return devs;
            };

            /* maxit = 1000; */
            BOOST_LOG_TRIVIAL(trace) << "Searching for Ea and A with Cost minimization in region Ea = [" << Ea_lb << "," << Ea_ub <<"], A = [" << A_lb << "," << A_ub << "]";
            auto dEa = (Ea_ub - Ea_lb)/100;
            auto dA = (A_ub - A_lb)/10;
            for(int i = 0; i < 10; ++i)
            {
              auto Ea = Ea_lb + i*dEa;
              for(int j = 0; j < 100; ++j)
              {
                auto A = A_lb + j*dA;
                try{
                  auto c = cost(Ea,A);
                std::cout << Ea << " " << A << " " << c << "\n";
                }catch(...){}

              }
              std::cout << "\n";
            }
            /* auto A_min = brent_find_minima( cost, A_lb, A_ub, prec ); */
            /* ret.A = A_min.first; */

            return ret;

          }

      protected:
    };

}


#pragma once

#include <fc/container/flat_fwd.hpp>
#include <fc/io/varint.hpp>
#include <fc/io/enum_type.hpp>
#include <fc/crypto/sha224.hpp>
#include <fc/crypto/ripemd160.hpp>
#include <fc/crypto/elliptic.hpp>
#include <fc/reflect/reflect.hpp>
#include <fc/reflect/variant.hpp>
#include <fc/optional.hpp>
#include <fc/safe.hpp>
#include <fc/container/flat.hpp>
#include <fc/string.hpp>
#include <fc/fixed_string.hpp>
#include <fc/io/raw.hpp>
#include <fc/uint128.hpp>
#include <fc/static_variant.hpp>
#include <fc/smart_ref_fwd.hpp>
#include <fc/time.hpp>
#include <fc/safe.hpp>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/uuid/uuid.hpp>

#include <memory>
#include <vector>
#include <deque>
#include <cstdint>

namespace scorum {

using fc::uint128_t;
typedef boost::multiprecision::uint256_t u256;
typedef boost::multiprecision::uint512_t u512;

using fc::enum_type;
using fc::flat_map;
using fc::flat_set;
using fc::optional;
using fc::safe;
using fc::signed_int;
using fc::smart_ref;
using fc::static_variant;
using fc::time_point;
using fc::time_point_sec;
using fc::unsigned_int;
using fc::variant;
using fc::variant_object;
using fc::ecc::commitment_type;
using fc::ecc::range_proof_info;
using fc::ecc::range_proof_type;
using uuid_type = boost::uuids::uuid;
struct void_t
{
};

namespace protocol {

typedef fc::ecc::private_key private_key_type;
typedef fc::sha256 chain_id_type;
typedef fc::fixed_string_16 account_name_type;
typedef fc::ripemd160 block_id_type;
typedef fc::ripemd160 checksum_type;
typedef fc::ripemd160 transaction_id_type;
typedef fc::sha256 digest_type;
typedef fc::ecc::compact_signature signature_type;
typedef uint16_t authority_weight_type;
typedef uint16_t percent_type;
typedef int16_t vote_weight_type;

using share_value_type = int64_t;
using share_type = fc::safe<share_value_type>;

struct public_key_type
{
    struct binary_key
    {
        binary_key()
        {
        }
        uint32_t check = 0;
        fc::ecc::public_key_data data;
    };
    fc::ecc::public_key_data key_data;
    public_key_type();
    public_key_type(const fc::ecc::public_key_data& data);
    public_key_type(const fc::ecc::public_key& pubkey);
    explicit public_key_type(const std::string& base58str);
    operator fc::ecc::public_key_data() const;
    operator fc::ecc::public_key() const;
    explicit operator std::string() const;
    friend bool operator==(const public_key_type& p1, const fc::ecc::public_key& p2);
    friend bool operator==(const public_key_type& p1, const public_key_type& p2);
    friend bool operator<(const public_key_type& p1, const public_key_type& p2)
    {
        return p1.key_data < p2.key_data;
    }
    friend bool operator!=(const public_key_type& p1, const public_key_type& p2);
};

struct extended_public_key_type
{
    struct binary_key
    {
        binary_key()
        {
        }
        uint32_t check = 0;
        fc::ecc::extended_key_data data;
    };

    fc::ecc::extended_key_data key_data;

    extended_public_key_type();
    extended_public_key_type(const fc::ecc::extended_key_data& data);
    extended_public_key_type(const fc::ecc::extended_public_key& extpubkey);
    explicit extended_public_key_type(const std::string& base58str);
    operator fc::ecc::extended_public_key() const;
    explicit operator std::string() const;
    friend bool operator==(const extended_public_key_type& p1, const fc::ecc::extended_public_key& p2);
    friend bool operator==(const extended_public_key_type& p1, const extended_public_key_type& p2);
    friend bool operator!=(const extended_public_key_type& p1, const extended_public_key_type& p2);
};

struct extended_private_key_type
{
    struct binary_key
    {
        binary_key()
        {
        }
        uint32_t check = 0;
        fc::ecc::extended_key_data data;
    };

    fc::ecc::extended_key_data key_data;

    extended_private_key_type();
    extended_private_key_type(const fc::ecc::extended_key_data& data);
    extended_private_key_type(const fc::ecc::extended_private_key& extprivkey);
    explicit extended_private_key_type(const std::string& base58str);
    operator fc::ecc::extended_private_key() const;
    explicit operator std::string() const;
    friend bool operator==(const extended_private_key_type& p1, const fc::ecc::extended_private_key& p2);
    friend bool operator==(const extended_private_key_type& p1, const extended_private_key_type& p2);
    friend bool operator!=(const extended_private_key_type& p1, const extended_private_key_type& p2);
};

enum class curve_id
{
    quadratic,
    linear,
    square_root,
    power1dot5
};

enum class budget_type
{
    post,
    banner
};
} // namespace protocol
} // namespace scorum

namespace fc {
void to_variant(const scorum::protocol::public_key_type& var, fc::variant& vo);
void from_variant(const fc::variant& var, scorum::protocol::public_key_type& vo);
void to_variant(const scorum::protocol::extended_public_key_type& var, fc::variant& vo);
void from_variant(const fc::variant& var, scorum::protocol::extended_public_key_type& vo);
void to_variant(const scorum::protocol::extended_private_key_type& var, fc::variant& vo);
void from_variant(const fc::variant& var, scorum::protocol::extended_private_key_type& vo);
void to_variant(const boost::uuids::uuid&, fc::variant&);
void from_variant(const fc::variant&, boost::uuids::uuid&);
} // namespace fc

FC_REFLECT(scorum::protocol::public_key_type, (key_data))
FC_REFLECT(scorum::protocol::public_key_type::binary_key, (data)(check))
FC_REFLECT(scorum::protocol::extended_public_key_type, (key_data))
FC_REFLECT(scorum::protocol::extended_public_key_type::binary_key, (check)(data))
FC_REFLECT(scorum::protocol::extended_private_key_type, (key_data))
FC_REFLECT(scorum::protocol::extended_private_key_type::binary_key, (check)(data))
FC_REFLECT(boost::uuids::uuid, (data))

FC_REFLECT_ENUM(scorum::protocol::curve_id, (quadratic)(linear)(square_root)(power1dot5))
FC_REFLECT_ENUM(scorum::protocol::budget_type, (post)(banner))

FC_REFLECT(scorum::void_t, )


/**********************************************************************
*  Copyright (c) 2008-2014, Alliance for Sustainable Energy.
*  All rights reserved.
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**********************************************************************/

#include <gtest/gtest.h>
#include <utilities/units/test/UnitsFixture.hpp>

#include <utilities/units/OSQuantityVector.hpp>
#include <utilities/units/Quantity.hpp>
#include <utilities/units/SIUnit.hpp>
#include <utilities/units/WhUnit.hpp>
#include <utilities/units/CelsiusUnit.hpp>

#include <utilities/data/Vector.hpp>

#include <utilities/core/Containers.hpp>

#include <boost/foreach.hpp>

using namespace openstudio;

TEST_F(UnitsFixture,OSQuantityVector_Constructors) {
  SIUnit u = createSILength();
  OSQuantityVector qvec(u);
  EXPECT_TRUE(qvec.empty());
  qvec.push_back(Quantity(1.0,u));
  qvec.push_back(Quantity(5.5,u));
  EXPECT_ANY_THROW(qvec.push_back(Quantity(1.0,createSIPower())));
  EXPECT_EQ(2u,qvec.size());
  EXPECT_FALSE(qvec.empty());
  qvec.resize(5u,3.0);
  EXPECT_EQ(5u,qvec.size());
  QuantityVector qs = qvec.quantities();
  ASSERT_EQ(5u,qs.size());
  EXPECT_EQ(3.0,qs[3].value());
  EXPECT_EQ("m",qs[3].standardUnitsString());

  qvec = OSQuantityVector(createSIPower(),50u,100.0);
  EXPECT_EQ("W",qvec.units().prettyString());
  qs = qvec.quantities();
  ASSERT_EQ(50u,qs.size());
  EXPECT_DOUBLE_EQ(100.0,qs[31].value());
  EXPECT_EQ("W",qs[25].prettyUnitsString());
  EXPECT_EQ("kg*m^2/s^3",qs[2].standardUnitsString());

  OSQuantityVector qvec2(createSIPower(),DoubleVector(50u,100.0));
  EXPECT_TRUE(qvec2 == qvec);
}

TEST_F(UnitsFixture,OSQuantityVector_MathematicalOperators) {
  // basic addition
  OSQuantityVector testVec1(createSIEnergyUseIntensity(),2u,3.5);
  OSQuantityVector testVec2(createSIEnergyUseIntensity(),2u,0.25);
  Quantity testQ1(3.5,createSIEnergyUseIntensity());
  Quantity testQ2(0.25,createSIEnergyUseIntensity());

  OSQuantityVector resultVec(testVec1);
  resultVec += resultVec;
  Quantity resultQ(testQ1);
  resultQ += resultQ;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultVec = testVec1 + testVec2;
  resultQ = testQ1 + testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultVec = testVec1 + testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  // basic subtraction
  resultVec = testVec1;
  resultVec -= resultVec;
  resultQ = testQ1;
  resultQ -= resultQ;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_EQ(0.0,resultQ.value());

  resultVec = testVec1 - testVec2;
  resultQ = testQ1 - testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultVec = testVec1 - testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  // add temperatures
  testVec1 = OSQuantityVector(createCelsiusTemperature(),2u,20.0);
  testVec2 = OSQuantityVector(createCelsiusTemperature(),2u,5.0);
  EXPECT_TRUE(testVec1.isAbsolute()); EXPECT_FALSE(testVec1.isRelative());
  testVec2.setAsRelative();
  EXPECT_FALSE(testVec2.isAbsolute()); EXPECT_TRUE(testVec2.isRelative());
  testQ1 = Quantity(20.0,createCelsiusTemperature());
  testQ2 = Quantity(5.0,createCelsiusTemperature());
  testQ2.setAsRelative();

  resultVec = testVec1 + testVec2;
  resultQ = testQ1 + testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isAbsolute());
  EXPECT_TRUE(resultQ.isAbsolute());

  resultVec = testVec2 + testVec1;
  resultQ = testQ2 + testQ1;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isAbsolute());
  EXPECT_TRUE(resultQ.isAbsolute());

  resultVec = testVec2 + testVec2;
  resultQ = testQ2 + testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isRelative());
  EXPECT_TRUE(resultQ.isRelative());

  // subtract temperatures
  resultVec = testVec1 - testVec2;
  resultQ = testQ1 - testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isAbsolute());
  EXPECT_TRUE(resultQ.isAbsolute());

  resultVec = testVec2 - testVec1;
  resultQ = testQ2 - testQ1;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isAbsolute());
  EXPECT_TRUE(resultQ.isAbsolute());

  resultVec = testVec1 - testVec1;
  resultQ = testQ1 - testQ1;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
  EXPECT_TRUE(resultVec.isRelative());
  EXPECT_TRUE(resultQ.isRelative());

  // multiply
  testVec1 = OSQuantityVector(createSILength(),2u,7.0);
  testQ1 = Quantity(7.0,createSILength());
  testQ2 = Quantity(2.0,createSIForce());

  resultVec = testVec1 * testQ2;
  resultQ = testQ1 * testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultVec = testQ2.value() * testVec1;
  resultQ = testQ2.value() * testQ1;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultQ = dot(testVec1,testVec1);
  EXPECT_DOUBLE_EQ(98.0,resultQ.value());
  EXPECT_EQ("m^2",resultQ.standardUnitsString());

  // divide
  testQ2 = Quantity(2.0,createSITime());

  resultVec = testVec1 / testQ2;
  resultQ = testQ1 / testQ2;
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));

  resultVec = testVec1 / testQ2.value();
  resultQ = testQ1 / testQ2.value();
  ASSERT_EQ(2u,resultVec.size());
  EXPECT_EQ(resultQ,resultVec.getQuantity(0));
  EXPECT_EQ(resultQ,resultVec.getQuantity(1));
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Construction_QuantityVectorBaseCase) {
  for (unsigned i = 0, n = 100; i < n; ++i) {
    DoubleVector vals = toStandardVector(randVector(0.0,1.0,8760u));
    Unit u = createSIForce();
    QuantityVector result;
    BOOST_FOREACH(double val,vals) {
      result.push_back(Quantity(val,u));
    }
  }
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Construction_OSQuantityVector) {
  for (unsigned i = 0, n = 100; i < n; ++i) {
    DoubleVector vals = toStandardVector(randVector(0.0,1.0,8760u));
    Unit u = createSIForce();
    OSQuantityVector result(u,vals);
  }
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Addition_QuantityVectorBaseCase) {
  for (unsigned i = 0, n = 10; i < n; ++i) {
    QuantityVector result(testQuantityVector);
    Quantity other(10.0,createSIPower());
    for (unsigned j = 0, n = result.size(); j < n; ++j) {
      result[j] += other;
    }
  }
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Addition_OSQuantityVector) {
  for (unsigned i = 0, n = 10; i < n; ++i) {
    Quantity other(10.0,createSIPower());
    OSQuantityVector result = testOSQuantityVector + other;
  }
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Multiplication_QuantityVectorBaseCase) {
  for (unsigned i = 0, n = 10; i < n; ++i) {
    QuantityVector result(testQuantityVector);
    Quantity other(8.0,createWhTime());
    for (unsigned j = 0, n = result.size(); j < n; ++j) {
      result[j] *= other;
    }
  }
}

TEST_F(UnitsFixture,OSQuantityVector_Profiling_Multiplication_OSQuantityVector) {
  for (unsigned i = 0, n = 10; i < n; ++i) {
    Quantity other(8.0,createWhTime());
    OSQuantityVector result = testOSQuantityVector * other;
  }
}