<commit_before>///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//    This file is part of qt-pods.                                          //
//    Copyright (C) 2015 Jacob Dawid, jacob@omg-it.works                     //
//                                                                           //
//    qt-pods is free software: you can redistribute it and/or modify        //
//    it under the terms of the GNU General Public License as published by   //
//    the Free Software Foundation, either version 3 of the License, or      //
//    (at your option) any later version.                                    //
//                                                                           //
//    qt-pods 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 General Public License for more details.                           //
//                                                                           //
//    You should have received a copy of the GNU General Public License      //
//    along with qt-pods. If not, see <http://www.gnu.org/licenses/>.        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// Own includes
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "pod.h"
#include "poddialog.h"
#include "sourcesdialog.h"

// Qt includes
#include <QFileDialog>
#include <QSettings>
#include <QStandardPaths>
#include <QMessageBox>
#include <QDesktopServices>
#include <QDebug>

#ifdef Q_OS_UNIX
#include <unistd.h>
#endif

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow) {
    setObjectName("MainWindow");
    setupStdOutRedirect();

    _settings = new QSettings("qt-pods", "qt-pods");

    _workerThread = new QThread();
    _podManager = new PodManager();
    _podManager->moveToThread(_workerThread);
    _workerThread->start();

    connect(_podManager, SIGNAL(installPodsFinished(QString, QList<Pod>, bool)),
            this, SLOT(installPodsFinished(QString,QList<Pod>,bool)));
    connect(_podManager, SIGNAL(removePodsFinished(QString, QStringList, bool)),
            this, SLOT(removePodsFinished(QString, QStringList,bool)));
    connect(_podManager, SIGNAL(updatePodsFinished(QString, QStringList, bool)),
            this, SLOT(updatePodsFinished(QString, QStringList, bool)));
    connect(_podManager, SIGNAL(installedPodsFinished(QString, QList<Pod>)),
            this, SLOT(installedPodsFinished(QString,QList<Pod>)));
    connect(_podManager, SIGNAL(availablePodsFinished(QStringList, QList<Pod>)),
            this, SLOT(availablePodsFinished(QStringList,QList<Pod>)));

    ui->setupUi(this);

    _localPods = new PodsModel();
    _remotePods = new PodsModel();

    _localPodsProxyModel = new QSortFilterProxyModel();
    _localPodsProxyModel->setSourceModel(_localPods);
    _localPodsProxyModel->setFilterKeyColumn(-1);

    _remotePodsProxyModel = new QSortFilterProxyModel();
    _remotePodsProxyModel->setSourceModel(_remotePods);
    _remotePodsProxyModel->setFilterKeyColumn(-1);

    ui->tableViewLocal->setModel(_localPodsProxyModel);
    ui->tableViewRemote->setModel(_remotePodsProxyModel);

    _systemTrayIcon.setIcon(QIcon(":/icons/icons/system-upgrade.svg"));
    _systemTrayIcon.setToolTip("Qt Pods");
    _systemTrayIcon.show();

    _availablePodsSpinnerWidget = new WaitingSpinnerWidget(ui->tableViewRemote);
    _localPodsSpinnerWidget = new WaitingSpinnerWidget(ui->tableViewLocal);

    updateBuildInfo();
    loadSettings();
}

MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::stdOutActivated(int fileDescriptor) {
    char readBuffer[1024];
    int numberOfBytesRead = ::read(fileDescriptor, readBuffer, sizeof(readBuffer) - 1);
    if(numberOfBytesRead > 0) {
        // Terminate buffer - just in case.
        readBuffer[numberOfBytesRead] = (char)0;

        ui->plainTextEditDiagnostic->appendPlainText(readBuffer);
    }
}

void MainWindow::updateBuildInfo() {
    QString buildString = QString("%1-%2 (built on %3 at %4)")
            .arg(GIT_VERSION)
#ifdef QT_DEBUG
            .arg("debug")
#else
            .arg("release")
#endif
            .arg(__DATE__)
            .arg(__TIME__);

    ui->lineEditBuild->setText(buildString);
}

void MainWindow::setupStdOutRedirect() {
#ifdef Q_OS_UNIX
    // Redirect our own stdout/stderr.
    int pipeDescriptors[2];
    if(::pipe(pipeDescriptors) == 0) {
        ::dup2(pipeDescriptors[1], STDOUT_FILENO);
        ::dup2(pipeDescriptors[1], STDERR_FILENO);
        _stdOutSocketNotifier = new QSocketNotifier(pipeDescriptors[0], QSocketNotifier::Read, this);
        connect(_stdOutSocketNotifier, SIGNAL(activated(int)),
                this, SLOT(stdOutActivated(int)));
    }
#endif
}

void MainWindow::on_pushButtonAddRepository_clicked() {
    QString directory = QFileDialog::getExistingDirectory(this, tr("Add git repository"));
    if(!directory.isEmpty()) {
        if(!_podManager->isGitRepository(directory)) {
            QMessageBox::warning(this,
                tr("Invalid repository"),
                tr("The directory you supplied does not appear to be the root of a valid git repository."));
            return;
        }

        if(ui->comboBoxCurrentRepository->findText(directory) == -1) {
            ui->comboBoxCurrentRepository->addItem(directory);
        }

        ui->comboBoxCurrentRepository->setCurrentText(directory);

        saveSettings();

        refreshLocalPods();
    }
}

void MainWindow::on_pushButtonRemoveRepository_clicked() {
    int confirmationResult = QMessageBox::warning(this,
        tr("Confirm removal"),
        tr("Are you sure you want to remove the repository \"%1\"?")
            .arg(ui->comboBoxCurrentRepository->currentText()), QMessageBox::No, QMessageBox::Yes);
    if(confirmationResult == QMessageBox::Yes) {
        ui->comboBoxCurrentRepository->removeItem(ui->comboBoxCurrentRepository->currentIndex());
        saveSettings();
    }
}

void MainWindow::on_comboBoxCurrentRepository_currentTextChanged(QString text) {
    setWindowTitle(QString("Qt Pods - %1").arg(text));
    refreshLocalPods();
}

void MainWindow::on_tabWidgetPods_currentChanged(int index) {
    switch (index) {
    case 0:
        refreshLocalPods();
        break;
    case 1:
        refreshAvailablePods();
    default:
        break;
    }
}

void MainWindow::on_lineEditSearchLocal_textChanged(QString text) {
    _localPodsProxyModel->setFilterWildcard(text);
}

void MainWindow::on_lineEditSearchRemote_textChanged(QString text) {
    _remotePodsProxyModel->setFilterWildcard(text);
}

void MainWindow::on_pushButtonRemoveLocalPods_clicked() {
    QModelIndexList modelIndices = ui->tableViewLocal->selectionModel()->selectedRows(0);
    if(modelIndices.count() == 0) {
        return;
    }

    int confirmationResult;
    if(modelIndices.count() == 1) {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm removal"),
            tr("Are you sure you want to remove \"%1\" from the repository?")
                .arg(modelIndices.at(0).data().toString()), QMessageBox::No, QMessageBox::Yes);
    } else {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm removal"),
            tr("Are you sure you want to remove %1 pods from the repository?")
                .arg(modelIndices.count()), QMessageBox::No, QMessageBox::Yes);
    }

    if(confirmationResult == QMessageBox::Yes) {
        _localPodsSpinnerWidget->start();
        QStringList podNames;
        foreach(QModelIndex modelIndex, modelIndices) {
            podNames.append(_localPods->pod(modelIndex).name);
        }

        metaObject()->invokeMethod(_podManager, "removePods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QStringList, podNames));
    }
}

void MainWindow::on_pushButtonUpdateLocalPods_clicked() {
    QModelIndexList modelIndices = ui->tableViewLocal->selectionModel()->selectedRows(0);
    if(modelIndices.count() == 0) {
        return;
    }

    int confirmationResult;
    if(modelIndices.count() == 1) {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm update"),
            tr("Are you sure you want to update \"%1\"?")
                .arg(modelIndices.at(0).data().toString()),QMessageBox::No, QMessageBox::Yes);
    } else {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm update"),
            tr("Are you sure you want to update \"%1\" pods?")
                .arg(modelIndices.count()), QMessageBox::No, QMessageBox::Yes);
    }

    if(confirmationResult == QMessageBox::Yes) {
        _localPodsSpinnerWidget->start();
        QStringList podNames;
        foreach(QModelIndex modelIndex, modelIndices) {
            podNames.append(_localPods->pod(modelIndex).name);
        }

        metaObject()->invokeMethod(_podManager, "updatePods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QStringList, podNames));
    }
}

void MainWindow::on_pushButtonRefreshLocalPods_clicked() {
    ui->tableViewLocal->setEnabled(false);
    ui->pushButtonRemoveLocalPods->setEnabled(false);
    ui->pushButtonRefreshLocalPods->setEnabled(false);

    refreshLocalPods();

    ui->tableViewLocal->setEnabled(true);
    ui->pushButtonRemoveLocalPods->setEnabled(true);
    ui->pushButtonRefreshLocalPods->setEnabled(true);
}

void MainWindow::on_pushButtonManageSources_clicked() {
    SourcesDialog sourcesDialog;
    QStringList sources = _settings->value("sources").toStringList();

    // At least add one source
    if(sources.isEmpty()) {
        sources << "http://qt-pods.org/pods.json";
    }
    sourcesDialog.setSources(sources);

    if(sourcesDialog.exec() == QDialog::Accepted) {
        _settings->setValue("sources", sourcesDialog.sources());
        _settings->sync();
    }
}

void MainWindow::on_pushButtonRefreshAvailablePods_clicked() {
    ui->tableViewRemote->setEnabled(false);
    ui->pushButtonInstallPods->setEnabled(false);
    ui->pushButtonRefreshAvailablePods->setEnabled(false);

    refreshAvailablePods();

    ui->tableViewRemote->setEnabled(true);
    ui->pushButtonInstallPods->setEnabled(true);
    ui->pushButtonRefreshAvailablePods->setEnabled(true);
}

void MainWindow::on_pushButtonInstallPods_clicked() {
    _availablePodsSpinnerWidget->start();
    _localPodsSpinnerWidget->start();

    QList<Pod> pods = _remotePods->pods(ui->tableViewRemote->selectionModel()->selectedRows(0));
    metaObject()->invokeMethod(_podManager,
                               "installPods",
                               Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                               Q_ARG(QList<Pod>, pods));
}

void MainWindow::on_pushButtonInstallExternalPod_clicked() {
    PodDialog podDialog;
    if(podDialog.exec() == QDialog::Accepted) {
        _availablePodsSpinnerWidget->start();
        _localPodsSpinnerWidget->start();

        QModelIndexList modelIndices = ui->tableViewRemote->selectionModel()->selectedRows(0);
        QList<Pod> pods;
        pods.append(podDialog.pod());

        metaObject()->invokeMethod(_podManager,
                                   "installPods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QList<Pod>, pods));
    }
}

void MainWindow::on_pushButtonReportIssue_clicked() {
    QDesktopServices desktopServices;
    desktopServices.openUrl(QUrl("https://github.com/cybercatalyst/qt-pods/issues"));
}

void MainWindow::on_tableViewLocal_doubleClicked(QModelIndex index) {
    PodDialog podDialog;
    podDialog.setPod(_localPods->pod(index));
    podDialog.setEditable(false);
    podDialog.exec();
}

void MainWindow::on_tableViewRemote_doubleClicked(QModelIndex index) {
    PodDialog podDialog;
    podDialog.setPod(_remotePods->pod(index));
    podDialog.setEditable(false);
    podDialog.exec();
}

void MainWindow::closeEvent(QCloseEvent *closeEvent) {
    saveSettings();
    QMainWindow::closeEvent(closeEvent);
}

void MainWindow::loadSettings() {
    QStringList repositories = _settings->value("local-repositories").toStringList();
    ui->comboBoxCurrentRepository->addItems(repositories);
    ui->comboBoxCurrentRepository->setCurrentText(_settings->value("active-repository").toString());
}

void MainWindow::saveSettings() {
    QStringList repositories;
    for(int i = 0; i < ui->comboBoxCurrentRepository->count(); i++) {
        repositories.append(ui->comboBoxCurrentRepository->itemText(i));
    }
    _settings->setValue("local-repositories", repositories);
    _settings->setValue("active-repository", ui->comboBoxCurrentRepository->currentText());
    _settings->sync();
}

void MainWindow::installPodsFinished(QString repository, QList<Pod> pods, bool success) {
    _availablePodsSpinnerWidget->stop();
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Installing pods in %1").arg(repository),
                                 tr("Finished installing %1 pods.").arg(pods.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Installing pods in %1").arg(repository),
                                 tr("Installing some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::removePodsFinished(QString repository, QStringList podNames, bool success) {
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Removing pods in %1").arg(repository),
                                 tr("Finished removing %1 pods.").arg(podNames.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Removing pods in %1").arg(repository),
                                 tr("Removing some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::updatePodsFinished(QString repository, QStringList podNames, bool success) {
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Updating pods in %1").arg(repository),
                                 tr("Finished updating %1 pods.").arg(podNames.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Updating pods in %1").arg(repository),
                                 tr("Updating some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::installedPodsFinished(QString repository, QList<Pod> installedPods) {
    Q_UNUSED(repository);
    _localPods->setModelData(installedPods);
    _localPodsSpinnerWidget->stop();
    ui->tableViewLocal->resizeColumnsToContents();
}

void MainWindow::availablePodsFinished(QStringList sources, QList<Pod> availablePods) {
    Q_UNUSED(sources);
    _remotePods->setModelData(availablePods);
    _availablePodsSpinnerWidget->stop();
    ui->tableViewRemote->resizeColumnsToContents();
}

void MainWindow::refreshLocalPods() {
    _localPodsSpinnerWidget->start();

    QString repository = ui->comboBoxCurrentRepository->currentText();
    if(!_podManager->isGitRepository(repository)) {
        QMessageBox::warning(this,
            tr("Invalid repository"),
            tr("The directory you supplied does not appear to be the root of a valid git repository."));

        int index;
        while((index = ui->comboBoxCurrentRepository->findText(repository)) != -1) {
            ui->comboBoxCurrentRepository->removeItem(index);
        }
        return;
    }

    metaObject()->invokeMethod(_podManager, "installedPods", Q_ARG(QString, repository));
}

void MainWindow::refreshAvailablePods() {
    _availablePodsSpinnerWidget->start();

    QStringList sources = _settings->value("sources").toStringList();
    if(sources.isEmpty()) {
        sources << "http://qt-pods.org/pods.json";
    }

    metaObject()->invokeMethod(_podManager, "availablePods", Q_ARG(QStringList, sources));
}

<commit_msg>Fixed proxy model mapping.<commit_after>///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//    This file is part of qt-pods.                                          //
//    Copyright (C) 2015 Jacob Dawid, jacob@omg-it.works                     //
//                                                                           //
//    qt-pods is free software: you can redistribute it and/or modify        //
//    it under the terms of the GNU General Public License as published by   //
//    the Free Software Foundation, either version 3 of the License, or      //
//    (at your option) any later version.                                    //
//                                                                           //
//    qt-pods 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 General Public License for more details.                           //
//                                                                           //
//    You should have received a copy of the GNU General Public License      //
//    along with qt-pods. If not, see <http://www.gnu.org/licenses/>.        //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// Own includes
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "pod.h"
#include "poddialog.h"
#include "sourcesdialog.h"

// Qt includes
#include <QFileDialog>
#include <QSettings>
#include <QStandardPaths>
#include <QMessageBox>
#include <QDesktopServices>
#include <QDebug>

#ifdef Q_OS_UNIX
#include <unistd.h>
#endif

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow) {
    setObjectName("MainWindow");
    setupStdOutRedirect();

    _settings = new QSettings("qt-pods", "qt-pods");

    _workerThread = new QThread();
    _podManager = new PodManager();
    _podManager->moveToThread(_workerThread);
    _workerThread->start();

    connect(_podManager, SIGNAL(installPodsFinished(QString, QList<Pod>, bool)),
            this, SLOT(installPodsFinished(QString,QList<Pod>,bool)));
    connect(_podManager, SIGNAL(removePodsFinished(QString, QStringList, bool)),
            this, SLOT(removePodsFinished(QString, QStringList,bool)));
    connect(_podManager, SIGNAL(updatePodsFinished(QString, QStringList, bool)),
            this, SLOT(updatePodsFinished(QString, QStringList, bool)));
    connect(_podManager, SIGNAL(installedPodsFinished(QString, QList<Pod>)),
            this, SLOT(installedPodsFinished(QString,QList<Pod>)));
    connect(_podManager, SIGNAL(availablePodsFinished(QStringList, QList<Pod>)),
            this, SLOT(availablePodsFinished(QStringList,QList<Pod>)));

    ui->setupUi(this);

    _localPods = new PodsModel();
    _remotePods = new PodsModel();

    _localPodsProxyModel = new QSortFilterProxyModel();
    _localPodsProxyModel->setSourceModel(_localPods);
    _localPodsProxyModel->setFilterKeyColumn(-1);

    _remotePodsProxyModel = new QSortFilterProxyModel();
    _remotePodsProxyModel->setSourceModel(_remotePods);
    _remotePodsProxyModel->setFilterKeyColumn(-1);

    ui->tableViewLocal->setModel(_localPodsProxyModel);
    ui->tableViewRemote->setModel(_remotePodsProxyModel);

    _systemTrayIcon.setIcon(QIcon(":/icons/icons/system-upgrade.svg"));
    _systemTrayIcon.setToolTip("Qt Pods");
    _systemTrayIcon.show();

    _availablePodsSpinnerWidget = new WaitingSpinnerWidget(ui->tableViewRemote);
    _localPodsSpinnerWidget = new WaitingSpinnerWidget(ui->tableViewLocal);

    updateBuildInfo();
    loadSettings();
}

MainWindow::~MainWindow() {
    delete ui;
}

void MainWindow::stdOutActivated(int fileDescriptor) {
    char readBuffer[1024];
    int numberOfBytesRead = ::read(fileDescriptor, readBuffer, sizeof(readBuffer) - 1);
    if(numberOfBytesRead > 0) {
        // Terminate buffer - just in case.
        readBuffer[numberOfBytesRead] = (char)0;

        ui->plainTextEditDiagnostic->appendPlainText(readBuffer);
    }
}

void MainWindow::updateBuildInfo() {
    QString buildString = QString("%1-%2 (built on %3 at %4)")
            .arg(GIT_VERSION)
#ifdef QT_DEBUG
            .arg("debug")
#else
            .arg("release")
#endif
            .arg(__DATE__)
            .arg(__TIME__);

    ui->lineEditBuild->setText(buildString);
}

void MainWindow::setupStdOutRedirect() {
#ifdef Q_OS_UNIX
    // Redirect our own stdout/stderr.
    int pipeDescriptors[2];
    if(::pipe(pipeDescriptors) == 0) {
        ::dup2(pipeDescriptors[1], STDOUT_FILENO);
        ::dup2(pipeDescriptors[1], STDERR_FILENO);
        _stdOutSocketNotifier = new QSocketNotifier(pipeDescriptors[0], QSocketNotifier::Read, this);
        connect(_stdOutSocketNotifier, SIGNAL(activated(int)),
                this, SLOT(stdOutActivated(int)));
    }
#endif
}

void MainWindow::on_pushButtonAddRepository_clicked() {
    QString directory = QFileDialog::getExistingDirectory(this, tr("Add git repository"));
    if(!directory.isEmpty()) {
        if(!_podManager->isGitRepository(directory)) {
            QMessageBox::warning(this,
                tr("Invalid repository"),
                tr("The directory you supplied does not appear to be the root of a valid git repository."));
            return;
        }

        if(ui->comboBoxCurrentRepository->findText(directory) == -1) {
            ui->comboBoxCurrentRepository->addItem(directory);
        }

        ui->comboBoxCurrentRepository->setCurrentText(directory);

        saveSettings();

        refreshLocalPods();
    }
}

void MainWindow::on_pushButtonRemoveRepository_clicked() {
    int confirmationResult = QMessageBox::warning(this,
        tr("Confirm removal"),
        tr("Are you sure you want to remove the repository \"%1\"?")
            .arg(ui->comboBoxCurrentRepository->currentText()), QMessageBox::No, QMessageBox::Yes);
    if(confirmationResult == QMessageBox::Yes) {
        ui->comboBoxCurrentRepository->removeItem(ui->comboBoxCurrentRepository->currentIndex());
        saveSettings();
    }
}

void MainWindow::on_comboBoxCurrentRepository_currentTextChanged(QString text) {
    setWindowTitle(QString("Qt Pods - %1").arg(text));
    refreshLocalPods();
}

void MainWindow::on_tabWidgetPods_currentChanged(int index) {
    switch (index) {
    case 0:
        refreshLocalPods();
        break;
    case 1:
        refreshAvailablePods();
    default:
        break;
    }
}

void MainWindow::on_lineEditSearchLocal_textChanged(QString text) {
    _localPodsProxyModel->setFilterWildcard(text);
}

void MainWindow::on_lineEditSearchRemote_textChanged(QString text) {
    _remotePodsProxyModel->setFilterWildcard(text);
}

void MainWindow::on_pushButtonRemoveLocalPods_clicked() {
    QItemSelection itemSelection = ui->tableViewLocal->selectionModel()->selection();
    QModelIndexList selectedModelIndices = _localPodsProxyModel->mapSelectionToSource(itemSelection).indexes();
    QModelIndexList modelIndices;
    foreach(QModelIndex i, selectedModelIndices) {
        if(i.column() == 0) {
            modelIndices.append(i);
        }
    }

    if(modelIndices.count() == 0) {
        return;
    }

    int confirmationResult;
    if(modelIndices.count() == 1) {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm removal"),
            tr("Are you sure you want to remove \"%1\" from the repository?")
                .arg(modelIndices.at(0).data().toString()), QMessageBox::No, QMessageBox::Yes);
    } else {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm removal"),
            tr("Are you sure you want to remove %1 pods from the repository?")
                .arg(modelIndices.count()), QMessageBox::No, QMessageBox::Yes);
    }

    if(confirmationResult == QMessageBox::Yes) {
        _localPodsSpinnerWidget->start();
        QStringList podNames;
        foreach(QModelIndex modelIndex, modelIndices) {
            podNames.append(_localPods->pod(modelIndex).name);
        }

        metaObject()->invokeMethod(_podManager, "removePods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QStringList, podNames));
    }
}

void MainWindow::on_pushButtonUpdateLocalPods_clicked() {
    QItemSelection itemSelection = ui->tableViewLocal->selectionModel()->selection();
    QModelIndexList selectedModelIndices = _localPodsProxyModel->mapSelectionToSource(itemSelection).indexes();
    QModelIndexList modelIndices;
    foreach(QModelIndex i, selectedModelIndices) {
        if(i.column() == 0) {
            modelIndices.append(i);
        }
    }

    if(modelIndices.count() == 0) {
        return;
    }

    int confirmationResult;
    if(modelIndices.count() == 1) {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm update"),
            tr("Are you sure you want to update \"%1\"?")
                .arg(modelIndices.at(0).data().toString()),QMessageBox::No, QMessageBox::Yes);
    } else {
        confirmationResult = QMessageBox::warning(this,
            tr("Confirm update"),
            tr("Are you sure you want to update \"%1\" pods?")
                .arg(modelIndices.count()), QMessageBox::No, QMessageBox::Yes);
    }

    if(confirmationResult == QMessageBox::Yes) {
        _localPodsSpinnerWidget->start();
        QStringList podNames;
        foreach(QModelIndex modelIndex, modelIndices) {
            podNames.append(_localPods->pod(modelIndex).name);
        }

        metaObject()->invokeMethod(_podManager, "updatePods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QStringList, podNames));
    }
}

void MainWindow::on_pushButtonRefreshLocalPods_clicked() {
    ui->tableViewLocal->setEnabled(false);
    ui->pushButtonRemoveLocalPods->setEnabled(false);
    ui->pushButtonRefreshLocalPods->setEnabled(false);

    refreshLocalPods();

    ui->tableViewLocal->setEnabled(true);
    ui->pushButtonRemoveLocalPods->setEnabled(true);
    ui->pushButtonRefreshLocalPods->setEnabled(true);
}

void MainWindow::on_pushButtonManageSources_clicked() {
    SourcesDialog sourcesDialog;
    QStringList sources = _settings->value("sources").toStringList();

    // At least add one source
    if(sources.isEmpty()) {
        sources << "http://qt-pods.org/pods.json";
    }
    sourcesDialog.setSources(sources);

    if(sourcesDialog.exec() == QDialog::Accepted) {
        _settings->setValue("sources", sourcesDialog.sources());
        _settings->sync();
    }
}

void MainWindow::on_pushButtonRefreshAvailablePods_clicked() {
    ui->tableViewRemote->setEnabled(false);
    ui->pushButtonInstallPods->setEnabled(false);
    ui->pushButtonRefreshAvailablePods->setEnabled(false);

    refreshAvailablePods();

    ui->tableViewRemote->setEnabled(true);
    ui->pushButtonInstallPods->setEnabled(true);
    ui->pushButtonRefreshAvailablePods->setEnabled(true);
}

void MainWindow::on_pushButtonInstallPods_clicked() {
    _availablePodsSpinnerWidget->start();
    _localPodsSpinnerWidget->start();

    QItemSelection itemSelection = ui->tableViewRemote->selectionModel()->selection();
    QModelIndexList selectedModelIndices =_remotePodsProxyModel->mapSelectionToSource(itemSelection).indexes();
    QModelIndexList modelIndices;
    foreach(QModelIndex i, selectedModelIndices) {
        if(i.column() == 0) {
            modelIndices.append(i);
        }
    }

    QList<Pod> pods = _remotePods->pods(modelIndices);
    metaObject()->invokeMethod(_podManager,
                               "installPods",
                               Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                               Q_ARG(QList<Pod>, pods));
}

void MainWindow::on_pushButtonInstallExternalPod_clicked() {
    PodDialog podDialog;
    if(podDialog.exec() == QDialog::Accepted) {
        _availablePodsSpinnerWidget->start();
        _localPodsSpinnerWidget->start();

        QList<Pod> pods;
        pods.append(podDialog.pod());

        metaObject()->invokeMethod(_podManager,
                                   "installPods",
                                   Q_ARG(QString, ui->comboBoxCurrentRepository->currentText()),
                                   Q_ARG(QList<Pod>, pods));
    }
}

void MainWindow::on_pushButtonReportIssue_clicked() {
    QDesktopServices desktopServices;
    desktopServices.openUrl(QUrl("https://github.com/cybercatalyst/qt-pods/issues"));
}

void MainWindow::on_tableViewLocal_doubleClicked(QModelIndex index) {
    PodDialog podDialog;
    podDialog.setPod(_localPods->pod(index));
    podDialog.setEditable(false);
    podDialog.exec();
}

void MainWindow::on_tableViewRemote_doubleClicked(QModelIndex index) {
    PodDialog podDialog;
    podDialog.setPod(_remotePods->pod(index));
    podDialog.setEditable(false);
    podDialog.exec();
}

void MainWindow::closeEvent(QCloseEvent *closeEvent) {
    saveSettings();
    QMainWindow::closeEvent(closeEvent);
}

void MainWindow::loadSettings() {
    QStringList repositories = _settings->value("local-repositories").toStringList();
    ui->comboBoxCurrentRepository->addItems(repositories);
    ui->comboBoxCurrentRepository->setCurrentText(_settings->value("active-repository").toString());
}

void MainWindow::saveSettings() {
    QStringList repositories;
    for(int i = 0; i < ui->comboBoxCurrentRepository->count(); i++) {
        repositories.append(ui->comboBoxCurrentRepository->itemText(i));
    }
    _settings->setValue("local-repositories", repositories);
    _settings->setValue("active-repository", ui->comboBoxCurrentRepository->currentText());
    _settings->sync();
}

void MainWindow::installPodsFinished(QString repository, QList<Pod> pods, bool success) {
    _availablePodsSpinnerWidget->stop();
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Installing pods in %1").arg(repository),
                                 tr("Finished installing %1 pods.").arg(pods.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Installing pods in %1").arg(repository),
                                 tr("Installing some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::removePodsFinished(QString repository, QStringList podNames, bool success) {
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Removing pods in %1").arg(repository),
                                 tr("Finished removing %1 pods.").arg(podNames.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Removing pods in %1").arg(repository),
                                 tr("Removing some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::updatePodsFinished(QString repository, QStringList podNames, bool success) {
    _localPodsSpinnerWidget->stop();

    ui->tabWidgetPods->setCurrentIndex(0);

    if(success) {
        QMessageBox::information(this,
                                 tr("Updating pods in %1").arg(repository),
                                 tr("Finished updating %1 pods.").arg(podNames.count()));
    } else {
        QMessageBox::information(this,
                                 tr("Updating pods in %1").arg(repository),
                                 tr("Updating some of the selected pods failed."));
    }

    refreshLocalPods();
}

void MainWindow::installedPodsFinished(QString repository, QList<Pod> installedPods) {
    Q_UNUSED(repository);
    _localPods->setModelData(installedPods);
    _localPodsSpinnerWidget->stop();
    ui->tableViewLocal->resizeColumnsToContents();
}

void MainWindow::availablePodsFinished(QStringList sources, QList<Pod> availablePods) {
    Q_UNUSED(sources);
    _remotePods->setModelData(availablePods);
    _availablePodsSpinnerWidget->stop();
    ui->tableViewRemote->resizeColumnsToContents();
}

void MainWindow::refreshLocalPods() {
    _localPodsSpinnerWidget->start();

    QString repository = ui->comboBoxCurrentRepository->currentText();
    if(!_podManager->isGitRepository(repository)) {
        QMessageBox::warning(this,
            tr("Invalid repository"),
            tr("The directory you supplied does not appear to be the root of a valid git repository."));

        int index;
        while((index = ui->comboBoxCurrentRepository->findText(repository)) != -1) {
            ui->comboBoxCurrentRepository->removeItem(index);
        }
        return;
    }

    metaObject()->invokeMethod(_podManager, "installedPods", Q_ARG(QString, repository));
}

void MainWindow::refreshAvailablePods() {
    _availablePodsSpinnerWidget->start();

    QStringList sources = _settings->value("sources").toStringList();
    if(sources.isEmpty()) {
        sources << "http://qt-pods.org/pods.json";
    }

    metaObject()->invokeMethod(_podManager, "availablePods", Q_ARG(QStringList, sources));
}

<|endoftext|>


<commit_before>#pragma once
#ifndef RECT_HPP
#define RECT_HPP
#include <QSharedPointer>
#include "widget.hpp"

namespace qtreports
{
    namespace detail
    {

        class Rect : public Widget {

        public:
            Rect();
            ~Rect();

        private:

        };
        typedef QSharedPointer< Rect > RectPtr;

    }
}

#endif // RECT_HPP<commit_msg>Update rect.hpp<commit_after>#pragma once
#ifndef RECT_HPP
#define RECT_HPP
#include <QSharedPointer>
#include "widget.hpp"

namespace qtreports
{
    namespace detail
    {

        /*! @~russian
        @brief Класс, реализующий тэг <Rect>
        Квадрат (тест)
        Класс, реализующий тэг <Rect>
        */
        class Rect : public Widget {

        public:
            Rect();
            ~Rect();

        private:

        };
        typedef QSharedPointer< Rect > RectPtr;

    }
}

#endif // RECT_HPP
<|endoftext|>


<commit_before>#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <stdlib.h> // for EXIT_ codes

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(pCmdLine);

	return EXIT_SUCCESS;
}<commit_msg>added message loop<commit_after>#ifndef UNICODE
#define UNICODE
#endif

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <stdlib.h> // for EXIT_ codes

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(pCmdLine);

	// handle messages loop
	BOOL bMsgFlag = 0;
	MSG pMsg = { 0 };
	while ((bMsgFlag = ::GetMessageW(&pMsg, NULL, 0, 0)) != 0) {
		if (bMsgFlag > 0) { // GetMessageW returned no error nor processed WM_QUIT message
			::TranslateMessage(&pMsg);
			::DispatchMessageW(&pMsg);
		} else { // handle error returned by GetMessageW
			// TODO: add a helper function to use GetLastError with FormatMessage for retrieve the error string
		}
	}

	return static_cast<int>(pMsg.wParam);
}
<|endoftext|>


<commit_before>#include "SymbolicRegressionSolver.hpp"

#include <algorithm>

#include "Function.hpp"
#include "GeneticOperations.hpp"

SymbolicRegressionSolver::SymbolicRegressionSolver() :
    m_randomEngine{m_randomDevice()}
{
}

SymbolicRegressionSolver::SymbolicRegressionSolver(const Config& config, const PointList& points) :
    m_config(config),
    m_points(points),
    m_randomEngine{m_randomDevice()}
{
}

void SymbolicRegressionSolver::reset()
{
    clearSolutions();
    m_isReset = true;

    m_currentGeneration = 0;
    m_foundSolution = false;

    populate();
}

void SymbolicRegressionSolver::step()
{
    /*
       std::cout << "Best solution for generation " << m_currentGeneration << " is: ";
       std::cout << m_solutions[0].function;
       std::cout << " [fitness = " << m_solutions[0].fitnessLevel << "]\n";
       */
    if(m_solutions[0].fitnessLevel <= m_config.solutionCriterea)
    {
        m_solutions.erase(std::remove_if(m_solutions.begin(),
                    m_solutions.end(),
                    [&](const Solution& sol)
                    {
                    return sol.fitnessLevel > m_config.solutionCriterea;
                    }), m_solutions.end());
        m_foundSolution = true;
        return;
    }

    m_isReset = false;
    m_currentGeneration++;

    m_solutions = performGeneticOperations();

    sort();
}

void SymbolicRegressionSolver::sort()
{
    std::sort(m_solutions.begin(), m_solutions.end(),
            [](const Solution& s1, const Solution& s2) -> bool
            {
            return s1.fitnessLevel < s2.fitnessLevel;
            });
}

SolutionList SymbolicRegressionSolver::solve()
{
    if(!isReset())
    {
        reset();
    }

    m_isRunning = true;

    for(int i = 0; m_isRunning && (i < m_config.maxGenerations && m_solutions.size() != 0) && !m_foundSolution; ++i)
    {
        step();
    }

    return m_foundSolution ? m_solutions : SolutionList();
}

SolutionList SymbolicRegressionSolver::performGeneticOperations()
{
    SolutionList newSolutions;
    newSolutions.reserve(m_solutions.size());

    std::vector<Function*> matingList;
    RandomEngine engine(m_randomDevice());
    std::uniform_real_distribution<> dist(0, 1);

    auto endIt = m_solutions.begin() + (size_t)(m_solutions.size() * m_config.keepPercentage);

    for(auto it = m_solutions.begin(); it != endIt; ++it)
    {
        auto& solution = *it;
#ifdef VERBOSE_LOG
        std::cout << "fn: " << solution.function << '\n';
#endif
        auto randomNumber = dist(engine);
        if(m_config.initialPopulation == 1 || randomNumber <= m_config.mutationPercent)
        {
            performMutation(solution);
            newSolutions.emplace_back(solution);
        }

        if(m_config.initialPopulation == 1)
            continue;

        else if(randomNumber <= m_config.mutationPercent + m_config.matePercent)
        {
            matingList.emplace_back(&solution.function);
            if(matingList.size() == 2)
            {
                Solution sol = createSolution(mate(m_randomEngine, *matingList[0], *matingList[1], [&]()
                            {
                                NodePtr node;
                                std::uniform_int_distribution<> dist(0, 2);
                                if(dist(engine) <= 1)
                                {
                                    node = ::node<ValueNode>(m_config.constantDist(engine));
                                }
                            else
                            {
                                node = ::node<VariableNode>("x");
                            }
                            return node;
                            }, m_config.maxSolutionDepth));
                sol.mated = true;
                newSolutions.emplace_back(sol);

#ifdef VERBOSE_LOG
                std::cout << "mating " << solution.function;
#endif
                matingList.clear();
            }
        }
        else
        {
            newSolutions.emplace_back(solution);
        }
    }

    if(m_config.populationRefillOption == Config::PopulationRefillOption::DUPLICATE)
    {
        for(auto& it = endIt; it != m_solutions.end(); ++it)
        {
            newSolutions.emplace_back(*it);
        }
    }
    else if(m_config.populationRefillOption == Config::PopulationRefillOption::REFILL)
    {
        for(size_t i = 0; i < m_config.initialPopulation - newSolutions.size(); ++i)
        {
            newSolutions.emplace_back(randomlyGenerateSolution());
        }
    }
    else
    {
        // do nothing
    }

    return newSolutions;
}

void SymbolicRegressionSolver::performMutation(Solution& solution)
{
    bool useNearestNeighbour = false;
    if(m_config.nearestNeighbourOption == Config::NearestNeighbourOption::ALWAYS_USE)
    {
        useNearestNeighbour = true;
    }
    else if(m_config.nearestNeighbourOption == Config::NearestNeighbourOption::RANDOM)
    {
        useNearestNeighbour = std::uniform_real_distribution<>{0, m_config.chanceToUseNearestNeighbour}(m_randomEngine) <= m_config.chanceToUseNearestNeighbour;
    }

    mutate(m_randomEngine, solution.function, m_config.constantDist, m_config.chanceToChangeVar, m_config.chanceToChangeConstant, useNearestNeighbour, m_config.stepSize);
    solution.fitnessLevel = fitness(solution.function);
    solution.mutated = true;

#ifdef VERBOSE_LOG
    std::cout << "mutated: " << solution.function << '\n';
#endif // VERBOSE_LOG
}

void SymbolicRegressionSolver::populate()
{
    for(int i = 0; i < m_config.initialPopulation; ++i)
    {
        m_solutions.emplace_back(randomlyGenerateSolution());
    }
    sort();
}

void SymbolicRegressionSolver::updateFitnesses()
{
    for(auto& solution : m_solutions)
    {
        solution.fitnessLevel = fitness(solution.function);
    }
}

size_t SymbolicRegressionSolver::fitness(Function& fn) const
{
    size_t result = 0;
    for(auto& p : m_points)
    {
        result += abs(p.y - fn(p.x));
    }
    return result;
}

Solution SymbolicRegressionSolver::createSolution(Function fn) const
{
    auto fit = fitness(fn);
    return Solution{std::move(fn), fit};
}

NodePtr generate(RandomEngine& engine, int depth, std::uniform_int_distribution<>& constantDist)
{
    static std::uniform_int_distribution<> binaryDist(0, 1);
    static std::uniform_int_distribution<> operatorDist(0, (int)Operator::COUNT - 1);
    Node::Type typeToGenerate = Node::Type::OPERATION;
    if(depth == 0)
    {
        typeToGenerate = binaryDist(engine) == 0 ? Node::Type::VALUE : Node::Type::VARIABLE;
    }

    switch(typeToGenerate)
    {
        case Node::Type::VARIABLE:
            return node<VariableNode>("x");
        case Node::Type::VALUE:
            return node<ValueNode>(constantDist(engine));
        case Node::Type::OPERATION:
            {
                return node<OperatorNode>(static_cast<Operator>(operatorDist(engine)),
                        std::move(generate(engine, depth - 1, constantDist)),
                        std::move(generate(engine, depth - 1, constantDist)));
            }
        default:
            std::cerr << "Invalid type generated\n";
            std::exit(1);
            return nullptr;
            break;
    }
}

Solution SymbolicRegressionSolver::randomlyGenerateSolution()
{
    std::uniform_int_distribution<> dist(1, m_config.initialMaxDepth);
    const int depthToGenerate = dist(m_randomEngine);

    return createSolution(std::move(Function{generate(m_randomEngine, depthToGenerate, m_config.constantDist)}));
}
<commit_msg>Indentation<commit_after>#include "SymbolicRegressionSolver.hpp"

#include <algorithm>

#include "Function.hpp"
#include "GeneticOperations.hpp"

SymbolicRegressionSolver::SymbolicRegressionSolver() :
    m_randomEngine{m_randomDevice()}
{
}

SymbolicRegressionSolver::SymbolicRegressionSolver(const Config& config, const PointList& points) :
    m_config(config),
    m_points(points),
    m_randomEngine{m_randomDevice()}
{
}

void SymbolicRegressionSolver::reset()
{
    clearSolutions();
    m_isReset = true;

    m_currentGeneration = 0;
    m_foundSolution = false;

    populate();
}

void SymbolicRegressionSolver::step()
{
    /*
       std::cout << "Best solution for generation " << m_currentGeneration << " is: ";
       std::cout << m_solutions[0].function;
       std::cout << " [fitness = " << m_solutions[0].fitnessLevel << "]\n";
       */
    if(m_solutions[0].fitnessLevel <= m_config.solutionCriterea)
    {
        m_solutions.erase(std::remove_if(m_solutions.begin(),
                    m_solutions.end(),
                    [&](const Solution& sol)
                    {
                        return sol.fitnessLevel > m_config.solutionCriterea;
                    }), m_solutions.end());
        m_foundSolution = true;
        return;
    }

    m_isReset = false;
    m_currentGeneration++;

    m_solutions = performGeneticOperations();

    sort();
}

void SymbolicRegressionSolver::sort()
{
    std::sort(m_solutions.begin(), m_solutions.end(),
            [](const Solution& s1, const Solution& s2)
            {
                return s1.fitnessLevel < s2.fitnessLevel;
            });
}

SolutionList SymbolicRegressionSolver::solve()
{
    if(!isReset())
    {
        reset();
    }

    m_isRunning = true;

    for(int i = 0; m_isRunning && (i < m_config.maxGenerations && m_solutions.size() != 0) && !m_foundSolution; ++i)
    {
        step();
    }

    return m_foundSolution ? m_solutions : SolutionList();
}

SolutionList SymbolicRegressionSolver::performGeneticOperations()
{
    SolutionList newSolutions;
    newSolutions.reserve(m_solutions.size());

    std::vector<Function*> matingList;
    RandomEngine engine(m_randomDevice());
    std::uniform_real_distribution<> dist(0, 1);

    auto endIt = m_solutions.begin() + (size_t)(m_solutions.size() * m_config.keepPercentage);

    for(auto it = m_solutions.begin(); it != endIt; ++it)
    {
        auto& solution = *it;
#ifdef VERBOSE_LOG
        std::cout << "fn: " << solution.function << '\n';
#endif
        auto randomNumber = dist(engine);
        if(m_config.initialPopulation == 1 || randomNumber <= m_config.mutationPercent)
        {
            performMutation(solution);
            newSolutions.emplace_back(solution);
        }

        if(m_config.initialPopulation == 1)
            continue;

        else if(randomNumber <= m_config.mutationPercent + m_config.matePercent)
        {
            matingList.emplace_back(&solution.function);
            if(matingList.size() == 2)
            {
                Solution sol = createSolution(mate(m_randomEngine, *matingList[0], *matingList[1], [&]()
                            {
                                NodePtr node;
                                std::uniform_int_distribution<> dist(0, 2);
                                if(dist(engine) <= 1)
                                {
                                    node = ::node<ValueNode>(m_config.constantDist(engine));
                                }
                            else
                            {
                                node = ::node<VariableNode>("x");
                            }
                            return node;
                            }, m_config.maxSolutionDepth));
                sol.mated = true;
                newSolutions.emplace_back(sol);

#ifdef VERBOSE_LOG
                std::cout << "mating " << solution.function;
#endif
                matingList.clear();
            }
        }
        else
        {
            newSolutions.emplace_back(solution);
        }
    }

    if(m_config.populationRefillOption == Config::PopulationRefillOption::DUPLICATE)
    {
        for(auto& it = endIt; it != m_solutions.end(); ++it)
        {
            newSolutions.emplace_back(*it);
        }
    }
    else if(m_config.populationRefillOption == Config::PopulationRefillOption::REFILL)
    {
        for(size_t i = 0; i < m_config.initialPopulation - newSolutions.size(); ++i)
        {
            newSolutions.emplace_back(randomlyGenerateSolution());
        }
    }
    else
    {
        // do nothing
    }

    return newSolutions;
}

void SymbolicRegressionSolver::performMutation(Solution& solution)
{
    bool useNearestNeighbour = false;
    if(m_config.nearestNeighbourOption == Config::NearestNeighbourOption::ALWAYS_USE)
    {
        useNearestNeighbour = true;
    }
    else if(m_config.nearestNeighbourOption == Config::NearestNeighbourOption::RANDOM)
    {
        useNearestNeighbour = std::uniform_real_distribution<>{0, m_config.chanceToUseNearestNeighbour}(m_randomEngine) <= m_config.chanceToUseNearestNeighbour;
    }

    mutate(m_randomEngine, solution.function, m_config.constantDist, m_config.chanceToChangeVar, m_config.chanceToChangeConstant, useNearestNeighbour, m_config.stepSize);
    solution.fitnessLevel = fitness(solution.function);
    solution.mutated = true;

#ifdef VERBOSE_LOG
    std::cout << "mutated: " << solution.function << '\n';
#endif // VERBOSE_LOG
}

void SymbolicRegressionSolver::populate()
{
    for(int i = 0; i < m_config.initialPopulation; ++i)
    {
        m_solutions.emplace_back(randomlyGenerateSolution());
    }
    sort();
}

void SymbolicRegressionSolver::updateFitnesses()
{
    for(auto& solution : m_solutions)
    {
        solution.fitnessLevel = fitness(solution.function);
    }
}

size_t SymbolicRegressionSolver::fitness(Function& fn) const
{
    size_t result = 0;
    for(auto& p : m_points)
    {
        result += abs(p.y - fn(p.x));
    }
    return result;
}

Solution SymbolicRegressionSolver::createSolution(Function fn) const
{
    auto fit = fitness(fn);
    return Solution{std::move(fn), fit};
}

NodePtr generate(RandomEngine& engine, int depth, std::uniform_int_distribution<>& constantDist)
{
    static std::uniform_int_distribution<> binaryDist(0, 1);
    static std::uniform_int_distribution<> operatorDist(0, (int)Operator::COUNT - 1);
    Node::Type typeToGenerate = Node::Type::OPERATION;
    if(depth == 0)
    {
        typeToGenerate = binaryDist(engine) == 0 ? Node::Type::VALUE : Node::Type::VARIABLE;
    }

    switch(typeToGenerate)
    {
        case Node::Type::VARIABLE:
            return node<VariableNode>("x");
        case Node::Type::VALUE:
            return node<ValueNode>(constantDist(engine));
        case Node::Type::OPERATION:
            {
                return node<OperatorNode>(static_cast<Operator>(operatorDist(engine)),
                        std::move(generate(engine, depth - 1, constantDist)),
                        std::move(generate(engine, depth - 1, constantDist)));
            }
        default:
            std::cerr << "Invalid type generated\n";
            std::exit(1);
            return nullptr;
            break;
    }
}

Solution SymbolicRegressionSolver::randomlyGenerateSolution()
{
    std::uniform_int_distribution<> dist(1, m_config.initialMaxDepth);
    const int depthToGenerate = dist(m_randomEngine);

    return createSolution(std::move(Function{generate(m_randomEngine, depthToGenerate, m_config.constantDist)}));
}
<|endoftext|>


<commit_before>/*
Copyright 2014, Jernej Kovacic

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @file
 * @author Jernej Kovacic
 *
 * Implementation of the class SampleStatGeneric that calculates sample's
 * mean, variance, standard deviation, sum, etc.
 *
 * As the class is templated, this file must not be compiled.
 * Instead it must be included after the class declaration in the .h file
 */

// deliberately there is no #include "SampleStatGeneric.hpp" !
#include <cstddef>
#include <cmath>
#include <vector>

#include "omp/omp_header.h"
#include "../settings/omp_settings.h"

#include "exception/StatisticsException.hpp"
#include "util/NumericUtil.hpp"

/**
 * @param x - vector of sample elements
 *
 * @return sum of all sample values
 */
template <class T>
T math::SampleStatGeneric<T>::sum(const std::vector<T>& x)
{
    const size_t N = x.size();

    if ( 0 == N )
    {
        return math::NumericUtil<T>::ZERO;
    }

    /*
     * Coarse grained parallelism will be applied, i.e. each thread will be
     * assigned an (approximately) equally sized contiguous block of data
     * to be processed.
     */

    // Ideal number of threads
    // (if each one processes approx. OMP_CHUNKS_PER_THREAD items):
    const size_t ideal = N / OMP_CHUNKS_PER_THREAD +
                         ( 0 == N % OMP_CHUNKS_PER_THREAD ? 0 : 1 );

    /*
     * In the first step, each thread calculates the sum of its block
     */
    T sum = math::NumericUtil<T>::ZERO;
    #pragma omp parallel num_threads(ideal) \
                    if(N>OMP_CHUNKS_PER_THREAD) \
                    default(none) shared(x) \
                    reduction(+ : sum)
    {
        // Depending on the number of available threads,
        // determine the ideal nr.of samples per thread,
        // and start and sample of a block that each thread will process.
        const size_t thrnr = omp_get_thread_num();
        const size_t nthreads = omp_get_num_threads();

        const size_t samples_per_thread = (N + nthreads -1) / nthreads;
        const size_t istart = samples_per_thread * thrnr;
        const size_t iend = istart + samples_per_thread;

        // Calculate the sum of the assigned block...
        T partsum = math::NumericUtil<T>::ZERO;
        for ( size_t i=istart; i<iend && i<N; ++i )
        {
            partsum += x.at(i);
        }

        // ... and add it to the total sum in a thread safe manner.
        sum += partsum;
    }

    return sum;

    // In serial mode this variable is never used.
    (void) ideal;
}

/**
 * Arithmetical mean (or average) of the sample.
 *
 * @param x - vector of sample elements
 *
 * @return mean value of the sample
 *
 * @throw StatisticsException if 'x' is empty
 */
template <class T>
T math::SampleStatGeneric<T>::mean(const std::vector<T>& x) throw(math::StatisticsException)
{
    /*
     * Arithmetical mean is calculated as:
     *
     *                   N
     *                 -----
     *             1   \
     * mean(X) = -----  >  X[i]
     *             N   /
     *                 -----
     *                  i=1
     *
     */

    const size_t N = x.size();

    if ( 0 == N )
    {
        throw math::StatisticsException(math::StatisticsException::SAMPLE_EMPTY);
    }

    return sum(x) / static_cast<T>(N);
}

/**
 * Variance of the sample.
 * The method allows the divisor (sample size) to be reduced by an arbitrary
 * positive integer number 'df_sub' as long as it is strictly smaller than
 * the sample size.
 *
 * @param x - vector of sample elements
 * @param df_sub - generalized Bessel's correction value (typically 1 or 0)
 *
 * @return variance of the sample, depending on the given 'df_sub'
 *
 * @throw StatisticsException if 'x' is empty or if 'df_sub' exceeds sample's size
 */
template <class T>
T math::SampleStatGeneric<T>::var(const std::vector<T>& x, size_t df_sub) throw(math::StatisticsException)
{
    /*
     * The best known algorithm to calculate a variance is:
     *
     *                              N
     *                            -----
     *                  1         \                 2
     *   var(X) = ------------     >   (X[i] - mean)
     *             N - df_sub     /
     *                            -----
     *                             i=1
     *
     * It is called a two step algorithm as it must calculate the sample mean
     * in the first step, followed by the algorithm above to calculate the variance.
     *
     * When the mean is not required, the expression above can be replaced by the
     * one step algorithm:
     *
     *
     *               N                       /   N           \  2
     *             -----                     | -----         |
     *             \              2      1   | \             |
     *              >     (X[i]-K)   -  ---  |  >   (X[i]-K) |
     *             /                     N   | /             |
     *             -----                     | -----         |
     *              i=1                      \  i=1          /
     *   var(X) = --------------------------------------------------
     *                               N - df_sub
     *
     * Where K may be an arbitrary value. Typically it is recommended to
     * not equal 0 to avoid the catastrophic cancellation (both terms may be
     * of very similar). Typically it can be assigned any element's value,
     * ideally close to the sample's mean.
     *
     * For more details, see:
     * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
     */

    const size_t N = x.size();

    if ( 0 == N )
    {
        throw math::StatisticsException(math::StatisticsException::SAMPLE_EMPTY);
    }

    if ( df_sub >= N )
    {
        throw math::StatisticsException(math::StatisticsException::DF_SUBTRAHEND_TOO_LARGE);
    }

    // Let K be equal to the first element:
    const T K = x.at(0);

    T sum  = math::NumericUtil<T>::ZERO;
    T sum2 = math::NumericUtil<T>::ZERO;

    /*
     * Coarse grained parallelism will be applied, i.e. each thread will be
     * assigned an (approximately) equally sized contiguous block of data
     * to be processed.
     */

    // Ideal number of threads
    // (if each one processes approx. OMP_CHUNKS_PER_THREAD items):
    const size_t ideal = N / OMP_CHUNKS_PER_THREAD +
                         ( 0 == N % OMP_CHUNKS_PER_THREAD ? 0 : 1 );

    #pragma omp parallel num_threads(ideal) \
                if(N>OMP_CHUNKS_PER_THREAD) \
                default(none) shared(x) \
                reduction(+ : sum, sum2)
    {
    	// Depending on the number of available threads,
        // determine the ideal nr.of samples per thread,
        // and start and sample of a block that each thread will process.
        const size_t thrnr = omp_get_thread_num();
        const size_t nthreads = omp_get_num_threads();

        const size_t samples_per_thread = (N + nthreads -1) / nthreads;
        const size_t istart = samples_per_thread * thrnr;
        const size_t iend = istart + samples_per_thread;

        // Calculate both sums of the assigned block...
        T partsum  = math::NumericUtil<T>::ZERO;
        T partsum2 = math::NumericUtil<T>::ZERO;
        for ( size_t i=istart; i<iend && i<N; ++i )
        {
            const T diff = x.at(i) - K;
            partsum  += diff;
            partsum2 += diff * diff;
        }

        // ... and add them to the total sums in a thread safe manner.
        sum  += partsum;
        sum2 += partsum2;
    }

    return (sum2 - (sum*sum)/static_cast<T>(N)) / static_cast<T>(N - df_sub);

    // In serial mode this variable is never used.
    (void) ideal;
}

/**
 * Variance of the sample.
 * Calculates either variance of a sample (sum of square deviations from
 * the mean is divided by N-1) or of a population (sum of squares divided by N).
 *
 * @param x - vector of sample elements
 * @param sample - if 'true', sum of squared deviations is divided by (N-1), otherwise by N
 *
 * @return variance of the sample depending on 'sample'
 *
 * @throw StatisticsException if the sample is empty or too small
 */
template <class T>
T math::SampleStatGeneric<T>::var(const std::vector<T>& x, bool sample) throw(math::StatisticsException)
{
    return var( x, static_cast<size_t>( (false==sample ? 0 : 1) ) );
}

/**
 * Standard deviation of the sample.
 * Calculates either standard deviation of a sample (sum of square deviations from
 * the mean is divided by N-1) or of a population (sum of squares divided by N).
 *
 * @param x - vector of sample elements
 * @param sample - if 'true', sum of squared deviations is divided by (N-1), otherwise by N
 *
 * @return standard deviation of the sample depending on 'sample'
 *
 * @throw StatisticsException if the sample is empty or too small
 */
template <class T>
T math::SampleStatGeneric<T>::stdev(const std::vector<T>& x, bool sample) throw(math::StatisticsException)
{
    return stdev( x, static_cast<size_t>( (false==sample ? 0 : 1) ) );
}

/**
 * Standard deviation of the sample.
 * The method allows the divisor (sample size) to be reduced by an arbitrary
 * positive integer number 'df_sub' as long as it is strictly smaller than
 * the sample size.
 *
 * @param x - vector of sample elements
 * @param df_sub - generalized Bessel's correction value (typically 1 or 0)
 *
 * @return standard deviation of the sample, depending on the given 'df_sub'
 *
 * @throw StatisticsException if 'x' is empty or 'df_sub' exceeds sample's size
 */
template <class T>
T math::SampleStatGeneric<T>::stdev(const std::vector<T>& x, size_t df_sub) throw(math::StatisticsException)
{
    /*
     * Standard deviation is calculated as square root
     * of the variance.
     */

    /*
     *  This operation is only supported for T=float, T=double or T=long double.
     *  Specialized implementation is provided below for these three types.
     *
     *  For any other type, the operation is (probably) not supported
     *  as sqrt may not be defined for the type. In such a case throw an
     *  exception immediately.
     */

    throw math::StatisticsException(math::StatisticsException::UNSUPPORTED_TYPE);

    // will never execute, but some compilers may produce a warning if nothing is returned
    return math::NumericUtil<T>::ZERO;
}

/*
 * Specialization of stdev() for float, double and long double.
 * All three specializations are very similar and only differ in types of the
 * returned  value.
 * For easier maintainability, the specialization will be implemented
 * only once using a parameterized #define.
 */

#define _MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(FD) \
template<> \
FD math::SampleStatGeneric<FD>::stdev(const std::vector<FD>& x, size_t df_sub) throw (math::StatisticsException) \
{ \
    return std::sqrt( var(x, df_sub) ); \
}
// end of #define

// the actual specialization for float:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(float)

// for double:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(double)

// and for long double:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(long double)

// definition of _MATH_QUATERNIONGENERIC_SPECIALIZED_NORM not needed anymore, #undef it
#undef _MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV
<commit_msg>iterators in stat. algorithms<commit_after>/*
Copyright 2014, Jernej Kovacic

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @file
 * @author Jernej Kovacic
 *
 * Implementation of the class SampleStatGeneric that calculates sample's
 * mean, variance, standard deviation, sum, etc.
 *
 * As the class is templated, this file must not be compiled.
 * Instead it must be included after the class declaration in the .h file
 */

// deliberately there is no #include "SampleStatGeneric.hpp" !
#include <cstddef>
#include <cmath>
#include <vector>

#include "omp/omp_header.h"
#include "../settings/omp_settings.h"

#include "exception/StatisticsException.hpp"
#include "util/NumericUtil.hpp"

/**
 * @param x - vector of sample elements
 *
 * @return sum of all sample values
 */
template <class T>
T math::SampleStatGeneric<T>::sum(const std::vector<T>& x)
{
    const size_t N = x.size();

    if ( 0 == N )
    {
        return math::NumericUtil<T>::ZERO;
    }

    /*
     * Coarse grained parallelism will be applied, i.e. each thread will be
     * assigned an (approximately) equally sized contiguous block of data
     * to be processed.
     */

    // Ideal number of threads
    // (if each one processes approx. OMP_CHUNKS_PER_THREAD items):
    const size_t ideal = N / OMP_CHUNKS_PER_THREAD +
                         ( 0 == N % OMP_CHUNKS_PER_THREAD ? 0 : 1 );

    /*
     * In the first step, each thread calculates the sum of its block
     */
    T sum = math::NumericUtil<T>::ZERO;
    #pragma omp parallel num_threads(ideal) \
                    if(N>OMP_CHUNKS_PER_THREAD) \
                    default(none) shared(x) \
                    reduction(+ : sum)
    {
        // Depending on the number of available threads,
        // determine the ideal nr.of samples per thread,
        // and start and sample of a block that each thread will process.
        const size_t thrnr = omp_get_thread_num();
        const size_t nthreads = omp_get_num_threads();

        const size_t samples_per_thread = (N + nthreads - 1) / nthreads;
        const size_t istart = samples_per_thread * thrnr;

        // Calculate the sum of the assigned block...
        T partsum = math::NumericUtil<T>::ZERO;
        size_t cntr = 0;
        for ( typename std::vector<T>::const_iterator it = x.begin() + istart;
              cntr<samples_per_thread && it!=x.end(); ++it, ++cntr )
        {
            partsum += *it;
        }

        // ... and add it to the total sum in a thread safe manner.
        sum += partsum;
    }

    return sum;

    // In serial mode this variable is never used.
    (void) ideal;
}

/**
 * Arithmetical mean (or average) of the sample.
 *
 * @param x - vector of sample elements
 *
 * @return mean value of the sample
 *
 * @throw StatisticsException if 'x' is empty
 */
template <class T>
T math::SampleStatGeneric<T>::mean(const std::vector<T>& x) throw(math::StatisticsException)
{
    /*
     * Arithmetical mean is calculated as:
     *
     *                   N
     *                 -----
     *             1   \
     * mean(X) = -----  >  X[i]
     *             N   /
     *                 -----
     *                  i=1
     *
     */

    const size_t N = x.size();

    if ( 0 == N )
    {
        throw math::StatisticsException(math::StatisticsException::SAMPLE_EMPTY);
    }

    return sum(x) / static_cast<T>(N);
}

/**
 * Variance of the sample.
 * The method allows the divisor (sample size) to be reduced by an arbitrary
 * positive integer number 'df_sub' as long as it is strictly smaller than
 * the sample size.
 *
 * @param x - vector of sample elements
 * @param df_sub - generalized Bessel's correction value (typically 1 or 0)
 *
 * @return variance of the sample, depending on the given 'df_sub'
 *
 * @throw StatisticsException if 'x' is empty or if 'df_sub' exceeds sample's size
 */
template <class T>
T math::SampleStatGeneric<T>::var(const std::vector<T>& x, size_t df_sub) throw(math::StatisticsException)
{
    /*
     * The best known algorithm to calculate a variance is:
     *
     *                              N
     *                            -----
     *                  1         \                 2
     *   var(X) = ------------     >   (X[i] - mean)
     *             N - df_sub     /
     *                            -----
     *                             i=1
     *
     * It is called a two step algorithm as it must calculate the sample mean
     * in the first step, followed by the algorithm above to calculate the variance.
     *
     * When the mean is not required, the expression above can be replaced by the
     * one step algorithm:
     *
     *
     *               N                       /   N           \  2
     *             -----                     | -----         |
     *             \              2      1   | \             |
     *              >     (X[i]-K)   -  ---  |  >   (X[i]-K) |
     *             /                     N   | /             |
     *             -----                     | -----         |
     *              i=1                      \  i=1          /
     *   var(X) = --------------------------------------------------
     *                               N - df_sub
     *
     * Where K may be an arbitrary value. Typically it is recommended to
     * not equal 0 to avoid the catastrophic cancellation (both terms may be
     * of very similar). Typically it can be assigned any element's value,
     * ideally close to the sample's mean.
     *
     * For more details, see:
     * https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
     */

    const size_t N = x.size();

    if ( 0 == N )
    {
        throw math::StatisticsException(math::StatisticsException::SAMPLE_EMPTY);
    }

    if ( df_sub >= N )
    {
        throw math::StatisticsException(math::StatisticsException::DF_SUBTRAHEND_TOO_LARGE);
    }

    // Let K be equal to the first element:
    const T K = x.at(0);

    T sum  = math::NumericUtil<T>::ZERO;
    T sum2 = math::NumericUtil<T>::ZERO;

    /*
     * Coarse grained parallelism will be applied, i.e. each thread will be
     * assigned an (approximately) equally sized contiguous block of data
     * to be processed.
     */

    // Ideal number of threads
    // (if each one processes approx. OMP_CHUNKS_PER_THREAD items):
    const size_t ideal = N / OMP_CHUNKS_PER_THREAD +
                         ( 0 == N % OMP_CHUNKS_PER_THREAD ? 0 : 1 );

    #pragma omp parallel num_threads(ideal) \
                if(N>OMP_CHUNKS_PER_THREAD) \
                default(none) shared(x) \
                reduction(+ : sum, sum2)
    {
    	// Depending on the number of available threads,
        // determine the ideal nr.of samples per thread,
        // and start and sample of a block that each thread will process.
        const size_t thrnr = omp_get_thread_num();
        const size_t nthreads = omp_get_num_threads();

        const size_t samples_per_thread = (N + nthreads - 1) / nthreads;
        const size_t istart = samples_per_thread * thrnr;

        // Calculate both sums of the assigned block...
        T partsum  = math::NumericUtil<T>::ZERO;
        T partsum2 = math::NumericUtil<T>::ZERO;
        size_t cntr = 0;
        for ( typename std::vector<T>::const_iterator it = x.begin() + istart;
              cntr<samples_per_thread && it!=x.end(); ++it, ++cntr )
        {
            const T diff = *it - K;
            partsum  += diff;
            partsum2 += diff * diff;
        }

        // ... and add them to the total sums in a thread safe manner.
        sum  += partsum;
        sum2 += partsum2;
    }

    return (sum2 - (sum*sum)/static_cast<T>(N)) / static_cast<T>(N - df_sub);

    // In serial mode this variable is never used.
    (void) ideal;
}

/**
 * Variance of the sample.
 * Calculates either variance of a sample (sum of square deviations from
 * the mean is divided by N-1) or of a population (sum of squares divided by N).
 *
 * @param x - vector of sample elements
 * @param sample - if 'true', sum of squared deviations is divided by (N-1), otherwise by N
 *
 * @return variance of the sample depending on 'sample'
 *
 * @throw StatisticsException if the sample is empty or too small
 */
template <class T>
T math::SampleStatGeneric<T>::var(const std::vector<T>& x, bool sample) throw(math::StatisticsException)
{
    return var( x, static_cast<size_t>( (false==sample ? 0 : 1) ) );
}

/**
 * Standard deviation of the sample.
 * Calculates either standard deviation of a sample (sum of square deviations from
 * the mean is divided by N-1) or of a population (sum of squares divided by N).
 *
 * @param x - vector of sample elements
 * @param sample - if 'true', sum of squared deviations is divided by (N-1), otherwise by N
 *
 * @return standard deviation of the sample depending on 'sample'
 *
 * @throw StatisticsException if the sample is empty or too small
 */
template <class T>
T math::SampleStatGeneric<T>::stdev(const std::vector<T>& x, bool sample) throw(math::StatisticsException)
{
    return stdev( x, static_cast<size_t>( (false==sample ? 0 : 1) ) );
}

/**
 * Standard deviation of the sample.
 * The method allows the divisor (sample size) to be reduced by an arbitrary
 * positive integer number 'df_sub' as long as it is strictly smaller than
 * the sample size.
 *
 * @param x - vector of sample elements
 * @param df_sub - generalized Bessel's correction value (typically 1 or 0)
 *
 * @return standard deviation of the sample, depending on the given 'df_sub'
 *
 * @throw StatisticsException if 'x' is empty or 'df_sub' exceeds sample's size
 */
template <class T>
T math::SampleStatGeneric<T>::stdev(const std::vector<T>& x, size_t df_sub) throw(math::StatisticsException)
{
    /*
     * Standard deviation is calculated as square root
     * of the variance.
     */

    /*
     *  This operation is only supported for T=float, T=double or T=long double.
     *  Specialized implementation is provided below for these three types.
     *
     *  For any other type, the operation is (probably) not supported
     *  as sqrt may not be defined for the type. In such a case throw an
     *  exception immediately.
     */

    throw math::StatisticsException(math::StatisticsException::UNSUPPORTED_TYPE);

    // will never execute, but some compilers may produce a warning if nothing is returned
    return math::NumericUtil<T>::ZERO;
}

/*
 * Specialization of stdev() for float, double and long double.
 * All three specializations are very similar and only differ in types of the
 * returned  value.
 * For easier maintainability, the specialization will be implemented
 * only once using a parameterized #define.
 */

#define _MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(FD) \
template<> \
FD math::SampleStatGeneric<FD>::stdev(const std::vector<FD>& x, size_t df_sub) throw (math::StatisticsException) \
{ \
    return std::sqrt( var(x, df_sub) ); \
}
// end of #define

// the actual specialization for float:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(float)

// for double:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(double)

// and for long double:
_MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV(long double)

// definition of _MATH_QUATERNIONGENERIC_SPECIALIZED_NORM not needed anymore, #undef it
#undef _MATH_SAMPLESTATGENERIC_SPECIALIZED_STDEV
<|endoftext|>


<commit_before>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2015 Scientific Computing and Imaging Institute,
   University of Utah.

   License for the specific language governing rights and limitations under
   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Testing/ModuleTestBase/ModuleTestBase.h>
#include <Modules/Factory/HardCodedModuleFactory.h>
#include <Core/Algorithms/Factory/HardCodedAlgorithmFactory.h>
#include <Dataflow/Engine/Controller/NetworkEditorController.h>
#include <Dataflow/Network/ConnectionId.h>
#include <Modules/Legacy/Fields/CreateLatVol.h>
#include <Modules/Legacy/Math/SolveMinNormLeastSqSystem.h>
#include <Modules/Legacy/Fields/GetMeshQualityField.h>
#include <Modules/Legacy/Fields/GetFieldData.h>

using namespace SCIRun;
using namespace SCIRun::Modules;
using namespace Testing;
using namespace Modules::Factory;
using namespace Dataflow::Networks;
using namespace ReplacementImpl;
using namespace Dataflow::Engine;
using namespace Core::Algorithms;

class ModuleReplaceTests : public ModuleTest
{
};

#ifdef BUILD_TESTING
const int NUM_MODULES = 150;
#else
const int NUM_MODULES = 143;
#endif
const int NUM_ALGORITHMS = 76;

const int EXPECTED_RANGE = 5;   // Require updating these numbers every few modules

TEST(HardCodedModuleFactoryTests, ListAllModules)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  std::cout << "descMap size: " << descMap.size() << std::endl;
  EXPECT_GE(descMap.size(), NUM_MODULES);
  EXPECT_LE(descMap.size(), NUM_MODULES + EXPECTED_RANGE);

  // for (const auto& m : descMap)
  // {
  //   std::cout << m.first << " -> " << m.second << std::endl;
  // }
}

TEST(HardCodedModuleFactoryTests, ListAllAlgorithms)
{
  HardCodedAlgorithmFactory factory;

  std::cout << "algorithm factory size: " << factory.numAlgorithms() << std::endl;
  EXPECT_GE(factory.numAlgorithms(), NUM_ALGORITHMS);
  EXPECT_LE(factory.numAlgorithms(), NUM_ALGORITHMS + EXPECTED_RANGE);

  // for (const auto& a : factory)
  // {
  //   std::cout << a.first << " -> " << a.second.first << std::endl;
  // }
}

TEST(HardCodedModuleFactoryTests, ModuleTraitHasAlgorithmMatchesAlgoFactory)
{
  HardCodedModuleFactory moduleFactory;

  auto modules = moduleFactory.getDirectModuleDescriptionLookupMap();

  HardCodedAlgorithmFactory algoFactory;

  std::set<std::string> modulesWithAlgorithms;

  for (const auto& a : algoFactory)
  {
    auto moduleName = a.first;
    auto modFactIter = std::find_if(modules.cbegin(), modules.cend(),
      [&moduleName](const DirectModuleDescriptionLookupMap::value_type& p) { return p.first.module_name_ == moduleName; });
    if (modFactIter != modules.end())
    {
      if (!modFactIter->second.hasAlgo_)
        std::cout << moduleName << " is missing trait HasAlgorithm" << std::endl;
      EXPECT_TRUE(modFactIter->second.hasAlgo_);
      modulesWithAlgorithms.insert(moduleName);
    }
    else
      FAIL() << "Module found in algorithm factory but not module factory: " << moduleName;
  }

  for (const auto& m : modules)
  {
    if (modulesWithAlgorithms.find(m.first.module_name_) != modulesWithAlgorithms.end())
    {
      if (!m.second.hasAlgo_)
        std::cout << m.first.module_name_ << " is missing trait HasAlgorithm" << std::endl;
      EXPECT_TRUE(m.second.hasAlgo_);
    }
    else
    {
      if (m.second.hasAlgo_)
        std::cout << m.first.module_name_ << " has trait HasAlgorithm, when it should not" << std::endl;
      EXPECT_FALSE(m.second.hasAlgo_);
    }
  }
}

TEST(ModuleTraitsTest, CanDetermineUIAlgoStatically)
{
  ASSERT_TRUE(HasUI<Fields::CreateLatVol>::value);
  ASSERT_FALSE(HasAlgorithm<Fields::CreateLatVol>::value);

  ASSERT_FALSE(HasUI<Fields::GetFieldData>::value);
  ASSERT_TRUE(HasAlgorithm<Fields::GetFieldData>::value);

  ASSERT_TRUE(HasUI<Fields::GetMeshQualityField>::value);
  ASSERT_TRUE(HasAlgorithm<Fields::GetMeshQualityField>::value);

  ASSERT_FALSE(HasUI<Math::SolveMinNormLeastSqSystem>::value);
  ASSERT_FALSE(HasAlgorithm<Math::SolveMinNormLeastSqSystem>::value);
}

TEST_F(ModuleReplaceTests, CanComputeConnectedPortInfoFromModule)
{
  ModuleFactoryHandle mf(new HardCodedModuleFactory);
  NetworkEditorController controller(mf, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
  initModuleParameters(false);

  auto network = controller.getNetwork();

  ModuleHandle send = controller.addModule("CreateMatrix");
  ModuleHandle process = controller.addModule("NeedToExecuteTester");
  ModuleHandle receive = controller.addModule("ReportMatrixInfo");

  ASSERT_EQ(3, network->nmodules());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo; // empty maps
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo; // empty maps
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  auto cid1 = network->connect(ConnectionOutputPort(send, 0), ConnectionInputPort(process, 0));
  auto cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(receive, 0));
  ASSERT_EQ(2, network->nconnections());

  //clang on mac doesn't like "{}" for empty std::map initialization in a struct.
  const ConnectedPortTypesWithCount empty;

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo { empty, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo { { { "Matrix", 1 } }, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo { { { "Matrix", 1 } }, empty };
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  network->disconnect(cid1);
  ASSERT_EQ(1, network->nconnections());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo { empty, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo { { { "Matrix", 1 } }, empty };
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  network->disconnect(cid2);
  ASSERT_EQ(0, network->nconnections());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo; // empty maps
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo; // empty maps
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  ModuleHandle create = controller.addModule("CreateLatVol");
  ModuleHandle setFD = controller.addModule("SetFieldData");
  ModuleHandle report = controller.addModule("ReportFieldInfo");

  cid1 = network->connect(ConnectionOutputPort(create, 0), ConnectionInputPort(setFD, 0));
  cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(setFD, 1));
  auto cid3 = network->connect(ConnectionOutputPort(setFD, 0), ConnectionInputPort(report, 0));
  ASSERT_EQ(3, network->nconnections());

  {
    auto createInfo = makeConnectedPortInfo(create);
    ConnectedPortInfo expectedCreateInfo { empty, { { "Field", 1 } } };
    EXPECT_EQ(expectedCreateInfo, createInfo);
    auto setInfo = makeConnectedPortInfo(setFD);
    ConnectedPortInfo expectedSetInfo { { { "Matrix", 1 }, { "Field", 1 } }, { { "Field", 1 } } };
    EXPECT_EQ(expectedSetInfo, setInfo);
    auto reportInfo = makeConnectedPortInfo(report);
    ConnectedPortInfo expectedReportInfo { { { "Field", 1 } }, empty };
    EXPECT_EQ(expectedReportInfo, reportInfo);
  }
}

TEST_F(ModuleReplaceTests, NoConnectedPortsCanBeReplacedWithAnything)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ConnectedPortInfo noConnections;
  auto noConnectionReplacements = filter->findReplacements(noConnections);
  EXPECT_EQ(descMap.size(), noConnectionReplacements.size());

  for (const auto& p : descMap)
  {
    if (find(noConnectionReplacements.begin(), noConnectionReplacements.end(), p.first) == noConnectionReplacements.end())
      std::cout << "replacements list did not contain " << p.first << std::endl;
  }
}

TEST(ReplacementFilterBuilderTests, CanBuild)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ASSERT_TRUE(filter != nullptr);
}

TEST_F(ModuleReplaceTests, CurrentConnectionsFilterReplacements)
{
  HardCodedModuleFactory factory;
  auto descMap = factory.getDirectModuleDescriptionLookupMap();
  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ModuleFactoryHandle mf(new HardCodedModuleFactory);
  NetworkEditorController controller(mf, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
  initModuleParameters(false);
  auto network = controller.getNetwork();
  ModuleHandle send = controller.addModule("CreateMatrix");
  ModuleHandle process = controller.addModule("NeedToExecuteTester");
  ModuleHandle receive = controller.addModule("ReportMatrixInfo");

  ASSERT_EQ(3, network->nmodules());

  auto repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_EQ(descMap.size(), repl.size()); //everything

  auto cid1 = network->connect(ConnectionOutputPort(send, 0), ConnectionInputPort(process, 0));
  auto cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(receive, 0));
  ASSERT_EQ(2, network->nconnections());

  repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_LE(repl.size(), descMap.size()); // some modules have been filtered out

  network->disconnect(cid1);
  repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_EQ(descMap.size(), repl.size()); //everything again
}
<commit_msg>increase expected NUM_MODULES by 10 so that HardCodedModuleFactoryTests does not complain.<commit_after>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2015 Scientific Computing and Imaging Institute,
   University of Utah.

   License for the specific language governing rights and limitations under
   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Testing/ModuleTestBase/ModuleTestBase.h>
#include <Modules/Factory/HardCodedModuleFactory.h>
#include <Core/Algorithms/Factory/HardCodedAlgorithmFactory.h>
#include <Dataflow/Engine/Controller/NetworkEditorController.h>
#include <Dataflow/Network/ConnectionId.h>
#include <Modules/Legacy/Fields/CreateLatVol.h>
#include <Modules/Legacy/Math/SolveMinNormLeastSqSystem.h>
#include <Modules/Legacy/Fields/GetMeshQualityField.h>
#include <Modules/Legacy/Fields/GetFieldData.h>

using namespace SCIRun;
using namespace SCIRun::Modules;
using namespace Testing;
using namespace Modules::Factory;
using namespace Dataflow::Networks;
using namespace ReplacementImpl;
using namespace Dataflow::Engine;
using namespace Core::Algorithms;

class ModuleReplaceTests : public ModuleTest
{
};

#ifdef BUILD_TESTING
const int NUM_MODULES = 150;
#else
const int NUM_MODULES = 143;
#endif
const int NUM_ALGORITHMS = 76;

const int EXPECTED_RANGE = 16;   // Require updating these numbers every few modules

TEST(HardCodedModuleFactoryTests, ListAllModules)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  std::cout << "descMap size: " << descMap.size() << std::endl;
  EXPECT_GE(descMap.size(), NUM_MODULES);
  EXPECT_LE(descMap.size(), NUM_MODULES + EXPECTED_RANGE);

  // for (const auto& m : descMap)
  // {
  //   std::cout << m.first << " -> " << m.second << std::endl;
  // }
}

TEST(HardCodedModuleFactoryTests, ListAllAlgorithms)
{
  HardCodedAlgorithmFactory factory;

  std::cout << "algorithm factory size: " << factory.numAlgorithms() << std::endl;
  EXPECT_GE(factory.numAlgorithms(), NUM_ALGORITHMS);
  EXPECT_LE(factory.numAlgorithms(), NUM_ALGORITHMS + EXPECTED_RANGE);

  // for (const auto& a : factory)
  // {
  //   std::cout << a.first << " -> " << a.second.first << std::endl;
  // }
}

TEST(HardCodedModuleFactoryTests, ModuleTraitHasAlgorithmMatchesAlgoFactory)
{
  HardCodedModuleFactory moduleFactory;

  auto modules = moduleFactory.getDirectModuleDescriptionLookupMap();

  HardCodedAlgorithmFactory algoFactory;

  std::set<std::string> modulesWithAlgorithms;

  for (const auto& a : algoFactory)
  {
    auto moduleName = a.first;
    auto modFactIter = std::find_if(modules.cbegin(), modules.cend(),
      [&moduleName](const DirectModuleDescriptionLookupMap::value_type& p) { return p.first.module_name_ == moduleName; });
    if (modFactIter != modules.end())
    {
      if (!modFactIter->second.hasAlgo_)
        std::cout << moduleName << " is missing trait HasAlgorithm" << std::endl;
      EXPECT_TRUE(modFactIter->second.hasAlgo_);
      modulesWithAlgorithms.insert(moduleName);
    }
    else
      FAIL() << "Module found in algorithm factory but not module factory: " << moduleName;
  }

  for (const auto& m : modules)
  {
    if (modulesWithAlgorithms.find(m.first.module_name_) != modulesWithAlgorithms.end())
    {
      if (!m.second.hasAlgo_)
        std::cout << m.first.module_name_ << " is missing trait HasAlgorithm" << std::endl;
      EXPECT_TRUE(m.second.hasAlgo_);
    }
    else
    {
      if (m.second.hasAlgo_)
        std::cout << m.first.module_name_ << " has trait HasAlgorithm, when it should not" << std::endl;
      EXPECT_FALSE(m.second.hasAlgo_);
    }
  }
}

TEST(ModuleTraitsTest, CanDetermineUIAlgoStatically)
{
  ASSERT_TRUE(HasUI<Fields::CreateLatVol>::value);
  ASSERT_FALSE(HasAlgorithm<Fields::CreateLatVol>::value);

  ASSERT_FALSE(HasUI<Fields::GetFieldData>::value);
  ASSERT_TRUE(HasAlgorithm<Fields::GetFieldData>::value);

  ASSERT_TRUE(HasUI<Fields::GetMeshQualityField>::value);
  ASSERT_TRUE(HasAlgorithm<Fields::GetMeshQualityField>::value);

  ASSERT_FALSE(HasUI<Math::SolveMinNormLeastSqSystem>::value);
  ASSERT_FALSE(HasAlgorithm<Math::SolveMinNormLeastSqSystem>::value);
}

TEST_F(ModuleReplaceTests, CanComputeConnectedPortInfoFromModule)
{
  ModuleFactoryHandle mf(new HardCodedModuleFactory);
  NetworkEditorController controller(mf, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
  initModuleParameters(false);

  auto network = controller.getNetwork();

  ModuleHandle send = controller.addModule("CreateMatrix");
  ModuleHandle process = controller.addModule("NeedToExecuteTester");
  ModuleHandle receive = controller.addModule("ReportMatrixInfo");

  ASSERT_EQ(3, network->nmodules());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo; // empty maps
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo; // empty maps
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  auto cid1 = network->connect(ConnectionOutputPort(send, 0), ConnectionInputPort(process, 0));
  auto cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(receive, 0));
  ASSERT_EQ(2, network->nconnections());

  //clang on mac doesn't like "{}" for empty std::map initialization in a struct.
  const ConnectedPortTypesWithCount empty;

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo { empty, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo { { { "Matrix", 1 } }, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo { { { "Matrix", 1 } }, empty };
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  network->disconnect(cid1);
  ASSERT_EQ(1, network->nconnections());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo { empty, { { "Matrix", 1 } } };
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo { { { "Matrix", 1 } }, empty };
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  network->disconnect(cid2);
  ASSERT_EQ(0, network->nconnections());

  {
    auto sendInfo = makeConnectedPortInfo(send);
    ConnectedPortInfo expectedSendInfo; // empty maps
    EXPECT_EQ(expectedSendInfo, sendInfo);
    auto procInfo = makeConnectedPortInfo(process);
    ConnectedPortInfo expectedProcInfo; // empty maps
    EXPECT_EQ(expectedProcInfo, procInfo);
    auto recInfo = makeConnectedPortInfo(receive);
    ConnectedPortInfo expectedRecInfo; // empty maps
    EXPECT_EQ(expectedRecInfo, recInfo);
  }

  ModuleHandle create = controller.addModule("CreateLatVol");
  ModuleHandle setFD = controller.addModule("SetFieldData");
  ModuleHandle report = controller.addModule("ReportFieldInfo");

  cid1 = network->connect(ConnectionOutputPort(create, 0), ConnectionInputPort(setFD, 0));
  cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(setFD, 1));
  auto cid3 = network->connect(ConnectionOutputPort(setFD, 0), ConnectionInputPort(report, 0));
  ASSERT_EQ(3, network->nconnections());

  {
    auto createInfo = makeConnectedPortInfo(create);
    ConnectedPortInfo expectedCreateInfo { empty, { { "Field", 1 } } };
    EXPECT_EQ(expectedCreateInfo, createInfo);
    auto setInfo = makeConnectedPortInfo(setFD);
    ConnectedPortInfo expectedSetInfo { { { "Matrix", 1 }, { "Field", 1 } }, { { "Field", 1 } } };
    EXPECT_EQ(expectedSetInfo, setInfo);
    auto reportInfo = makeConnectedPortInfo(report);
    ConnectedPortInfo expectedReportInfo { { { "Field", 1 } }, empty };
    EXPECT_EQ(expectedReportInfo, reportInfo);
  }
}

TEST_F(ModuleReplaceTests, NoConnectedPortsCanBeReplacedWithAnything)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ConnectedPortInfo noConnections;
  auto noConnectionReplacements = filter->findReplacements(noConnections);
  EXPECT_EQ(descMap.size(), noConnectionReplacements.size());

  for (const auto& p : descMap)
  {
    if (find(noConnectionReplacements.begin(), noConnectionReplacements.end(), p.first) == noConnectionReplacements.end())
      std::cout << "replacements list did not contain " << p.first << std::endl;
  }
}

TEST(ReplacementFilterBuilderTests, CanBuild)
{
  HardCodedModuleFactory factory;

  auto descMap = factory.getDirectModuleDescriptionLookupMap();

  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ASSERT_TRUE(filter != nullptr);
}

TEST_F(ModuleReplaceTests, CurrentConnectionsFilterReplacements)
{
  HardCodedModuleFactory factory;
  auto descMap = factory.getDirectModuleDescriptionLookupMap();
  ModuleReplacementFilterBuilder builder(descMap);
  auto filter = builder.build();

  ModuleFactoryHandle mf(new HardCodedModuleFactory);
  NetworkEditorController controller(mf, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
  initModuleParameters(false);
  auto network = controller.getNetwork();
  ModuleHandle send = controller.addModule("CreateMatrix");
  ModuleHandle process = controller.addModule("NeedToExecuteTester");
  ModuleHandle receive = controller.addModule("ReportMatrixInfo");

  ASSERT_EQ(3, network->nmodules());

  auto repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_EQ(descMap.size(), repl.size()); //everything

  auto cid1 = network->connect(ConnectionOutputPort(send, 0), ConnectionInputPort(process, 0));
  auto cid2 = network->connect(ConnectionOutputPort(process, 0), ConnectionInputPort(receive, 0));
  ASSERT_EQ(2, network->nconnections());

  repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_LE(repl.size(), descMap.size()); // some modules have been filtered out

  network->disconnect(cid1);
  repl = filter->findReplacements(makeConnectedPortInfo(send));
  EXPECT_EQ(descMap.size(), repl.size()); //everything again
}
<|endoftext|>