//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Job/MinimizerItem.cpp
//! @brief     Implements MinimizerItem class
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Model/Job/MinimizerItem.h"
#include "Base/Util/Assert.h"
#include "Fit/Suite/GSLLevenbergMarquardtMinimizer.h"
#include "Fit/Suite/GSLMultiMinimizer.h"
#include "Fit/Suite/GeneticMinimizer.h"
#include "Fit/Suite/Minuit2Minimizer.h"
#include "Fit/Suite/SimAnMinimizer.h"
#include "GUI/Model/Job/MinimizerItemCatalog.h"
#include "GUI/Support/XML/UtilXML.h"
#include "Sim/Fitting/ObjectiveMetric.h"
#include "Sim/Fitting/ObjectiveMetricUtil.h"

namespace {

namespace Tag {

const QString Tolerance("Tolerance");
const QString MaxIterations("MaxIterations");
const QString IterationsAtTemp("IterationsAtTemp");
const QString StepSize("StepSize");
const QString BoltzmannK("BoltzmannK");
const QString Tinit("Tinit");
const QString BoltzmannMu("BoltzmannMu");
const QString Tmin("Tmin");
const QString PopulationSize("PopulationSize");
const QString RandomSeed("RandomSeed");
const QString Algorithm("Algorithm");
const QString Strategy("Strategy");
const QString ErrorDef("ErrorDef");
const QString Precision("Precision");
const QString MaxFuncCalls("MaxFuncCalls");
const QString Minimizer("Minimizer");
const QString Metric("Metric");
const QString Norm("Norm");
const QString MinuitMinimizer("MinuitMinimizer");
const QString GSLMultiMinimizer("GSLMultiMinimizer");
const QString GeneticMinimizer("GeneticMinimizer");
const QString SimAnMinimizer("SimAnMinimizer");
const QString GSLLMAMinimizer("GSLLMAMinimizer");

} // namespace Tag

const QMap<QString, MinimizerType> minimizer_names_map = {
    {QString::fromStdString(MinimizerInfo::buildMinuit2Info().name()), MinimizerType::Minuit2},
    {QString::fromStdString(MinimizerInfo::buildGSLMultiMinInfo().name()),
     MinimizerType::GSLMultiMin},
    {QString::fromStdString(MinimizerInfo::buildGSLLMAInfo().name()), MinimizerType::GSLLMA},
    {QString::fromStdString(MinimizerInfo::buildGSLSimAnInfo().name()), MinimizerType::GSLSimAn},
    {QString::fromStdString(MinimizerInfo::buildGeneticInfo().name()), MinimizerType::Genetic}};

} // namespace

QString minimizerTypeToName(MinimizerType type)
{
    ASSERT(minimizer_names_map.values().contains(type));
    return minimizer_names_map.key(type);
}

namespace {

QMap<QString /*algorithm*/, QString /*minimizer*/> algorithm_minimizer_map;
QString add_algorithm_from_minimizer_to_list_and_map(MinimizerType minimizer_type,
                                                     QStringList& common_algorithms_list,
                                                     QStringList& common_algorithms_descriptions)
{
    QString minimizer_name = minimizerTypeToName(minimizer_type);
    QStringList algorithms = MinimizerItemCatalog::algorithmNames(minimizer_name);
    QStringList descriptions = MinimizerItemCatalog::algorithmDescriptions(minimizer_name);
    ASSERT(algorithms.size() == descriptions.size());

    for (int i = 0; i < algorithms.size(); i++) {
        common_algorithms_list.append(algorithms[i]);
        common_algorithms_descriptions.append(descriptions[i]);
        algorithm_minimizer_map.insert(algorithms[i], minimizer_name);
    }
    ASSERT(algorithms.size() > 0);
    return algorithms.first();
}
void create_algorithm_list_and_map(QString& default_common_algorithm, QStringList& algorithms_list,
                                   QStringList& algorithms_descriptions)
{
    // Group headers may not directly correspond to minimizers, so for them we don't use
    // descriptions from MinimizerInfo

    algorithms_list.clear();
    algorithms_descriptions.clear();

    // group 1
    // group header. Cannot be selected and have no mapped minimizer
    algorithms_list.append("Local optimization");
    algorithms_descriptions.append("");

    add_algorithm_from_minimizer_to_list_and_map(MinimizerType::Minuit2, algorithms_list,
                                                 algorithms_descriptions);

    add_algorithm_from_minimizer_to_list_and_map(MinimizerType::GSLMultiMin, algorithms_list,
                                                 algorithms_descriptions);

    add_algorithm_from_minimizer_to_list_and_map(MinimizerType::GSLLMA, algorithms_list,
                                                 algorithms_descriptions);

    // group 2
    algorithms_list.append("Global optimization");
    algorithms_descriptions.append("");

    // move "Scan" algorithm from the local group to the global
    qsizetype scan_index = algorithms_list.indexOf("Scan");
    ASSERT(scan_index >= 0);
    QString name = algorithms_list.takeAt(scan_index);
    QString descr = algorithms_descriptions.takeAt(scan_index);
    algorithms_list.append(name);
    algorithms_descriptions.append(descr);

    // other algorithms
    // choose genetic algorithm as defaut
    default_common_algorithm = add_algorithm_from_minimizer_to_list_and_map(
        MinimizerType::Genetic, algorithms_list, algorithms_descriptions);

    add_algorithm_from_minimizer_to_list_and_map(MinimizerType::GSLSimAn, algorithms_list,
                                                 algorithms_descriptions);
}

} // namespace

MinimizerContainerItem::MinimizerContainerItem()
    : m_MinuitMinimizer(std::make_unique<MinuitMinimizerItem>())
    , m_GSLMultiMinimizer(std::make_unique<GSLMultiMinimizerItem>())
    , m_GeneticMinimizer(std::make_unique<GeneticMinimizerItem>())
    , m_SimAnMinimizer(std::make_unique<SimAnMinimizerItem>())
    , m_GSLLMAMinimizer(std::make_unique<GSLLMAMinimizerItem>())
{
    QString default_common_algorithm;
    QStringList common_algorithms_list;
    QStringList common_algorithms_descriptions;
    QString default_minimizer = minimizerTypeToName(MinimizerType::Genetic);
    create_algorithm_list_and_map(default_common_algorithm, common_algorithms_list,
                                  common_algorithms_descriptions);
    m_algorithm = ComboProperty::fromList(common_algorithms_list, default_common_algorithm);
    m_algorithm.setToolTips(common_algorithms_descriptions);

    m_minimizer = ComboProperty::fromList(minimizer_names_map.keys(), default_minimizer);
    m_metric = ComboProperty::fromStdVec(ObjectiveMetricUtil::metricNames(),
                                         ObjectiveMetricUtil::defaultMetricName());
    m_norm = ComboProperty::fromStdVec(ObjectiveMetricUtil::normNames(),
                                       ObjectiveMetricUtil::defaultNormName());
}

MinimizerContainerItem::~MinimizerContainerItem() = default;

MinuitMinimizerItem* MinimizerContainerItem::minimizerItemMinuit() const
{
    return m_MinuitMinimizer.get();
}

GSLMultiMinimizerItem* MinimizerContainerItem::minimizerItemGSLMulti() const
{
    return m_GSLMultiMinimizer.get();
}

GeneticMinimizerItem* MinimizerContainerItem::minimizerItemGenetic() const
{
    return m_GeneticMinimizer.get();
}

SimAnMinimizerItem* MinimizerContainerItem::minimizerItemSimAn() const
{
    return m_SimAnMinimizer.get();
}

GSLLMAMinimizerItem* MinimizerContainerItem::minimizerItemGSLLMA() const
{
    return m_GSLLMAMinimizer.get();
}

MinimizerItem* MinimizerContainerItem::currentMinimizerItem() const
{
    if (currentMinimizer() == minimizerTypeToName(MinimizerType::Minuit2))
        return minimizerItemMinuit();

    else if (currentMinimizer() == minimizerTypeToName(MinimizerType::GSLMultiMin))
        return minimizerItemGSLMulti();

    else if (currentMinimizer() == minimizerTypeToName(MinimizerType::Genetic))
        return minimizerItemGenetic();

    else if (currentMinimizer() == minimizerTypeToName(MinimizerType::GSLSimAn))
        return minimizerItemSimAn();

    else if (currentMinimizer() == minimizerTypeToName(MinimizerType::GSLLMA))
        return minimizerItemGSLLMA();

    else
        ASSERT(false);

    return nullptr;
}

QString MinimizerContainerItem::currentMinimizer() const
{
    return m_minimizer.currentValue();
}

void MinimizerContainerItem::setCurrentMinimizer(const QString& name)
{
    m_minimizer.setCurrentValue(name);
}

bool MinimizerContainerItem::algorithmHasMinimizer(const QString& name)
{
    return algorithm_minimizer_map.contains(name);
}

void MinimizerContainerItem::setCurrentCommonAlgorithm(const QString& name)
{
    m_algorithm.setCurrentValue(name);

    ASSERT(algorithmHasMinimizer(name));
    setCurrentMinimizer(algorithm_minimizer_map.value(name));
    applyAlgorithmToMinimizer(name);
}

ComboProperty MinimizerContainerItem::commonAlgorithmCombo() const
{
    return m_algorithm;
}

void MinimizerContainerItem::applyAlgorithmToMinimizer(const QString& name)
{
    // Minuit2
    if (currentMinimizer() == minimizerTypeToName(MinimizerType::Minuit2))
        minimizerItemMinuit()->setCurrentAlgorithm(name);

    // GSL MultiMin
    if (currentMinimizer() == minimizerTypeToName(MinimizerType::GSLMultiMin))
        minimizerItemGSLMulti()->setCurrentAlgorithm(name);

    // TMVA Genetic
    // do nothing

    // GSL Simulated Annealing
    // do nothing

    // GSL Levenberg-Marquardt
    // do nothing
}

QString MinimizerContainerItem::currentObjectiveMetric() const
{
    return m_metric.currentValue();
}

void MinimizerContainerItem::setCurrentObjectiveMetric(const QString& name)
{
    m_metric.setCurrentValue(name);
}

ComboProperty MinimizerContainerItem::objectiveMetricCombo() const
{
    return m_metric;
}

QString MinimizerContainerItem::currentNormFunction() const
{
    return m_norm.currentValue();
}

void MinimizerContainerItem::setCurrentNormFunction(const QString& name)
{
    m_norm.setCurrentValue(name);
}

ComboProperty MinimizerContainerItem::normFunctionCombo() const
{
    return m_norm;
}

std::unique_ptr<IMinimizer> MinimizerContainerItem::createMinimizer() const
{
    return currentMinimizerItem()->createMinimizer();
}

std::unique_ptr<ObjectiveMetric> MinimizerContainerItem::createMetric() const
{
    return ObjectiveMetricUtil::createMetric(currentObjectiveMetric().toStdString(),
                                             currentNormFunction().toStdString());
}

void MinimizerContainerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // algorithm
    w->writeStartElement(Tag::Algorithm);
    m_algorithm.writeTo(w);
    w->writeEndElement();

    // minimizer
    w->writeStartElement(Tag::Minimizer);
    m_minimizer.writeTo(w);
    w->writeEndElement();

    // metric
    w->writeStartElement(Tag::Metric);
    m_metric.writeTo(w);
    w->writeEndElement();

    // norm
    w->writeStartElement(Tag::Norm);
    m_norm.writeTo(w);
    w->writeEndElement();

    // Minuit
    w->writeStartElement(Tag::MinuitMinimizer);
    m_MinuitMinimizer->writeTo(w);
    w->writeEndElement();

    // GSLMulti
    w->writeStartElement(Tag::GSLMultiMinimizer);
    m_GSLMultiMinimizer->writeTo(w);
    w->writeEndElement();

    // Genetic
    w->writeStartElement(Tag::GeneticMinimizer);
    m_GeneticMinimizer->writeTo(w);
    w->writeEndElement();

    // SimAn
    w->writeStartElement(Tag::SimAnMinimizer);
    m_SimAnMinimizer->writeTo(w);
    w->writeEndElement();

    // GSLLMA
    w->writeStartElement(Tag::GSLLMAMinimizer);
    m_GSLLMAMinimizer->writeTo(w);
    w->writeEndElement();
}

void MinimizerContainerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // algorithm
        if (tag == Tag::Algorithm) {
            m_algorithm.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // minimizer
        } else if (tag == Tag::Minimizer) {
            m_minimizer.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // metric
        } else if (tag == Tag::Metric) {
            m_metric.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // norm
        } else if (tag == Tag::Norm) {
            m_norm.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // Minuit
        } else if (tag == Tag::MinuitMinimizer) {
            m_MinuitMinimizer->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // GSLMulti
        } else if (tag == Tag::GSLMultiMinimizer) {
            m_GSLMultiMinimizer->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // Genetic
        } else if (tag == Tag::GeneticMinimizer) {
            m_GeneticMinimizer->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // SimAn
        } else if (tag == Tag::SimAnMinimizer) {
            m_SimAnMinimizer->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // GSLLMA
        } else if (tag == Tag::GSLLMAMinimizer) {
            m_GSLLMAMinimizer->readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

// ----------------------------------------------------------------------------

MinuitMinimizerItem::MinuitMinimizerItem()
    : MinimizerItem()
    , m_strategy(1)
    , m_errorDef(1.0)
    , m_tolerance(0.01)
    , m_precision(-1.0)
    , m_maxFuncCalls(0)
{
    QString minimizer_name = minimizerTypeToName(MinimizerType::Minuit2);
    m_algorithm = MinimizerItemCatalog::algorithmCombo(minimizer_name);
}

MinuitMinimizerItem::~MinuitMinimizerItem() = default;

QString MinuitMinimizerItem::currentAlgorithm() const
{
    return m_algorithm.currentValue();
}

void MinuitMinimizerItem::setCurrentAlgorithm(const QString& name)
{
    m_algorithm.setCurrentValue(name);
}

int MinuitMinimizerItem::strategy() const
{
    return m_strategy;
}

void MinuitMinimizerItem::setStrategy(int value)
{
    m_strategy = value;
}

double MinuitMinimizerItem::errorDefinition() const
{
    return m_errorDef;
}

void MinuitMinimizerItem::setErrorDefinition(double value)
{
    m_errorDef = value;
}

double MinuitMinimizerItem::tolerance() const
{
    return m_tolerance;
}

void MinuitMinimizerItem::setTolerance(double value)
{
    m_tolerance = value;
}

double MinuitMinimizerItem::precision() const
{
    return m_precision;
}

void MinuitMinimizerItem::setPrecision(double value)
{
    m_precision = value;
}

int MinuitMinimizerItem::maxFuncCalls() const
{
    return m_maxFuncCalls;
}

void MinuitMinimizerItem::setMaxFuncCalls(int value)
{
    m_maxFuncCalls = value;
}

std::unique_ptr<IMinimizer> MinuitMinimizerItem::createMinimizer() const
{
    auto* domainMinimizer = new Minuit2Minimizer(currentAlgorithm().toStdString());
    domainMinimizer->setStrategy(strategy());
    domainMinimizer->setErrorDefinition(errorDefinition());
    domainMinimizer->setTolerance(tolerance());
    domainMinimizer->setPrecision(precision());
    domainMinimizer->setMaxFunctionCalls(maxFuncCalls());

    return std::unique_ptr<IMinimizer>(domainMinimizer);
}

void MinuitMinimizerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // algorithm
    w->writeStartElement(Tag::Algorithm);
    m_algorithm.writeTo(w);
    w->writeEndElement();

    // strategy
    w->writeStartElement(Tag::Strategy);
    XML::writeAttribute(w, XML::Attrib::value, m_strategy);
    w->writeEndElement();

    // error definition
    w->writeStartElement(Tag::ErrorDef);
    XML::writeAttribute(w, XML::Attrib::value, m_errorDef);
    w->writeEndElement();

    // tolerance
    w->writeStartElement(Tag::Tolerance);
    XML::writeAttribute(w, XML::Attrib::value, m_tolerance);
    w->writeEndElement();

    // precision
    w->writeStartElement(Tag::Precision);
    XML::writeAttribute(w, XML::Attrib::value, m_precision);
    w->writeEndElement();

    // max function calls
    w->writeStartElement(Tag::MaxFuncCalls);
    XML::writeAttribute(w, XML::Attrib::value, m_maxFuncCalls);
    w->writeEndElement();
}

void MinuitMinimizerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // algorithm
        if (tag == Tag::Algorithm) {
            m_algorithm.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // strategy
        } else if (tag == Tag::Strategy) {
            XML::readAttribute(r, XML::Attrib::value, &m_strategy);
            XML::gotoEndElementOfTag(r, tag);

            // error definition
        } else if (tag == Tag::ErrorDef) {
            XML::readAttribute(r, XML::Attrib::value, &m_errorDef);
            XML::gotoEndElementOfTag(r, tag);

            // tolerance
        } else if (tag == Tag::Tolerance) {
            XML::readAttribute(r, XML::Attrib::value, &m_tolerance);
            XML::gotoEndElementOfTag(r, tag);

            // precision
        } else if (tag == Tag::Precision) {
            XML::readAttribute(r, XML::Attrib::value, &m_precision);
            XML::gotoEndElementOfTag(r, tag);

            // max function calls
        } else if (tag == Tag::MaxFuncCalls) {
            XML::readAttribute(r, XML::Attrib::value, &m_maxFuncCalls);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

// ----------------------------------------------------------------------------

GSLMultiMinimizerItem::GSLMultiMinimizerItem()
    : MinimizerItem()
    , m_maxIterations(0)
{
    QString minimizer_name = minimizerTypeToName(MinimizerType::GSLMultiMin);
    m_algorithm = MinimizerItemCatalog::algorithmCombo(minimizer_name);
}

GSLMultiMinimizerItem::~GSLMultiMinimizerItem() = default;

QString GSLMultiMinimizerItem::currentAlgorithm() const
{
    return m_algorithm.currentValue();
}

void GSLMultiMinimizerItem::setCurrentAlgorithm(const QString& name)
{
    m_algorithm.setCurrentValue(name);
}

int GSLMultiMinimizerItem::maxIterations() const
{
    return m_maxIterations;
}

void GSLMultiMinimizerItem::setMaxIterations(int value)
{
    m_maxIterations = value;
}

std::unique_ptr<IMinimizer> GSLMultiMinimizerItem::createMinimizer() const
{
    auto* domainMinimizer = new GSLMultiMinimizer(currentAlgorithm().toStdString());
    domainMinimizer->setMaxIterations(maxIterations());
    return std::unique_ptr<IMinimizer>(domainMinimizer);
}

void GSLMultiMinimizerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // algorithm
    w->writeStartElement(Tag::Algorithm);
    m_algorithm.writeTo(w);
    w->writeEndElement();

    // max iterations
    w->writeStartElement(Tag::MaxIterations);
    XML::writeAttribute(w, XML::Attrib::value, m_maxIterations);
    w->writeEndElement();
}

void GSLMultiMinimizerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // algorithm
        if (tag == Tag::Algorithm) {
            m_algorithm.readFrom(r);
            XML::gotoEndElementOfTag(r, tag);

            // max iterations
        } else if (tag == Tag::MaxIterations) {
            XML::readAttribute(r, XML::Attrib::value, &m_maxIterations);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

// ----------------------------------------------------------------------------

GeneticMinimizerItem::GeneticMinimizerItem()
    : MinimizerItem()
    , m_tolerance(0.01)
    , m_maxIterations(3)
    , m_populationSize(300)
    , m_randomSeed(0)
{
}

GeneticMinimizerItem::~GeneticMinimizerItem() = default;

double GeneticMinimizerItem::tolerance() const
{
    return m_tolerance;
}

void GeneticMinimizerItem::setTolerance(double value)
{
    m_tolerance = value;
}

int GeneticMinimizerItem::maxIterations() const
{
    return m_maxIterations;
}

void GeneticMinimizerItem::setMaxIterations(int value)
{
    m_maxIterations = value;
}

int GeneticMinimizerItem::populationSize() const
{
    return m_populationSize;
}

void GeneticMinimizerItem::setPopulationSize(int value)
{
    m_populationSize = value;
}

int GeneticMinimizerItem::randomSeed() const
{
    return m_randomSeed;
}

void GeneticMinimizerItem::setRandomSeed(int value)
{
    m_randomSeed = value;
}

std::unique_ptr<IMinimizer> GeneticMinimizerItem::createMinimizer() const
{
    auto* domainMinimizer = new GeneticMinimizer();
    domainMinimizer->setTolerance(tolerance());
    domainMinimizer->setMaxIterations(maxIterations());
    domainMinimizer->setPopulationSize(populationSize());
    domainMinimizer->setRandomSeed(randomSeed());
    return std::unique_ptr<IMinimizer>(domainMinimizer);
}

void GeneticMinimizerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // tolerance
    w->writeStartElement(Tag::Tolerance);
    XML::writeAttribute(w, XML::Attrib::value, m_tolerance);
    w->writeEndElement();

    // max iterations
    w->writeStartElement(Tag::MaxIterations);
    XML::writeAttribute(w, XML::Attrib::value, m_maxIterations);
    w->writeEndElement();

    // population size
    w->writeStartElement(Tag::PopulationSize);
    XML::writeAttribute(w, XML::Attrib::value, m_populationSize);
    w->writeEndElement();

    // random seed
    w->writeStartElement(Tag::RandomSeed);
    XML::writeAttribute(w, XML::Attrib::value, m_randomSeed);
    w->writeEndElement();
}

void GeneticMinimizerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // tolerance
        if (tag == Tag::Tolerance) {
            XML::readAttribute(r, XML::Attrib::value, &m_tolerance);
            XML::gotoEndElementOfTag(r, tag);

            // max iterations
        } else if (tag == Tag::MaxIterations) {
            XML::readAttribute(r, XML::Attrib::value, &m_maxIterations);
            XML::gotoEndElementOfTag(r, tag);

            // population size
        } else if (tag == Tag::PopulationSize) {
            XML::readAttribute(r, XML::Attrib::value, &m_populationSize);
            XML::gotoEndElementOfTag(r, tag);

            // random seed
        } else if (tag == Tag::RandomSeed) {
            XML::readAttribute(r, XML::Attrib::value, &m_randomSeed);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

// ----------------------------------------------------------------------------

SimAnMinimizerItem::SimAnMinimizerItem()
    : MinimizerItem()
    , m_maxIterations(100)
    , m_iterationsAtTemp(10)
    , m_stepSize(1.0)
    , m_boltzmann_K(1.0)
    , m_boltzmann_T_init(50.0)
    , m_boltzmann_Mu(1.05)
    , m_boltzmann_T_min(0.1)
{
}

SimAnMinimizerItem::~SimAnMinimizerItem() = default;

int SimAnMinimizerItem::maxIterations() const
{
    return m_maxIterations;
}

void SimAnMinimizerItem::setMaxIterations(int value)
{
    m_maxIterations = value;
}

int SimAnMinimizerItem::iterationsAtEachTemp() const
{
    return m_iterationsAtTemp;
}

void SimAnMinimizerItem::setIterationsAtEachTemp(int value)
{
    m_iterationsAtTemp = value;
}

double SimAnMinimizerItem::stepSize() const
{
    return m_stepSize;
}

void SimAnMinimizerItem::setStepSize(double value)
{
    m_stepSize = value;
}

double SimAnMinimizerItem::boltzmanK() const
{
    return m_boltzmann_K;
}

void SimAnMinimizerItem::setBoltzmanK(double value)
{
    m_boltzmann_K = value;
}

double SimAnMinimizerItem::boltzmanInitT() const
{
    return m_boltzmann_T_init;
}

void SimAnMinimizerItem::setBoltzmanInitT(double value)
{
    m_boltzmann_T_init = value;
}

double SimAnMinimizerItem::boltzmanMu() const
{
    return m_boltzmann_Mu;
}

void SimAnMinimizerItem::setBoltzmanMu(double value)
{
    m_boltzmann_Mu = value;
}

double SimAnMinimizerItem::boltzmanMinT() const
{
    return m_boltzmann_T_min;
}

void SimAnMinimizerItem::setBoltzmanMinT(double value)
{
    m_boltzmann_T_min = value;
}

std::unique_ptr<IMinimizer> SimAnMinimizerItem::createMinimizer() const
{
    auto* domainMinimizer = new SimAnMinimizer();
    domainMinimizer->setMaxIterations(maxIterations());
    domainMinimizer->setIterationsAtEachTemp(iterationsAtEachTemp());
    domainMinimizer->setStepSize(stepSize());
    domainMinimizer->setBoltzmannK(boltzmanK());
    domainMinimizer->setBoltzmannInitialTemp(boltzmanInitT());
    domainMinimizer->setBoltzmannMu(boltzmanMu());
    domainMinimizer->setBoltzmannMinTemp(boltzmanMinT());
    return std::unique_ptr<IMinimizer>(domainMinimizer);
}

void SimAnMinimizerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // max iterations
    w->writeStartElement(Tag::MaxIterations);
    XML::writeAttribute(w, XML::Attrib::value, m_maxIterations);
    w->writeEndElement();

    // iterations at each temperature
    w->writeStartElement(Tag::IterationsAtTemp);
    XML::writeAttribute(w, XML::Attrib::value, m_iterationsAtTemp);
    w->writeEndElement();

    // step size
    w->writeStartElement(Tag::StepSize);
    XML::writeAttribute(w, XML::Attrib::value, m_stepSize);
    w->writeEndElement();

    // k
    w->writeStartElement(Tag::BoltzmannK);
    XML::writeAttribute(w, XML::Attrib::value, m_boltzmann_K);
    w->writeEndElement();

    // T init
    w->writeStartElement(Tag::Tinit);
    XML::writeAttribute(w, XML::Attrib::value, m_boltzmann_T_init);
    w->writeEndElement();

    // mu
    w->writeStartElement(Tag::BoltzmannMu);
    XML::writeAttribute(w, XML::Attrib::value, m_boltzmann_Mu);
    w->writeEndElement();

    // T min
    w->writeStartElement(Tag::Tmin);
    XML::writeAttribute(w, XML::Attrib::value, m_boltzmann_T_min);
    w->writeEndElement();
}

void SimAnMinimizerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // max iterations
        if (tag == Tag::MaxIterations) {
            XML::readAttribute(r, XML::Attrib::value, &m_maxIterations);
            XML::gotoEndElementOfTag(r, tag);

            // iterations at each temperature
        } else if (tag == Tag::IterationsAtTemp) {
            XML::readAttribute(r, XML::Attrib::value, &m_iterationsAtTemp);
            XML::gotoEndElementOfTag(r, tag);

            // step size
        } else if (tag == Tag::StepSize) {
            XML::readAttribute(r, XML::Attrib::value, &m_stepSize);
            XML::gotoEndElementOfTag(r, tag);

            // k
        } else if (tag == Tag::BoltzmannK) {
            XML::readAttribute(r, XML::Attrib::value, &m_boltzmann_K);
            XML::gotoEndElementOfTag(r, tag);

            // T init
        } else if (tag == Tag::Tinit) {
            XML::readAttribute(r, XML::Attrib::value, &m_boltzmann_T_init);
            XML::gotoEndElementOfTag(r, tag);

            // mu
        } else if (tag == Tag::BoltzmannMu) {
            XML::readAttribute(r, XML::Attrib::value, &m_boltzmann_Mu);
            XML::gotoEndElementOfTag(r, tag);

            // T min
        } else if (tag == Tag::Tmin) {
            XML::readAttribute(r, XML::Attrib::value, &m_boltzmann_T_min);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}

// ----------------------------------------------------------------------------

GSLLMAMinimizerItem::GSLLMAMinimizerItem()
    : MinimizerItem()
    , m_tolerance(0.01)
    , m_maxIterations(0)
{
}

GSLLMAMinimizerItem::~GSLLMAMinimizerItem() = default;

double GSLLMAMinimizerItem::tolerance() const
{
    return m_tolerance;
}

void GSLLMAMinimizerItem::setTolerance(double value)
{
    m_tolerance = value;
}

int GSLLMAMinimizerItem::maxIterations() const
{
    return m_maxIterations;
}

void GSLLMAMinimizerItem::setMaxIterations(int value)
{
    m_maxIterations = value;
}

std::unique_ptr<IMinimizer> GSLLMAMinimizerItem::createMinimizer() const
{
    auto* domainMinimizer = new GSLLevenbergMarquardtMinimizer();
    domainMinimizer->setTolerance(tolerance());
    domainMinimizer->setMaxIterations(maxIterations());
    return std::unique_ptr<IMinimizer>(domainMinimizer);
}

void GSLLMAMinimizerItem::writeTo(QXmlStreamWriter* w) const
{
    XML::writeAttribute(w, XML::Attrib::version, uint(1));

    // tolerance
    w->writeStartElement(Tag::Tolerance);
    XML::writeAttribute(w, XML::Attrib::value, m_tolerance);
    w->writeEndElement();

    // max iterations
    w->writeStartElement(Tag::MaxIterations);
    XML::writeAttribute(w, XML::Attrib::value, m_maxIterations);
    w->writeEndElement();
}

void GSLLMAMinimizerItem::readFrom(QXmlStreamReader* r)
{
    const uint version = XML::readUIntAttribute(r, XML::Attrib::version);
    Q_UNUSED(version)

    while (r->readNextStartElement()) {
        QString tag = r->name().toString();

        // tolerance
        if (tag == Tag::Tolerance) {
            XML::readAttribute(r, XML::Attrib::value, &m_tolerance);
            XML::gotoEndElementOfTag(r, tag);

            // max iterations
        } else if (tag == Tag::MaxIterations) {
            XML::readAttribute(r, XML::Attrib::value, &m_maxIterations);
            XML::gotoEndElementOfTag(r, tag);

        } else
            r->skipCurrentElement();
    }
}
