<commit_before>/*
Copyright (c) 2015-2020 Alternative Games Ltd / Turo Lamminen

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 <cstring>
#include <cassert>

#include <sys/stat.h>

#include <stdexcept>

#include "Utils.h"

#include <SDL.h>

struct FILEDeleter {
	void operator()(FILE *f) { fclose(f); }
};

static FILE *logFile;

void logInit() {
	assert(!logFile);

	char *logFilePath = SDL_GetPrefPath("", "SMAADemo");
	std::string logFileName(logFilePath);
	SDL_free(logFilePath);
	logFileName += "logfile.txt";
	logFile = fopen(logFileName.c_str(), "wb");
}

void logWrite(const char* message, ...) {
	va_list argp;
	va_start(argp, message);

	if (logFile) {
		vfprintf(logFile, message, argp);
	} else {
		// Write to console if opening log file failed
		vprintf(message, argp);
	}

	va_end(argp);
}

void logShutdown() {
	assert(logFile);

	fflush(logFile);
	fclose(logFile);
	logFile = nullptr;
}

void logFlush() {
	assert(logFile);

	fflush(logFile);
}

std::vector<char> readTextFile(std::string filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (!file) {
		// TODO: better exception
		throw std::runtime_error("file not found " + filename);
	}

	int fd = fileno(file.get());
	if (fd < 0) {
		// TODO: better exception
		throw std::runtime_error("no fd");
	}

	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = fstat(fd, &statbuf);
	if (retval < 0) {
		// TODO: better exception
		throw std::runtime_error("fstat failed");
	}

	unsigned int filesize = static_cast<unsigned int>(statbuf.st_size);
	// ensure NUL -termination
	std::vector<char> buf(filesize + 1, '\0');

	size_t ret = fread(&buf[0], 1, filesize, file.get());
	if (ret != filesize)
	{
		// TODO: better exception
		throw std::runtime_error("fread failed");
	}

	return buf;
}

std::vector<char> readFile(std::string filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (!file) {
		// TODO: better exception
		throw std::runtime_error("file not found " + filename);
	}

	int fd = fileno(file.get());
	if (fd < 0) {
		// TODO: better exception
		throw std::runtime_error("no fd");
	}

	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = fstat(fd, &statbuf);
	if (retval < 0) {
		// TODO: better exception
		throw std::runtime_error("fstat failed");
	}

	unsigned int filesize = static_cast<unsigned int>(statbuf.st_size);
	std::vector<char> buf(filesize, '\0');

	size_t ret = fread(&buf[0], 1, filesize, file.get());
	if (ret != filesize)
	{
		// TODO: better exception
		throw std::runtime_error("fread failed");
	}

	return buf;
}

void writeFile(const std::string &filename, const void *contents, size_t size) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "wb"));

	fwrite(contents, 1, size, file.get());
}

bool fileExists(const std::string &filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (file) {
		return true;
	} else {
		return false;
	}
}

int64_t getFileTimestamp(const std::string &filename) {
	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = stat(filename.c_str(), &statbuf);
	if (retval < 0) {
		// TODO: better exception
		throw std::runtime_error("fstat failed");
	}
	return statbuf.st_mtime;
}
<commit_msg>Attach file and reason to exception when fstat fails<commit_after>/*
Copyright (c) 2015-2020 Alternative Games Ltd / Turo Lamminen

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 <cstring>
#include <cassert>

#include <sys/stat.h>

#include <stdexcept>

#include "Utils.h"

#include <SDL.h>

struct FILEDeleter {
	void operator()(FILE *f) { fclose(f); }
};

static FILE *logFile;

void logInit() {
	assert(!logFile);

	char *logFilePath = SDL_GetPrefPath("", "SMAADemo");
	std::string logFileName(logFilePath);
	SDL_free(logFilePath);
	logFileName += "logfile.txt";
	logFile = fopen(logFileName.c_str(), "wb");
}

void logWrite(const char* message, ...) {
	va_list argp;
	va_start(argp, message);

	if (logFile) {
		vfprintf(logFile, message, argp);
	} else {
		// Write to console if opening log file failed
		vprintf(message, argp);
	}

	va_end(argp);
}

void logShutdown() {
	assert(logFile);

	fflush(logFile);
	fclose(logFile);
	logFile = nullptr;
}

void logFlush() {
	assert(logFile);

	fflush(logFile);
}

std::vector<char> readTextFile(std::string filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (!file) {
		// TODO: better exception
		throw std::runtime_error("file not found " + filename);
	}

	int fd = fileno(file.get());
	if (fd < 0) {
		// TODO: better exception
		throw std::runtime_error("no fd");
	}

	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = fstat(fd, &statbuf);
	if (retval < 0) {
		// TODO: better exception
		std::string msg = "fstat failed for \"" + filename + "\": " + strerror(errno);
		throw std::runtime_error(msg);
	}

	unsigned int filesize = static_cast<unsigned int>(statbuf.st_size);
	// ensure NUL -termination
	std::vector<char> buf(filesize + 1, '\0');

	size_t ret = fread(&buf[0], 1, filesize, file.get());
	if (ret != filesize)
	{
		// TODO: better exception
		throw std::runtime_error("fread failed");
	}

	return buf;
}

std::vector<char> readFile(std::string filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (!file) {
		// TODO: better exception
		throw std::runtime_error("file not found " + filename);
	}

	int fd = fileno(file.get());
	if (fd < 0) {
		// TODO: better exception
		throw std::runtime_error("no fd");
	}

	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = fstat(fd, &statbuf);
	if (retval < 0) {
		// TODO: better exception
		std::string msg = "fstat failed for \"" + filename + "\": " + strerror(errno);
		throw std::runtime_error(msg);
	}

	unsigned int filesize = static_cast<unsigned int>(statbuf.st_size);
	std::vector<char> buf(filesize, '\0');

	size_t ret = fread(&buf[0], 1, filesize, file.get());
	if (ret != filesize)
	{
		// TODO: better exception
		throw std::runtime_error("fread failed");
	}

	return buf;
}

void writeFile(const std::string &filename, const void *contents, size_t size) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "wb"));

	fwrite(contents, 1, size, file.get());
}

bool fileExists(const std::string &filename) {
	std::unique_ptr<FILE, FILEDeleter> file(fopen(filename.c_str(), "rb"));

	if (file) {
		return true;
	} else {
		return false;
	}
}

int64_t getFileTimestamp(const std::string &filename) {
	struct stat statbuf;
	memset(&statbuf, 0, sizeof(struct stat));
	int retval = stat(filename.c_str(), &statbuf);
	if (retval < 0) {
		// TODO: better exception
		std::string msg = "fstat failed for \"" + filename + "\": " + strerror(errno);
		throw std::runtime_error(msg);
	}
	return statbuf.st_mtime;
}
<|endoftext|>


<commit_before>#include "job.h"
#include "debug.h"
#include "utils.h"

/*TODO: These could be turned into a user setting */
#define JOB_NAME_PREFIX QLatin1String("Job_")

Job::Job(QObject *parent)
    : QObject(parent),
      _optionScheduledEnabled(false),
      _optionPreservePaths(true),
      _optionTraverseMount(true),
      _optionFollowSymLinks(false),
      _optionSkipFilesSize(0),
      _optionSkipFiles(false),
      _optionSkipFilesPatterns(DEFAULT_SKIP_FILES),
      _optionSkipNoDump(false)
{
    QSettings settings;
    setOptionPreservePaths(settings.value("tarsnap/preserve_pathnames", true).toBool());
    setOptionTraverseMount(settings.value("tarsnap/traverse_mount", true).toBool());
    setOptionFollowSymLinks(settings.value("tarsnap/follow_symlinks", false).toBool());
    setOptionSkipNoDump(settings.value("app/skip_nodump", false).toBool());
    setOptionSkipFilesSize(settings.value("app/skip_files_size", 0).toInt());
    setOptionSkipFiles(settings.value("app/skip_system_enabled", false).toBool());
    setOptionSkipFilesPatterns(settings.value("app/skip_system_files", DEFAULT_SKIP_FILES).toString());
}

Job::~Job()
{
}

QString Job::name() const
{
    return _name;
}

void Job::setName(const QString &name)
{
    _name = name;
}

QString Job::archivePrefix()
{
    return JOB_NAME_PREFIX + name();
}

QList<QUrl> Job::urls() const
{
    return _urls;
}

void Job::setUrls(const QList<QUrl> &urls)
{
    _urls = urls;
}

QList<ArchivePtr> Job::archives() const
{
    return _archives;
}

void Job::setArchives(const QList<ArchivePtr> &archives)
{
    _archives.clear();
    _archives = archives;
    std::sort(_archives.begin(), _archives.end(),
              [](const ArchivePtr &a, const ArchivePtr &b) {
                  return (a->timestamp() > b->timestamp());
              });
    foreach(ArchivePtr archive, _archives)
    {
        connect(archive.data(), &Archive::purged, this, &Job::loadArchives,
                QUEUED);
    }
    emit changed();
}

bool Job::optionScheduledEnabled() const
{
    return _optionScheduledEnabled;
}

void Job::setOptionScheduledEnabled(bool optionScheduledEnabled)
{
    _optionScheduledEnabled = optionScheduledEnabled;
}

bool Job::optionPreservePaths() const
{
    return _optionPreservePaths;
}

void Job::setOptionPreservePaths(bool optionPreservePaths)
{
    _optionPreservePaths = optionPreservePaths;
}

bool Job::optionFollowSymLinks() const
{
    return _optionFollowSymLinks;
}

void Job::setOptionFollowSymLinks(bool optionFollowSymLinks)
{
    _optionFollowSymLinks = optionFollowSymLinks;
}

bool Job::optionTraverseMount() const
{
    return _optionTraverseMount;
}

void Job::setOptionTraverseMount(bool optionTraverseMount)
{
    _optionTraverseMount = optionTraverseMount;
}

int Job::optionSkipFilesSize() const
{
    return _optionSkipFilesSize;
}

void Job::setOptionSkipFilesSize(const int &optionSkipFilesSize)
{
    _optionSkipFilesSize = optionSkipFilesSize;
}

bool Job::optionSkipFiles() const
{
    return _optionSkipFiles;
}

void Job::setOptionSkipFiles(bool optionSkipFiles)
{
    _optionSkipFiles = optionSkipFiles;
}

QString Job::optionSkipFilesPatterns() const
{
    return _optionSkipFilesPatterns;
}

void Job::setOptionSkipFilesPatterns(const QString &optionSkipFilesPatterns)
{
    _optionSkipFilesPatterns = optionSkipFilesPatterns;
}

bool Job::optionSkipNoDump() const
{
    return _optionSkipNoDump;
}

void Job::setOptionSkipNoDump(bool optionSkipNoDump)
{
    _optionSkipNoDump = optionSkipNoDump;
}

BackupTaskPtr Job::createBackupTask()
{
    QSettings     settings;
    BackupTaskPtr backup(new BackupTask);
    backup->setName(JOB_NAME_PREFIX + name() +
                    QDateTime::currentDateTime().toString(ARCHIVE_TIMESTAMP_FORMAT));
    backup->setJobRef(objectKey());
    backup->setUrls(urls());
    backup->setOptionPreservePaths(optionPreservePaths());
    backup->setOptionTraverseMount(optionTraverseMount());
    backup->setOptionFollowSymLinks(optionFollowSymLinks());
    backup->setOptionSkipFilesSize(optionSkipFilesSize());
    backup->setOptionSkipSystem(optionSkipFiles());
    backup->setOptionSkipSystemFiles(optionSkipFilesPatterns());
    backup->setOptionSkipNoDump(optionSkipNoDump());
    backup->setOptionDryRun(settings.value("tarsnap/dry_run", false).toBool());
    return backup;
}

void Job::save()
{
    bool exists = findObjectWithKey(_name);

    QString queryString;
    if(exists)
        queryString = QLatin1String(
            "update jobs set name=?, urls=?, optionScheduledEnabled=?, "
            "optionPreservePaths=?, "
            "optionTraverseMount=?, optionFollowSymLinks=?, "
            "optionSkipFilesSize=?, "
            "optionSkipFiles=?, optionSkipFilesPatterns=?, optionSkipNoDump=?"
            "where name=?");
    else
        queryString = QLatin1String(
            "insert into jobs(name, urls, optionScheduledEnabled, "
            "optionPreservePaths, optionTraverseMount, "
            "optionFollowSymLinks, optionSkipFilesSize, optionSkipFiles, "
            "optionSkipFilesPatterns, optionSkipNoDump) values(?, ?, ?, ?, ?, "
            "?, ?, ?, ?, ?)");

    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(queryString))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    QStringList urls;
    foreach(QUrl url, _urls)
        urls << url.toString(QUrl::FullyEncoded);
    query.addBindValue(urls.join('\n'));
    query.addBindValue(_optionScheduledEnabled);
    query.addBindValue(_optionPreservePaths);
    query.addBindValue(_optionTraverseMount);
    query.addBindValue(_optionFollowSymLinks);
    query.addBindValue(_optionSkipFilesSize);
    query.addBindValue(_optionSkipFiles);
    query.addBindValue(_optionSkipFilesPatterns);
    query.addBindValue(_optionSkipNoDump);
    if(exists)
        query.addBindValue(_name);

    store.runQuery(query);
    setObjectKey(_name);
    emit changed();
}

void Job::load()
{
    if(_name.isEmpty())
    {
        DEBUG << "Attempting to load Job object with empty _name key.";
        return;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("select * from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    if(store.runQuery(query) && query.next())
    {
        _urls = QUrl::fromStringList(query.value(query.record().indexOf("urls"))
                                         .toString()
                                         .split('\n', QString::SkipEmptyParts));
        _optionScheduledEnabled =
            query.value(query.record().indexOf("optionScheduledEnabled")).toBool();
        _optionPreservePaths =
            query.value(query.record().indexOf("optionPreservePaths")).toBool();
        _optionTraverseMount =
            query.value(query.record().indexOf("optionTraverseMount")).toBool();
        _optionFollowSymLinks =
            query.value(query.record().indexOf("optionFollowSymLinks")).toBool();
        _optionSkipFilesSize =
            query.value(query.record().indexOf("optionSkipFilesSize")).toInt();
        _optionSkipFiles =
            query.value(query.record().indexOf("optionSkipFiles")).toBool();
        _optionSkipFilesPatterns =
            query.value(query.record().indexOf("optionSkipFilesPatterns")).toString();
        _optionSkipNoDump =
            query.value(query.record().indexOf("optionSkipNoDump")).toBool();
        setObjectKey(_name);
        emit loadArchives();
    }
    else
    {
        DEBUG << "Job object with key " << _name << " not found.";
    }
}

void Job::purge()
{
    if(_name.isEmpty())
    {
        DEBUG << "Attempting to delete Job object with empty _name key.";
        return;
    }
    if(!findObjectWithKey(_name))
    {
        DEBUG << "No Job object with key " << _name;
        return;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("delete from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    store.runQuery(query);
    setObjectKey("");
}

bool Job::findObjectWithKey(QString key)
{
    bool found = false;
    if(key.isEmpty())
    {
        DEBUG << "findObjectWithKey method called with empty args";
        return found;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("select name from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return found;
    }
    query.addBindValue(key);
    if(store.runQuery(query) && query.next())
    {
        found = true;
    }
    return found;
}
<commit_msg>Remove obsolete TODO comment.<commit_after>#include "job.h"
#include "debug.h"
#include "utils.h"

#define JOB_NAME_PREFIX QLatin1String("Job_")

Job::Job(QObject *parent)
    : QObject(parent),
      _optionScheduledEnabled(false),
      _optionPreservePaths(true),
      _optionTraverseMount(true),
      _optionFollowSymLinks(false),
      _optionSkipFilesSize(0),
      _optionSkipFiles(false),
      _optionSkipFilesPatterns(DEFAULT_SKIP_FILES),
      _optionSkipNoDump(false)
{
    QSettings settings;
    setOptionPreservePaths(settings.value("tarsnap/preserve_pathnames", true).toBool());
    setOptionTraverseMount(settings.value("tarsnap/traverse_mount", true).toBool());
    setOptionFollowSymLinks(settings.value("tarsnap/follow_symlinks", false).toBool());
    setOptionSkipNoDump(settings.value("app/skip_nodump", false).toBool());
    setOptionSkipFilesSize(settings.value("app/skip_files_size", 0).toInt());
    setOptionSkipFiles(settings.value("app/skip_system_enabled", false).toBool());
    setOptionSkipFilesPatterns(settings.value("app/skip_system_files", DEFAULT_SKIP_FILES).toString());
}

Job::~Job()
{
}

QString Job::name() const
{
    return _name;
}

void Job::setName(const QString &name)
{
    _name = name;
}

QString Job::archivePrefix()
{
    return JOB_NAME_PREFIX + name();
}

QList<QUrl> Job::urls() const
{
    return _urls;
}

void Job::setUrls(const QList<QUrl> &urls)
{
    _urls = urls;
}

QList<ArchivePtr> Job::archives() const
{
    return _archives;
}

void Job::setArchives(const QList<ArchivePtr> &archives)
{
    _archives.clear();
    _archives = archives;
    std::sort(_archives.begin(), _archives.end(),
              [](const ArchivePtr &a, const ArchivePtr &b) {
                  return (a->timestamp() > b->timestamp());
              });
    foreach(ArchivePtr archive, _archives)
    {
        connect(archive.data(), &Archive::purged, this, &Job::loadArchives,
                QUEUED);
    }
    emit changed();
}

bool Job::optionScheduledEnabled() const
{
    return _optionScheduledEnabled;
}

void Job::setOptionScheduledEnabled(bool optionScheduledEnabled)
{
    _optionScheduledEnabled = optionScheduledEnabled;
}

bool Job::optionPreservePaths() const
{
    return _optionPreservePaths;
}

void Job::setOptionPreservePaths(bool optionPreservePaths)
{
    _optionPreservePaths = optionPreservePaths;
}

bool Job::optionFollowSymLinks() const
{
    return _optionFollowSymLinks;
}

void Job::setOptionFollowSymLinks(bool optionFollowSymLinks)
{
    _optionFollowSymLinks = optionFollowSymLinks;
}

bool Job::optionTraverseMount() const
{
    return _optionTraverseMount;
}

void Job::setOptionTraverseMount(bool optionTraverseMount)
{
    _optionTraverseMount = optionTraverseMount;
}

int Job::optionSkipFilesSize() const
{
    return _optionSkipFilesSize;
}

void Job::setOptionSkipFilesSize(const int &optionSkipFilesSize)
{
    _optionSkipFilesSize = optionSkipFilesSize;
}

bool Job::optionSkipFiles() const
{
    return _optionSkipFiles;
}

void Job::setOptionSkipFiles(bool optionSkipFiles)
{
    _optionSkipFiles = optionSkipFiles;
}

QString Job::optionSkipFilesPatterns() const
{
    return _optionSkipFilesPatterns;
}

void Job::setOptionSkipFilesPatterns(const QString &optionSkipFilesPatterns)
{
    _optionSkipFilesPatterns = optionSkipFilesPatterns;
}

bool Job::optionSkipNoDump() const
{
    return _optionSkipNoDump;
}

void Job::setOptionSkipNoDump(bool optionSkipNoDump)
{
    _optionSkipNoDump = optionSkipNoDump;
}

BackupTaskPtr Job::createBackupTask()
{
    QSettings     settings;
    BackupTaskPtr backup(new BackupTask);
    backup->setName(JOB_NAME_PREFIX + name() +
                    QDateTime::currentDateTime().toString(ARCHIVE_TIMESTAMP_FORMAT));
    backup->setJobRef(objectKey());
    backup->setUrls(urls());
    backup->setOptionPreservePaths(optionPreservePaths());
    backup->setOptionTraverseMount(optionTraverseMount());
    backup->setOptionFollowSymLinks(optionFollowSymLinks());
    backup->setOptionSkipFilesSize(optionSkipFilesSize());
    backup->setOptionSkipSystem(optionSkipFiles());
    backup->setOptionSkipSystemFiles(optionSkipFilesPatterns());
    backup->setOptionSkipNoDump(optionSkipNoDump());
    backup->setOptionDryRun(settings.value("tarsnap/dry_run", false).toBool());
    return backup;
}

void Job::save()
{
    bool exists = findObjectWithKey(_name);

    QString queryString;
    if(exists)
        queryString = QLatin1String(
            "update jobs set name=?, urls=?, optionScheduledEnabled=?, "
            "optionPreservePaths=?, "
            "optionTraverseMount=?, optionFollowSymLinks=?, "
            "optionSkipFilesSize=?, "
            "optionSkipFiles=?, optionSkipFilesPatterns=?, optionSkipNoDump=?"
            "where name=?");
    else
        queryString = QLatin1String(
            "insert into jobs(name, urls, optionScheduledEnabled, "
            "optionPreservePaths, optionTraverseMount, "
            "optionFollowSymLinks, optionSkipFilesSize, optionSkipFiles, "
            "optionSkipFilesPatterns, optionSkipNoDump) values(?, ?, ?, ?, ?, "
            "?, ?, ?, ?, ?)");

    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(queryString))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    QStringList urls;
    foreach(QUrl url, _urls)
        urls << url.toString(QUrl::FullyEncoded);
    query.addBindValue(urls.join('\n'));
    query.addBindValue(_optionScheduledEnabled);
    query.addBindValue(_optionPreservePaths);
    query.addBindValue(_optionTraverseMount);
    query.addBindValue(_optionFollowSymLinks);
    query.addBindValue(_optionSkipFilesSize);
    query.addBindValue(_optionSkipFiles);
    query.addBindValue(_optionSkipFilesPatterns);
    query.addBindValue(_optionSkipNoDump);
    if(exists)
        query.addBindValue(_name);

    store.runQuery(query);
    setObjectKey(_name);
    emit changed();
}

void Job::load()
{
    if(_name.isEmpty())
    {
        DEBUG << "Attempting to load Job object with empty _name key.";
        return;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("select * from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    if(store.runQuery(query) && query.next())
    {
        _urls = QUrl::fromStringList(query.value(query.record().indexOf("urls"))
                                         .toString()
                                         .split('\n', QString::SkipEmptyParts));
        _optionScheduledEnabled =
            query.value(query.record().indexOf("optionScheduledEnabled")).toBool();
        _optionPreservePaths =
            query.value(query.record().indexOf("optionPreservePaths")).toBool();
        _optionTraverseMount =
            query.value(query.record().indexOf("optionTraverseMount")).toBool();
        _optionFollowSymLinks =
            query.value(query.record().indexOf("optionFollowSymLinks")).toBool();
        _optionSkipFilesSize =
            query.value(query.record().indexOf("optionSkipFilesSize")).toInt();
        _optionSkipFiles =
            query.value(query.record().indexOf("optionSkipFiles")).toBool();
        _optionSkipFilesPatterns =
            query.value(query.record().indexOf("optionSkipFilesPatterns")).toString();
        _optionSkipNoDump =
            query.value(query.record().indexOf("optionSkipNoDump")).toBool();
        setObjectKey(_name);
        emit loadArchives();
    }
    else
    {
        DEBUG << "Job object with key " << _name << " not found.";
    }
}

void Job::purge()
{
    if(_name.isEmpty())
    {
        DEBUG << "Attempting to delete Job object with empty _name key.";
        return;
    }
    if(!findObjectWithKey(_name))
    {
        DEBUG << "No Job object with key " << _name;
        return;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("delete from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return;
    }
    query.addBindValue(_name);
    store.runQuery(query);
    setObjectKey("");
}

bool Job::findObjectWithKey(QString key)
{
    bool found = false;
    if(key.isEmpty())
    {
        DEBUG << "findObjectWithKey method called with empty args";
        return found;
    }
    PersistentStore &store = getStore();
    QSqlQuery        query = store.createQuery();
    if(!query.prepare(QLatin1String("select name from jobs where name = ?")))
    {
        DEBUG << query.lastError().text();
        return found;
    }
    query.addBindValue(key);
    if(store.runQuery(query) && query.next())
    {
        found = true;
    }
    return found;
}
<|endoftext|>


<commit_before>/** feature-selection.cc ---
 *
 * Copyright (C) 2011 OpenCog Foundation
 *
 * Author: Nil Geisweiller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program 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 Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <iostream>
#include <fstream>
#include <memory>
#include <stdio.h>

#include <boost/assign/std/vector.hpp> // for 'operator+=()'
#include <boost/range/algorithm/find.hpp>
#include <boost/range/irange.hpp>

#include <opencog/util/Logger.h>

#include <opencog/comboreduct/table/table.h>
#include <opencog/comboreduct/table/table_io.h>
#include <opencog/learning/moses/optimization/hill-climbing.h> // for hc_params

#include "feature-selection.h"
#include "../algo/deme_optimize.h"
#include "../algo/incremental.h"
#include "../algo/stochastic_max_dependency.h"
#include "../algo/simple.h"

namespace opencog {

using namespace combo;
using namespace std;
using namespace boost::assign; // bring 'operator+=()' into scope

void err_empty_features(const string& outfile)
{
    // If the dataset is degenerate, it can happen that feature
    // selection will fail to find any features.  Rather than just
    // crashing as a result, write out an empty file, which at least
    // allows downstream processes to infer what happened (i.e. to tell
    // apart this case from an outright crash.)
    logger().warn() << "No features have been selected.";
    cerr << "No features have been selected." << endl;
    if (!outfile.empty()) {
        ofstream out(outfile.c_str());
        out << "# No features have been selected." << endl;
    }
    exit(0);
}

// Score all individual features of Table
vector<double> score_individual_features(const Table& table,
                                         const feature_selection_parameters& fs_params)
{
    typedef set<arity_t> FS;
    CTable ctable = table.compressed();
    fs_scorer<FS> fs_sc(ctable, fs_params);
    vector<double> res;
    boost::transform(boost::irange(0, table.get_arity()), back_inserter(res),
                     [&](arity_t idx) { FS fs = {idx}; return fs_sc(fs); });
    return res;
}

// log the set of features and its number
void log_selected_features(arity_t old_arity, const Table& ftable,
                           const feature_selection_parameters& fs_params)
{
    // log the number selected features
    logger().info("%d out of %d features have been selected",
                  ftable.get_arity(), old_arity);

    // log set of selected feature set
    const string_seq& labs = ftable.itable.get_labels();
    const vector<double>& sco = score_individual_features(ftable, fs_params);
    for (unsigned i=0; i<labs.size(); i++)
    {
        logger().info() << labs[i] << " " << sco[i];
    }
}

/**
 * Get indices (aka positions or offsets) of a list of labels given a header
 */
vector<unsigned> get_indices(const vector<string>& labels,
                             const vector<string>& header)
{
    vector<unsigned> res;
    for (unsigned i = 0; i < header.size(); ++i)
        if (boost::find(labels, header[i]) != labels.end())
            res.push_back(i);
    return res;
}

unsigned get_index(const string& label, const vector<string>& header)
{
    return distance(header.begin(), boost::find(header, label));
}

void write_results(const Table& selected_table,
                   const feature_selection_parameters& fs_params)
{
    Table table_wff = selected_table;
    table_wff.add_features_from_file(fs_params.input_file,
                                     fs_params.force_features_str);
    if (fs_params.output_file.empty())
        ostreamTable(cout, table_wff);
    else
        saveTable(fs_params.output_file, table_wff);
}

feature_set initial_features(const vector<string>& ilabels,
                             const feature_selection_parameters& fs_params)
{
    vector<string> vif; // valid initial features, used for logging
    feature_set res;

    for (const string& f : fs_params.initial_features) {
        size_t idx = distance(ilabels.begin(), boost::find(ilabels, f));
        if(idx < ilabels.size()) { // feature found
            res.insert(idx);
            // for logging
            vif += f;
        }
        else // feature not found
            logger().warn("No such a feature %s in file %s. It will be ignored as initial feature.", f.c_str(), fs_params.input_file.c_str());
    }
    // Logger
    if(vif.empty())
        logger().info("The search will start with the empty feature set");
    else {
        stringstream ss;
        ss << "The search will start with the following feature set: ";
        ostreamContainer(ss, vif, ",");
        logger().info(ss.str());
    }
    // ~Logger
    return res;
}

feature_set_pop select_feature_sets(const CTable& ctable,
                                    const feature_selection_parameters& fs_params)
{
    if (fs_params.algorithm == moses::hc) {
        // setting moses optimization parameters
        double pop_size_ratio = 20;
        size_t max_dist = 4;
        score_t min_score_improv = 0.0;
        optim_parameters op_params;
        op_params.opt_algo = moses::hc;
        op_params.pop_size_ratio = pop_size_ratio;
        op_params.terminate_if_gte = fs_params.hc_max_score;
        op_params.max_dist = max_dist;
        op_params.set_min_score_improv(min_score_improv);
        hc_parameters hc_params;
        hc_params.widen_search = fs_params.hc_widen_search;
        hc_params.single_step = false;
        hc_params.crossover = fs_params.hc_crossover;
        hc_params.crossover_pop_size = fs_params.hc_crossover_pop_size;
        hc_params.prefix_stat_deme = "FSDemes";
        hill_climbing hc(op_params, hc_params);
        return moses_select_feature_sets(ctable, hc, fs_params);
    } else if (fs_params.algorithm == inc) {
        return incremental_select_feature_sets(ctable, fs_params);
    } else if (fs_params.algorithm == smd) {
        return smd_select_feature_sets(ctable, fs_params);
    } else if (fs_params.algorithm == simple) {
        return simple_select_feature_sets(ctable, fs_params);
    } else {
        cerr << "Fatal Error: Algorithm '" << fs_params.algorithm
             << "' is unknown, please consult the help for the "
             << "list of algorithms." << endl;
        exit(1);
        return feature_set_pop(); // to please Mr compiler
    }
}

feature_set select_features(const CTable& ctable,
                            const feature_selection_parameters& fs_params)
{
    feature_set_pop fs_pop = select_feature_sets(ctable, fs_params);
    OC_ASSERT(!fs_pop.empty(), "There might a bug");
    return fs_pop.begin()->second;
}

feature_set select_features(const Table& table,
                            const feature_selection_parameters& fs_params)
{
    CTable ctable = table.compressed();
    return select_features(ctable, fs_params);
}

void feature_selection(const Table& table,
                       const feature_selection_parameters& fs_params)
{
    feature_set selected_features = select_features(table, fs_params);
    if (selected_features.empty())
        err_empty_features(fs_params.output_file);
    else {
        Table filt_table = table.filtered(selected_features);
        log_selected_features(table.get_arity(), filt_table, fs_params);
        write_results(filt_table, fs_params);
    }
}

} // ~namespace opencog
<commit_msg>clarify who is printing this log message<commit_after>/** feature-selection.cc ---
 *
 * Copyright (C) 2011 OpenCog Foundation
 *
 * Author: Nil Geisweiller
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License v3 as
 * published by the Free Software Foundation and including the exceptions
 * at http://opencog.org/wiki/Licenses
 *
 * This program 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 Affero General Public License
 * along with this program; if not, write to:
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <iostream>
#include <fstream>
#include <memory>
#include <stdio.h>

#include <boost/assign/std/vector.hpp> // for 'operator+=()'
#include <boost/range/algorithm/find.hpp>
#include <boost/range/irange.hpp>

#include <opencog/util/Logger.h>

#include <opencog/comboreduct/table/table.h>
#include <opencog/comboreduct/table/table_io.h>
#include <opencog/learning/moses/optimization/hill-climbing.h> // for hc_params

#include "feature-selection.h"
#include "../algo/deme_optimize.h"
#include "../algo/incremental.h"
#include "../algo/stochastic_max_dependency.h"
#include "../algo/simple.h"

namespace opencog {

using namespace combo;
using namespace std;
using namespace boost::assign; // bring 'operator+=()' into scope

void err_empty_features(const string& outfile)
{
    // If the dataset is degenerate, it can happen that feature
    // selection will fail to find any features.  Rather than just
    // crashing as a result, write out an empty file, which at least
    // allows downstream processes to infer what happened (i.e. to tell
    // apart this case from an outright crash.)
    logger().warn() << "No features have been selected.";
    cerr << "No features have been selected." << endl;
    if (!outfile.empty()) {
        ofstream out(outfile.c_str());
        out << "# No features have been selected." << endl;
    }
    exit(0);
}

// Score all individual features of Table
vector<double> score_individual_features(const Table& table,
                                         const feature_selection_parameters& fs_params)
{
    typedef set<arity_t> FS;
    CTable ctable = table.compressed();
    fs_scorer<FS> fs_sc(ctable, fs_params);
    vector<double> res;
    boost::transform(boost::irange(0, table.get_arity()), back_inserter(res),
                     [&](arity_t idx) { FS fs = {idx}; return fs_sc(fs); });
    return res;
}

// log the set of features and its number
void log_selected_features(arity_t old_arity, const Table& ftable,
                           const feature_selection_parameters& fs_params)
{
    // log the number selected features
    logger().info("%d out of %d features have been selected",
                  ftable.get_arity(), old_arity);

    // log set of selected feature set
    const string_seq& labs = ftable.itable.get_labels();
    const vector<double>& sco = score_individual_features(ftable, fs_params);
    for (unsigned i=0; i<labs.size(); i++)
    {
        logger().info() << "log_selected_features(): " << labs[i] << " " << sco[i];
    }
}

/**
 * Get indices (aka positions or offsets) of a list of labels given a header
 */
vector<unsigned> get_indices(const vector<string>& labels,
                             const vector<string>& header)
{
    vector<unsigned> res;
    for (unsigned i = 0; i < header.size(); ++i)
        if (boost::find(labels, header[i]) != labels.end())
            res.push_back(i);
    return res;
}

unsigned get_index(const string& label, const vector<string>& header)
{
    return distance(header.begin(), boost::find(header, label));
}

void write_results(const Table& selected_table,
                   const feature_selection_parameters& fs_params)
{
    Table table_wff = selected_table;
    table_wff.add_features_from_file(fs_params.input_file,
                                     fs_params.force_features_str);
    if (fs_params.output_file.empty())
        ostreamTable(cout, table_wff);
    else
        saveTable(fs_params.output_file, table_wff);
}

feature_set initial_features(const vector<string>& ilabels,
                             const feature_selection_parameters& fs_params)
{
    vector<string> vif; // valid initial features, used for logging
    feature_set res;

    for (const string& f : fs_params.initial_features) {
        size_t idx = distance(ilabels.begin(), boost::find(ilabels, f));
        if(idx < ilabels.size()) { // feature found
            res.insert(idx);
            // for logging
            vif += f;
        }
        else // feature not found
            logger().warn("No such a feature %s in file %s. It will be ignored as initial feature.", f.c_str(), fs_params.input_file.c_str());
    }
    // Logger
    if(vif.empty())
        logger().info("The search will start with the empty feature set");
    else {
        stringstream ss;
        ss << "The search will start with the following feature set: ";
        ostreamContainer(ss, vif, ",");
        logger().info(ss.str());
    }
    // ~Logger
    return res;
}

feature_set_pop select_feature_sets(const CTable& ctable,
                                    const feature_selection_parameters& fs_params)
{
    if (fs_params.algorithm == moses::hc) {
        // setting moses optimization parameters
        double pop_size_ratio = 20;
        size_t max_dist = 4;
        score_t min_score_improv = 0.0;
        optim_parameters op_params;
        op_params.opt_algo = moses::hc;
        op_params.pop_size_ratio = pop_size_ratio;
        op_params.terminate_if_gte = fs_params.hc_max_score;
        op_params.max_dist = max_dist;
        op_params.set_min_score_improv(min_score_improv);
        hc_parameters hc_params;
        hc_params.widen_search = fs_params.hc_widen_search;
        hc_params.single_step = false;
        hc_params.crossover = fs_params.hc_crossover;
        hc_params.crossover_pop_size = fs_params.hc_crossover_pop_size;
        hc_params.prefix_stat_deme = "FSDemes";
        hill_climbing hc(op_params, hc_params);
        return moses_select_feature_sets(ctable, hc, fs_params);
    } else if (fs_params.algorithm == inc) {
        return incremental_select_feature_sets(ctable, fs_params);
    } else if (fs_params.algorithm == smd) {
        return smd_select_feature_sets(ctable, fs_params);
    } else if (fs_params.algorithm == simple) {
        return simple_select_feature_sets(ctable, fs_params);
    } else {
        cerr << "Fatal Error: Algorithm '" << fs_params.algorithm
             << "' is unknown, please consult the help for the "
             << "list of algorithms." << endl;
        exit(1);
        return feature_set_pop(); // to please Mr compiler
    }
}

feature_set select_features(const CTable& ctable,
                            const feature_selection_parameters& fs_params)
{
    feature_set_pop fs_pop = select_feature_sets(ctable, fs_params);
    OC_ASSERT(!fs_pop.empty(), "There might a bug");
    return fs_pop.begin()->second;
}

feature_set select_features(const Table& table,
                            const feature_selection_parameters& fs_params)
{
    CTable ctable = table.compressed();
    return select_features(ctable, fs_params);
}

void feature_selection(const Table& table,
                       const feature_selection_parameters& fs_params)
{
    feature_set selected_features = select_features(table, fs_params);
    if (selected_features.empty())
        err_empty_features(fs_params.output_file);
    else {
        Table filt_table = table.filtered(selected_features);
        log_selected_features(table.get_arity(), filt_table, fs_params);
        write_results(filt_table, fs_params);
    }
}

} // ~namespace opencog
<|endoftext|>


<commit_before>//Copyright (c) 2015 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include "GCodePlannerTest.h"

#include "../src/MeshGroup.h" //Needed to construct the GCodePlanner.

#define ALLOWED_ESTIMATE_ERROR 0.1 //Fraction of the time estimates that the estimate is allowed to be off from the ground truth.

namespace cura
{

CPPUNIT_TEST_SUITE_REGISTRATION(GCodePlannerTest);

void GCodePlannerTest::setUp()
{
    storage = new SliceDataStorage(nullptr); //Empty data.
    storage->retraction_config.speed = 1; //We'll set some of the configurations, just in order to get valid g-code (speed not zero, etc.)
    storage->retraction_config.primeSpeed = 1;
    storage->retraction_config.distance = 10;
    storage->travel_config.init(1,1,1,1,1);
    FanSpeedLayerTimeSettings fan_speed_layer_time_settings; //A dummy fan speed and layer time settings.
    fan_speed_layer_time_settings.cool_min_layer_time = 0;
    fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = 1;
    fan_speed_layer_time_settings.cool_fan_speed_min = 0;
    fan_speed_layer_time_settings.cool_fan_speed_max = 1;
    fan_speed_layer_time_settings.cool_min_speed = 0.5;
    //                              Slice     layer  z  layer   last        current   is inside  fan speed and layer            combing           comb    travel  travel avoid
    //                              storage   nr        height  position    extruder  mesh       time settings                  mode              offset  avoid   distance
    gCodePlanner = new GCodePlanner(*storage, 0,     0, 0.1,    Point(0,0), 0,        false,     fan_speed_layer_time_settings, CombingMode::OFF, 100,    false,  50          );
}

void GCodePlannerTest::tearDown()
{
    delete gCodePlanner;
    gCodePlanner = nullptr;
    delete storage;
    storage = nullptr;
}

void GCodePlannerTest::computeNaiveTimeEstimatesRetractionTest()
{
    TimeMaterialEstimates estimate_empty = gCodePlanner->computeNaiveTimeEstimates(); //First try estimating time and material without any content.
    TimeMaterialEstimates estimate_empty_expected(0,0,0,0); //We expect the estimate of all time and material used to be 0.
    verifyEstimates(estimate_empty,estimate_empty_expected,"Empty GCodePlanner");

    GCodeExport gcode;
    GCodePathConfig configuration = storage->travel_config;
    gCodePlanner->addExtrusionMove(Point(0, 0), &configuration, SpaceFillType::Lines, 1.0f); //Need to have at least one path to have a configuration.
    TimeMaterialEstimates before_retract = gCodePlanner->computeNaiveTimeEstimates();
    gCodePlanner->writeRetraction(gcode,(unsigned int)0,(unsigned int)0); //Make a retract.
    TimeMaterialEstimates after_retract = gCodePlanner->computeNaiveTimeEstimates();
    TimeMaterialEstimates estimate_one_retraction = after_retract - before_retract;
    double retract_unretract_time = configuration.retraction_config->distance / configuration.retraction_config->primeSpeed;
    TimeMaterialEstimates estimate_one_retraction_expected(0,retract_unretract_time * 0.5,retract_unretract_time * 0.5,0);
    verifyEstimates(estimate_one_retraction,estimate_one_retraction_expected,"One retraction");
}

void GCodePlannerTest::verifyEstimates(const TimeMaterialEstimates& observed,const TimeMaterialEstimates& expected,std::string test_description)
{
    //Check each of the four estimates in the TimeMaterialEstimate instances.
    {
        std::stringstream ss;
        ss << test_description << ": Extrude time is " << observed.getExtrudeTime() << " instead of the expected " << expected.getExtrudeTime();
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getExtrudeTime() - expected.getExtrudeTime() < expected.getExtrudeTime() * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Unretracted travel time is " << (observed.getTotalUnretractedTime() - observed.getExtrudeTime()) << " instead of the expected " << (expected.getTotalUnretractedTime() - expected.getExtrudeTime());
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getTotalUnretractedTime() - observed.getExtrudeTime() - (expected.getTotalUnretractedTime() - expected.getExtrudeTime()) < (expected.getTotalUnretractedTime() - expected.getExtrudeTime()) * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Retracted travel time is " << (observed.getTotalTime() - observed.getTotalUnretractedTime()) << " instead of the expected " << (expected.getTotalTime() - expected.getTotalUnretractedTime());
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getTotalTime() - observed.getTotalUnretractedTime() - (expected.getTotalTime() - expected.getTotalUnretractedTime()) < (expected.getTotalTime() - expected.getTotalUnretractedTime()) * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Material used is " << observed.getMaterial() << " instead of the expected " << expected.getMaterial();
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getMaterial() - expected.getMaterial() < expected.getMaterial() * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
}

}<commit_msg>lil codestyle<commit_after>//Copyright (c) 2015 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include "GCodePlannerTest.h"

#include "../src/MeshGroup.h" //Needed to construct the GCodePlanner.

#define ALLOWED_ESTIMATE_ERROR 0.1 //Fraction of the time estimates that the estimate is allowed to be off from the ground truth.

namespace cura
{

CPPUNIT_TEST_SUITE_REGISTRATION(GCodePlannerTest);

void GCodePlannerTest::setUp()
{
    storage = new SliceDataStorage(nullptr); //Empty data.
    storage->retraction_config.speed = 1; //We'll set some of the configurations, just in order to get valid g-code (speed not zero, etc.)
    storage->retraction_config.primeSpeed = 1;
    storage->retraction_config.distance = 10;
    storage->travel_config.init(1, 1, 1, 1, 1);
    FanSpeedLayerTimeSettings fan_speed_layer_time_settings; //A dummy fan speed and layer time settings.
    fan_speed_layer_time_settings.cool_min_layer_time = 0;
    fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = 1;
    fan_speed_layer_time_settings.cool_fan_speed_min = 0;
    fan_speed_layer_time_settings.cool_fan_speed_max = 1;
    fan_speed_layer_time_settings.cool_min_speed = 0.5;
    //                              Slice     layer  z  layer   last        current   is inside  fan speed and layer            combing           comb    travel  travel avoid
    //                              storage   nr        height  position    extruder  mesh       time settings                  mode              offset  avoid   distance
    gCodePlanner = new GCodePlanner(*storage, 0,     0, 0.1,    Point(0,0), 0,        false,     fan_speed_layer_time_settings, CombingMode::OFF, 100,    false,  50          );
}

void GCodePlannerTest::tearDown()
{
    delete gCodePlanner;
    gCodePlanner = nullptr;
    delete storage;
    storage = nullptr;
}

void GCodePlannerTest::computeNaiveTimeEstimatesRetractionTest()
{
    TimeMaterialEstimates estimate_empty = gCodePlanner->computeNaiveTimeEstimates(); //First try estimating time and material without any content.
    TimeMaterialEstimates estimate_empty_expected(0,0,0,0); //We expect the estimate of all time and material used to be 0.
    verifyEstimates(estimate_empty,estimate_empty_expected,"Empty GCodePlanner");

    GCodeExport gcode;
    GCodePathConfig configuration = storage->travel_config;
    gCodePlanner->addExtrusionMove(Point(0, 0), &configuration, SpaceFillType::Lines, 1.0f); //Need to have at least one path to have a configuration.
    TimeMaterialEstimates before_retract = gCodePlanner->computeNaiveTimeEstimates();
    gCodePlanner->writeRetraction(gcode,(unsigned int)0,(unsigned int)0); //Make a retract.
    TimeMaterialEstimates after_retract = gCodePlanner->computeNaiveTimeEstimates();
    TimeMaterialEstimates estimate_one_retraction = after_retract - before_retract;
    double retract_unretract_time = configuration.retraction_config->distance / configuration.retraction_config->primeSpeed;
    TimeMaterialEstimates estimate_one_retraction_expected(0,retract_unretract_time * 0.5,retract_unretract_time * 0.5,0);
    verifyEstimates(estimate_one_retraction,estimate_one_retraction_expected,"One retraction");
}

void GCodePlannerTest::verifyEstimates(const TimeMaterialEstimates& observed,const TimeMaterialEstimates& expected,std::string test_description)
{
    //Check each of the four estimates in the TimeMaterialEstimate instances.
    {
        std::stringstream ss;
        ss << test_description << ": Extrude time is " << observed.getExtrudeTime() << " instead of the expected " << expected.getExtrudeTime();
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getExtrudeTime() - expected.getExtrudeTime() < expected.getExtrudeTime() * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Unretracted travel time is " << (observed.getTotalUnretractedTime() - observed.getExtrudeTime()) << " instead of the expected " << (expected.getTotalUnretractedTime() - expected.getExtrudeTime());
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getTotalUnretractedTime() - observed.getExtrudeTime() - (expected.getTotalUnretractedTime() - expected.getExtrudeTime()) < (expected.getTotalUnretractedTime() - expected.getExtrudeTime()) * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Retracted travel time is " << (observed.getTotalTime() - observed.getTotalUnretractedTime()) << " instead of the expected " << (expected.getTotalTime() - expected.getTotalUnretractedTime());
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getTotalTime() - observed.getTotalUnretractedTime() - (expected.getTotalTime() - expected.getTotalUnretractedTime()) < (expected.getTotalTime() - expected.getTotalUnretractedTime()) * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
    {
        std::stringstream ss;
        ss << test_description << ": Material used is " << observed.getMaterial() << " instead of the expected " << expected.getMaterial();
        CPPUNIT_ASSERT_MESSAGE(ss.str(),observed.getMaterial() - expected.getMaterial() < expected.getMaterial() * ALLOWED_ESTIMATE_ERROR + 0.001);
    }
}

}<|endoftext|>


<commit_before>#ifndef diff_hh_INCLUDED
#define diff_hh_INCLUDED

// Implementation of the linear space variant of the algorithm described in
// "An O(ND) Difference Algorithm and Its Variations"
// (http://xmailserver.org/diff2.pdf)

#include "array_view.hh"
#include "vector.hh"

#include <functional>
#include <iterator>

namespace Kakoune
{

struct Snake{ int x, y, u, v; bool add; };

template<typename Iterator, typename Equal>
Snake find_end_snake_of_further_reaching_dpath(Iterator a, int N, Iterator b, int M,
                                               const int* V,
                                               const int D, const int k, Equal eq)
{
    const bool add = k == -D or (k != D and V[k-1] < V[k+1]);

    // if diagonal on the right goes further along x than diagonal on the left,
    // then we take a vertical edge from it to this diagonal, hence x = V[k+1]
    // else, we take an horizontal edge from our left diagonal,x = V[k-1]+1
    const int x = add ? V[k+1] : V[k-1]+1;
    // we are by construction on diagonal k, so our position along b (y) is x - k.
    const int y = x - k;

    int u = x, v = y;
    // follow end snake along diagonal k
    while (u < N and v < M and eq(a[u], b[v]))
        ++u, ++v;

    return { x, y, u, v, add };
}

struct SnakeLen : Snake
{
    SnakeLen(Snake s, int d) : Snake(s), d(d) {}
    int d;
};

template<typename Iterator, typename Equal>
SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M,
                           int* V1, int* V2, Equal eq)
{
    const int delta = N - M;
    V1[1] = 0;
    V2[1] = 0;

    std::reverse_iterator<Iterator> ra{a + N}, rb{b + M};

    for (int D = 0; D <= (M + N + 1) / 2; ++D)
    {
        for (int k1 = -D; k1 <= D; k1 += 2)
        {
            auto p = find_end_snake_of_further_reaching_dpath(a, N, b, M, V1, D, k1, eq);
            V1[k1] = p.u;

            const int k2 = -(k1 - delta);
            if ((delta % 2 != 0) and -(D-1) <= k2 and k2 <= (D-1))
            {
                if (V1[k1] + V2[k2] >= N)
                    return { p, 2 * D - 1 };// return last snake on forward path
            }
        }

        for (int k2 = -D; k2 <= D; k2 += 2)
        {
            auto p = find_end_snake_of_further_reaching_dpath(ra, N, rb, M, V2, D, k2, eq);
            V2[k2] = p.u;

            const int k1 = -(k2 - delta);
            if ((delta % 2 == 0) and -D <= k1 and k1 <= D)
            {
                if (V1[k1] + V2[k2] >= N)
                    return { { N - p.u, M - p.v, N - p.x , M - p.y, p.add } , 2 * D };// return last snake on reverse path
            }
        }
    }

    kak_assert(false);
    return { {}, 0 };
}

struct Diff
{
    enum { Keep, Add, Remove } mode;
    int len;
    int posB;
};

inline void append_diff(Vector<Diff>& diffs, Diff diff)
{
    if (diff.len == 0)
        return;

    if (not diffs.empty() and diffs.back().mode == diff.mode
        and (diff.mode != Diff::Add or
             diffs.back().posB + diffs.back().len == diff.posB))
        diffs.back().len += diff.len;
    else
        diffs.push_back(diff);
}

template<typename Iterator, typename Equal>
void find_diff_rec(Iterator a, int offA, int lenA,
                   Iterator b, int offB, int lenB,
                   int* V1, int* V2, Equal eq, Vector<Diff>& diffs)
{
    int prefix_len = 0;
    while (lenA > 0 and lenB > 0 and a[offA] == b[offB])
         ++offA, ++offB, --lenA, --lenB, ++prefix_len;

    int suffix_len = 0;
    while (lenA != 0 and lenB != 0 and a[offA + lenA - 1] == b[offB + lenB - 1])
        --lenA, --lenB, ++suffix_len;

    append_diff(diffs, {Diff::Keep, prefix_len, 0});

    if (lenA == 0)
        append_diff(diffs, {Diff::Add, lenB, offB});
    else if (lenB == 0)
        append_diff(diffs, {Diff::Remove, lenA, 0});
    else
    {
        auto middle_snake = find_middle_snake(a + offA, lenA, b + offB, lenB, V1, V2, eq);
        kak_assert(middle_snake.u <= lenA and middle_snake.v <= lenB);
        if (middle_snake.d > 1)
        {
            find_diff_rec(a, offA, middle_snake.x,
                          b, offB, middle_snake.y,
                          V1, V2, eq, diffs);

            append_diff(diffs, {Diff::Keep, middle_snake.u - middle_snake.x, 0});

            find_diff_rec(a, offA + middle_snake.u, lenA - middle_snake.u,
                          b, offB + middle_snake.v, lenB - middle_snake.v,
                          V1, V2, eq, diffs);
        }
        else
        {
            if (middle_snake.d == 1)
            {
                const int diag = middle_snake.x - (middle_snake.add ? 0 : 1);
                append_diff(diffs, {Diff::Keep, diag, 0});

                if (middle_snake.add)
                    append_diff(diffs, {Diff::Add, 1, offB + diag});
                else
                    append_diff(diffs, {Diff::Remove, 1, 0});
            }
            append_diff(diffs, {Diff::Keep, middle_snake.u - middle_snake.x, 0});
        }
    }

    append_diff(diffs, {Diff::Keep, suffix_len, 0});
}

template<typename Iterator, typename Equal = std::equal_to<typename std::iterator_traits<Iterator>::value_type>>
Vector<Diff> find_diff(Iterator a, int N, Iterator b, int M, Equal eq = Equal{})
{
    const int max = 2 * (N + M) + 1;
    Vector<int> data(2*max);
    Vector<Diff> diffs;
    find_diff_rec(a, 0, N, b, 0, M,
                  data.data() + (N+M), data.data() + max + (N+M),
                  eq, diffs);

    return diffs;
}

}

#endif // diff_hh_INCLUDED
<commit_msg>Slight style change<commit_after>#ifndef diff_hh_INCLUDED
#define diff_hh_INCLUDED

// Implementation of the linear space variant of the algorithm described in
// "An O(ND) Difference Algorithm and Its Variations"
// (http://xmailserver.org/diff2.pdf)

#include "array_view.hh"
#include "vector.hh"

#include <functional>
#include <iterator>

namespace Kakoune
{

struct Snake{ int x, y, u, v; bool add; };

template<typename Iterator, typename Equal>
Snake find_end_snake_of_further_reaching_dpath(Iterator a, int N, Iterator b, int M,
                                               const int* V,
                                               const int D, const int k, Equal eq)
{
    const bool add = k == -D or (k != D and V[k-1] < V[k+1]);

    // if diagonal on the right goes further along x than diagonal on the left,
    // then we take a vertical edge from it to this diagonal, hence x = V[k+1]
    // else, we take an horizontal edge from our left diagonal,x = V[k-1]+1
    const int x = add ? V[k+1] : V[k-1]+1;
    // we are by construction on diagonal k, so our position along b (y) is x - k.
    const int y = x - k;

    int u = x, v = y;
    // follow end snake along diagonal k
    while (u < N and v < M and eq(a[u], b[v]))
        ++u, ++v;

    return { x, y, u, v, add };
}

struct SnakeLen : Snake
{
    SnakeLen(Snake s, int d) : Snake(s), d(d) {}
    int d;
};

template<typename Iterator, typename Equal>
SnakeLen find_middle_snake(Iterator a, int N, Iterator b, int M,
                           int* V1, int* V2, Equal eq)
{
    const int delta = N - M;
    V1[1] = 0;
    V2[1] = 0;

    std::reverse_iterator<Iterator> ra{a + N}, rb{b + M};

    for (int D = 0; D <= (M + N + 1) / 2; ++D)
    {
        for (int k1 = -D; k1 <= D; k1 += 2)
        {
            auto p = find_end_snake_of_further_reaching_dpath(a, N, b, M, V1, D, k1, eq);
            V1[k1] = p.u;

            const int k2 = -(k1 - delta);
            if ((delta % 2 != 0) and -(D-1) <= k2 and k2 <= (D-1))
            {
                if (V1[k1] + V2[k2] >= N)
                    return { p, 2 * D - 1 };// return last snake on forward path
            }
        }

        for (int k2 = -D; k2 <= D; k2 += 2)
        {
            auto p = find_end_snake_of_further_reaching_dpath(ra, N, rb, M, V2, D, k2, eq);
            V2[k2] = p.u;

            const int k1 = -(k2 - delta);
            if ((delta % 2 == 0) and -D <= k1 and k1 <= D)
            {
                if (V1[k1] + V2[k2] >= N)
                    return { { N - p.u, M - p.v, N - p.x , M - p.y, p.add } , 2 * D };// return last snake on reverse path
            }
        }
    }

    kak_assert(false);
    return { {}, 0 };
}

struct Diff
{
    enum { Keep, Add, Remove } mode;
    int len;
    int posB;
};

inline void append_diff(Vector<Diff>& diffs, Diff diff)
{
    if (diff.len == 0)
        return;

    if (not diffs.empty() and diffs.back().mode == diff.mode
        and (diff.mode != Diff::Add or
             diffs.back().posB + diffs.back().len == diff.posB))
        diffs.back().len += diff.len;
    else
        diffs.push_back(diff);
}

template<typename Iterator, typename Equal>
void find_diff_rec(Iterator a, int offA, int lenA,
                   Iterator b, int offB, int lenB,
                   int* V1, int* V2, Equal eq, Vector<Diff>& diffs)
{
    int prefix_len = 0;
    while (lenA > 0 and lenB > 0 and a[offA] == b[offB])
         ++offA, ++offB, --lenA, --lenB, ++prefix_len;

    int suffix_len = 0;
    while (lenA != 0 and lenB != 0 and a[offA + lenA - 1] == b[offB + lenB - 1])
        --lenA, --lenB, ++suffix_len;

    append_diff(diffs, {Diff::Keep, prefix_len, 0});

    if (lenA == 0)
        append_diff(diffs, {Diff::Add, lenB, offB});
    else if (lenB == 0)
        append_diff(diffs, {Diff::Remove, lenA, 0});
    else
    {
        auto middle_snake = find_middle_snake(a + offA, lenA, b + offB, lenB, V1, V2, eq);
        kak_assert(middle_snake.u <= lenA and middle_snake.v <= lenB);
        if (middle_snake.d > 1)
        {
            find_diff_rec(a, offA, middle_snake.x,
                          b, offB, middle_snake.y,
                          V1, V2, eq, diffs);

            append_diff(diffs, {Diff::Keep, middle_snake.u - middle_snake.x, 0});

            find_diff_rec(a, offA + middle_snake.u, lenA - middle_snake.u,
                          b, offB + middle_snake.v, lenB - middle_snake.v,
                          V1, V2, eq, diffs);
        }
        else
        {
            if (middle_snake.d == 1)
            {
                const int diag = middle_snake.x - (middle_snake.add ? 0 : 1);
                append_diff(diffs, {Diff::Keep, diag, 0});

                if (middle_snake.add)
                    append_diff(diffs, {Diff::Add, 1, offB + diag});
                else
                    append_diff(diffs, {Diff::Remove, 1, 0});
            }
            append_diff(diffs, {Diff::Keep, middle_snake.u - middle_snake.x, 0});
        }
    }

    append_diff(diffs, {Diff::Keep, suffix_len, 0});
}

template<typename Iterator, typename Equal = std::equal_to<typename std::iterator_traits<Iterator>::value_type>>
Vector<Diff> find_diff(Iterator a, int N, Iterator b, int M, Equal eq = Equal{})
{
    const int max = 2 * (N + M) + 1;
    Vector<int> data(2*max);
    Vector<Diff> diffs;
    find_diff_rec(a, 0, N, b, 0, M, &data[N+M], &data[max + N+M], eq, diffs);

    return diffs;
}

}

#endif // diff_hh_INCLUDED
<|endoftext|>


<commit_before>/* -------------------------------------------------------------------------- *
 *                             OpenSim:  Body.cpp                             *
 * -------------------------------------------------------------------------- *
 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
 * See http://opensim.stanford.edu and the NOTICE file for more information.  *
 * OpenSim is developed at Stanford University and supported by the US        *
 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
 * through the Warrior Web program.                                           *
 *                                                                            *
 * Copyright (c) 2005-2017 Stanford University and the Authors                *
 * Author(s): Frank C. Anderson, Ajay Seth                                    *
 *                                                                            *
 * 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.                                             *
 * -------------------------------------------------------------------------- */

//=============================================================================
// INCLUDES
//=============================================================================
#include "Body.h"

//=============================================================================
// STATICS
//=============================================================================
using namespace std;
//using namespace SimTK;
using namespace OpenSim;
using SimTK::Mat33;
using SimTK::Vec3;
using SimTK::DecorativeGeometry;

//=============================================================================
// CONSTRUCTOR(S)
//=============================================================================
//_____________________________________________________________________________
/**
 * Default constructor.
 */
Body::Body() : PhysicalFrame()
{
    constructProperties();
}

//_____________________________________________________________________________
/**
 * Constructor.
 */
Body::Body(const std::string &aName,double aMass,const SimTK::Vec3& aMassCenter,const SimTK::Inertia& aInertia) :
    PhysicalFrame()
{
    constructProperties();
    setName(aName);
    set_mass(aMass);
    set_mass_center(aMassCenter);
    setInertia(aInertia);
}

//_____________________________________________________________________________
/**
 * Connect properties to local pointers.
 */
void Body::constructProperties()
{
    constructProperty_mass(SimTK::NaN);
    constructProperty_mass_center(SimTK::Vec3(0));
    constructProperty_inertia(SimTK::Vec6(0));
}

void Body::extendFinalizeFromProperties()
{
    Super::extendFinalizeFromProperties();
    const SimTK::MassProperties& massProps = getMassProperties();
    _internalRigidBody = SimTK::Body::Rigid(massProps);
}

//_____________________________________________________________________________
/* Connect this Body to the Model it belongs to
 *
 * @param aModel OpenSim model containing this Body.
 */
void Body::extendConnectToModel(Model& aModel)
{
    Super::extendConnectToModel(aModel);

    int nslaves = (int)_slaves.size();

    if (nslaves){
        int nbods = nslaves + 1; // include the master
        const SimTK::MassProperties& massProps = getMassProperties();
        SimTK::MassProperties slaveMassProps(massProps.getMass() / nbods,
            massProps.getMassCenter(), massProps.getUnitInertia());

        // update the portion taken on by the master
        _internalRigidBody = SimTK::Body::Rigid(slaveMassProps);

        // and the slaves
        for (int i = 0; i < nslaves; ++i){
            _slaves[i]->_internalRigidBody = SimTK::Body::Rigid(slaveMassProps);
            _slaves[i]->setInertia(slaveMassProps.getUnitInertia());
            _slaves[i]->setMass(slaveMassProps.getMass());
            _slaves[i]->setMassCenter(slaveMassProps.getMassCenter());
        }
    }
}

//=============================================================================
// GET AND SET
//=============================================================================

//_____________________________________________________________________________
/**
 * Get the inertia matrix of the body.
 *
 * @return 3x3 inertia matrix.
 */
const SimTK::Inertia& Body::getInertia() const
{
    // Has not been set programmatically
    if (_inertia.isNaN()){
        // initialize from properties
        const double& m = getMass();
        // if mass is zero, non-zero inertia makes no sense
        if (-SimTK::SignificantReal <= m && m <= SimTK::SignificantReal){
            // force zero inertia
            cout<<"Body '"<<getName()<<"' is massless but nonzero inertia provided.";
            cout<<" Inertia reset to zero. "<<"Otherwise provide nonzero mass."<< endl;
            _inertia = SimTK::Inertia(0);
        }
        else{
            const SimTK::Vec6& Ivec = get_inertia();
            try {
                _inertia = SimTK::Inertia(Ivec.getSubVec<3>(0), Ivec.getSubVec<3>(3));
            }
            catch (const std::exception& ex){
                // Should throw an Exception but we have models we have released with
                // bad inertias. E.g. early gait23 models had an error in the inertia
                // of the toes Body. We cannot allow failures with our models so
                // raise a warning and do something sensible with the values at hand.
                cout << "WARNING: Body " + getName() + " has invalid inertia. " << endl;
                cout << ex.what() << endl;

                // get some aggregate value for the inertia based on existing values
                double diag = Ivec.getSubVec<3>(0).norm()/sqrt(3.0);

                // and then assume a spherical shape.
                _inertia = SimTK::Inertia(Vec3(diag), Vec3(0));

                cout << getName() << " Body's inertia being reset to:" << endl;
                cout << _inertia << endl;
            }
        }
    }
    return _inertia;
}
//_____________________________________________________________________________
/**
 * Set the inertia matrix of the body.
 *
 * @param aInertia 9-element inertia matrix.
 */
void Body::setInertia(const SimTK::Inertia& inertia)
{
    _inertia = inertia;
    const SimTK::SymMat33& I = _inertia.asSymMat33();
    upd_inertia()[0] = I[0][0];
    upd_inertia()[1] = I[1][1];
    upd_inertia()[2] = I[2][2];
    upd_inertia()[3] = I[0][1];
    upd_inertia()[4] = I[0][2];
    upd_inertia()[5] = I[1][2];
}

//=============================================================================
// SCALING
//=============================================================================
//_____________________________________________________________________________
/**
 * Scale the body.
 *
 * @param aScaleFactors XYZ scale factors.
 * @param aScaleMass whether or not to scale mass properties
 */
void Body::scale(const SimTK::Vec3& aScaleFactors, bool aScaleMass)
{
    Super::scale(aScaleFactors);
    for(int i=0; i<3; i++) {
        upd_mass_center()[i] *= aScaleFactors[i];
    }

        scaleInertialProperties(aScaleFactors, aScaleMass);
}

//_____________________________________________________________________________
/**
 * Scale the body's mass and inertia tensor.
 *
 * @param aScaleFactors XYZ scale factors.
 * @param aScaleMass Whether or not to scale the mass
 */
void Body::scaleInertialProperties(const SimTK::Vec3& aScaleFactors, bool aScaleMass)
{
    // Save the unscaled mass for possible use later.
    double unscaledMass = get_mass();

    // Calculate and store the product of the scale factors.
    double massScaleFactor = abs(aScaleFactors[0] * aScaleFactors[1] * aScaleFactors[2]);

    // Scale the mass.
    if (aScaleMass)
        upd_mass() *= massScaleFactor;

    SimTK::SymMat33 inertia = _inertia.asSymMat33();

    // If the mass is zero, then make the inertia tensor zero as well.
    // If the X, Y, Z scale factors are equal, then you can scale the
    // inertia tensor exactly by the square of the scale factor (and
    // possibly by massScaleFactor), since each element in the tensor
    // is proportional to the square of one or more dimensional
    // measurements. For determining if the scale factors are equal,
    // ignore reflections-- look only at the absolute value of the factors.
    if (get_mass() <= SimTK::Eps) {
        inertia *= 0.0;
    }
    else if (SimTK::isNumericallyEqual(aScaleFactors[0], aScaleFactors[1])
             && SimTK::isNumericallyEqual(aScaleFactors[1], aScaleFactors[2])) {
        // If the mass is also being scaled, scale the inertia terms by massScaleFactor.
        if (aScaleMass) {
            inertia *= massScaleFactor;
        }

        // Now scale by the length-squared component.
        inertia *= (aScaleFactors[0] * aScaleFactors[0]);

    } else {
        // If the scale factors are not equal, then assume that the segment
        // is a cylinder and the inertia is calculated about one end of it.
        int axis;

        // 1. Find the smallest diagonal component. This dimension is the axis
        //    of the cylinder.
        if (inertia[0][0] <= inertia[1][1]){
            if (inertia[0][0] <= inertia[2][2])
                axis = 0;
            else
                axis = 2;

        } else if (inertia[1][1] <= inertia[2][2]) {
            axis = 1;

        } else {
            axis = 2;
        }

        // 2. The smallest inertia component is equal to 0.5 * mass * radius * radius,
        //    so you can rearrange and solve for the radius.
        int oa;
        double radius, rad_sqr, length;
        double term = 2.0 * inertia[axis][axis] / unscaledMass;
        if (term < 0.0)
            radius = 0.0;
        else
            radius = sqrt(term);

        // 3. Choose either of the other diagonal components and use it to solve for the
        //    length of the cylinder. This component is equal to:
        //    0.083 * mass * length * length  +  0.25 * mass * radius * radius
        if (axis == 0)
            oa = 1;
        else
            oa = 0;
        term = 12.0 * (inertia[oa][oa] - 0.25 * unscaledMass * radius * radius) / unscaledMass;
        if (term < 0.0)
            length = 0.0;
        else
            length = sqrt(term);

        // 4. Scale the radius and length, and recalculate the diagonal inertia terms.
        length *= aScaleFactors[axis];

        if (axis == 0) {
            rad_sqr = radius * (aScaleFactors[1]) * radius * (aScaleFactors[2]);
            inertia[0][0] = 0.5 * get_mass() * rad_sqr;
            inertia[1][1] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[2][2] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);

        } else if (axis == 1) {
            rad_sqr = radius * (aScaleFactors[0]) * radius * (aScaleFactors[2]);
            inertia[0][0] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[1][1] = 0.5 * get_mass() * rad_sqr;
            inertia[2][2] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);

        } else {
            rad_sqr = radius * (aScaleFactors[0]) * radius * (aScaleFactors[1]);
            inertia[0][0] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[1][1] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[2][2] = 0.5 * get_mass() * rad_sqr;
        }

        // 5. Scale the inertia products, in case some are non-zero. These are scaled by
        //    two scale factors for the length term (which two depend on the inertia term
        //    being scaled), and, if the mass is also scaled, by massScaleFactor.
        inertia[0][1] *= ((aScaleFactors[0] * aScaleFactors[1]));
        inertia[0][2] *= ((aScaleFactors[0] * aScaleFactors[2]));
        inertia[1][0] *= ((aScaleFactors[1] * aScaleFactors[0]));
        inertia[1][2] *= ((aScaleFactors[1] * aScaleFactors[2]));
        inertia[2][0] *= ((aScaleFactors[2] * aScaleFactors[0]));
        inertia[2][1] *= ((aScaleFactors[2] * aScaleFactors[1]));

        if (aScaleMass) {
            inertia[0][1] *= massScaleFactor;
            inertia[0][2] *= massScaleFactor;
            inertia[1][0] *= massScaleFactor;
            inertia[1][2] *= massScaleFactor;
            inertia[2][0] *= massScaleFactor;
            inertia[2][1] *= massScaleFactor;
        }
    }

    setInertia(SimTK::Inertia(inertia));
}

//_____________________________________________________________________________
/**
 * Scale the body's mass and inertia tensor (represents a scaling of the
 * body's density).
 *
 * @param aScaleFactors XYZ scale factors.
 */
void Body::scaleMass(double aScaleFactor)
{
    upd_mass() *= aScaleFactor;
    _inertia *= aScaleFactor;
    upd_inertia() *= aScaleFactor;
}

//=============================================================================
// UTILITY
//=============================================================================
SimTK::MassProperties Body::getMassProperties() const
{
    const double& m = get_mass();
    const Vec3& com = get_mass_center();

    try{
        const SimTK::Inertia& Icom = getInertia();

        SimTK::Inertia Ib = Icom;
        // If com and body, b, frame are coincident then don't bother shifting
        if (com.norm() > SimTK::Eps) {
            // shift if com has nonzero distance from b
            Ib = Icom.shiftFromMassCenter(com, m);
        }

        return SimTK::MassProperties(m, com, Ib);
    }
    catch (const std::exception& ex) {
        string msg = "Body " + getName() + " has invalid mass properties. ";
        msg += ex.what();
        throw Exception(msg, __FILE__, __LINE__);
    }
}

//=============================================================================
// I/O
//=============================================================================

void Body::updateFromXMLNode(SimTK::Xml::Element& aNode, int versionNumber)
{
    if (versionNumber < XMLDocument::getLatestVersion()) {
        if (versionNumber < 30500) {
            SimTK::Vec6 newInertia(1.0, 1.0, 1.0, 0., 0., 0.);
            std::string inertiaComponents[] = { "inertia_xx", "inertia_yy", "inertia_zz", "inertia_xy", "inertia_xz", "inertia_yz" };
            for (int i = 0; i < 6; ++i) {
                SimTK::Xml::element_iterator iIter = aNode.element_begin(inertiaComponents[i]);
                if (iIter != aNode.element_end()) {
                    newInertia[i] = iIter->getValueAs<double>();
                    aNode.removeNode(iIter);
                }
            }
            std::ostringstream strs;
            for (int i = 0; i < 6; ++i) {
                strs << newInertia[i];
                if (i < 5) strs << " ";
            }
            std::string strInertia = strs.str();
            SimTK::Xml::Element inertiaNode("inertia", strInertia);
            aNode.insertNodeAfter(aNode.element_end(), inertiaNode);
        }
    }
    Super::updateFromXMLNode(aNode, versionNumber);
}

Body* Body::addSlave()
{
    Body* slave = new Body();
    int count = (int)_slaves.size();

    stringstream name;
    name << getName() << "_slave_" << count;
    slave->setName(name.str());

    //add to internal list of references
    _slaves.push_back(SimTK::ReferencePtr<Body>(slave));

    //add to list of subcomponents to automatically add to system and initialize
    adoptSubcomponent(slave);

    return slave;
}
<commit_msg>Improve warning about nonzero inertia.<commit_after>/* -------------------------------------------------------------------------- *
 *                             OpenSim:  Body.cpp                             *
 * -------------------------------------------------------------------------- *
 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
 * See http://opensim.stanford.edu and the NOTICE file for more information.  *
 * OpenSim is developed at Stanford University and supported by the US        *
 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
 * through the Warrior Web program.                                           *
 *                                                                            *
 * Copyright (c) 2005-2017 Stanford University and the Authors                *
 * Author(s): Frank C. Anderson, Ajay Seth                                    *
 *                                                                            *
 * 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.                                             *
 * -------------------------------------------------------------------------- */

//=============================================================================
// INCLUDES
//=============================================================================
#include "Body.h"

//=============================================================================
// STATICS
//=============================================================================
using namespace std;
//using namespace SimTK;
using namespace OpenSim;
using SimTK::Mat33;
using SimTK::Vec3;
using SimTK::DecorativeGeometry;

//=============================================================================
// CONSTRUCTOR(S)
//=============================================================================
//_____________________________________________________________________________
/**
 * Default constructor.
 */
Body::Body() : PhysicalFrame()
{
    constructProperties();
}

//_____________________________________________________________________________
/**
 * Constructor.
 */
Body::Body(const std::string &aName,double aMass,const SimTK::Vec3& aMassCenter,const SimTK::Inertia& aInertia) :
    PhysicalFrame()
{
    constructProperties();
    setName(aName);
    set_mass(aMass);
    set_mass_center(aMassCenter);
    setInertia(aInertia);
}

//_____________________________________________________________________________
/**
 * Connect properties to local pointers.
 */
void Body::constructProperties()
{
    constructProperty_mass(SimTK::NaN);
    constructProperty_mass_center(SimTK::Vec3(0));
    constructProperty_inertia(SimTK::Vec6(0));
}

void Body::extendFinalizeFromProperties()
{
    Super::extendFinalizeFromProperties();
    const SimTK::MassProperties& massProps = getMassProperties();
    _internalRigidBody = SimTK::Body::Rigid(massProps);
}

//_____________________________________________________________________________
/* Connect this Body to the Model it belongs to
 *
 * @param aModel OpenSim model containing this Body.
 */
void Body::extendConnectToModel(Model& aModel)
{
    Super::extendConnectToModel(aModel);

    int nslaves = (int)_slaves.size();

    if (nslaves){
        int nbods = nslaves + 1; // include the master
        const SimTK::MassProperties& massProps = getMassProperties();
        SimTK::MassProperties slaveMassProps(massProps.getMass() / nbods,
            massProps.getMassCenter(), massProps.getUnitInertia());

        // update the portion taken on by the master
        _internalRigidBody = SimTK::Body::Rigid(slaveMassProps);

        // and the slaves
        for (int i = 0; i < nslaves; ++i){
            _slaves[i]->_internalRigidBody = SimTK::Body::Rigid(slaveMassProps);
            _slaves[i]->setInertia(slaveMassProps.getUnitInertia());
            _slaves[i]->setMass(slaveMassProps.getMass());
            _slaves[i]->setMassCenter(slaveMassProps.getMassCenter());
        }
    }
}

//=============================================================================
// GET AND SET
//=============================================================================

//_____________________________________________________________________________
/**
 * Get the inertia matrix of the body.
 *
 * @return 3x3 inertia matrix.
 */
const SimTK::Inertia& Body::getInertia() const
{
    // Has not been set programmatically
    if (_inertia.isNaN()){
        // initialize from properties
        const double& m = getMass();
        const SimTK::Vec6& Ivec = get_inertia();
        // if mass is zero, non-zero inertia makes no sense
        if (std::abs(m) <= SimTK::SignificantReal &&
                Ivec.norm() > SimTK::SignificantReal) {
            // force zero inertia
            cout<<"Body '"<<getName()<<"' is massless but nonzero inertia provided.";
            cout<<" Inertia reset to zero. "<<"Otherwise provide nonzero mass."<< endl;
            _inertia = SimTK::Inertia(0);
        }
        else{
            try {
                _inertia = SimTK::Inertia(Ivec.getSubVec<3>(0), Ivec.getSubVec<3>(3));
            }
            catch (const std::exception& ex){
                // Should throw an Exception but we have models we have released with
                // bad inertias. E.g. early gait23 models had an error in the inertia
                // of the toes Body. We cannot allow failures with our models so
                // raise a warning and do something sensible with the values at hand.
                cout << "WARNING: Body " + getName() + " has invalid inertia. " << endl;
                cout << ex.what() << endl;

                // get some aggregate value for the inertia based on existing values
                double diag = Ivec.getSubVec<3>(0).norm()/sqrt(3.0);

                // and then assume a spherical shape.
                _inertia = SimTK::Inertia(Vec3(diag), Vec3(0));

                cout << getName() << " Body's inertia being reset to:" << endl;
                cout << _inertia << endl;
            }
        }
    }
    return _inertia;
}
//_____________________________________________________________________________
/**
 * Set the inertia matrix of the body.
 *
 * @param aInertia 9-element inertia matrix.
 */
void Body::setInertia(const SimTK::Inertia& inertia)
{
    _inertia = inertia;
    const SimTK::SymMat33& I = _inertia.asSymMat33();
    upd_inertia()[0] = I[0][0];
    upd_inertia()[1] = I[1][1];
    upd_inertia()[2] = I[2][2];
    upd_inertia()[3] = I[0][1];
    upd_inertia()[4] = I[0][2];
    upd_inertia()[5] = I[1][2];
}

//=============================================================================
// SCALING
//=============================================================================
//_____________________________________________________________________________
/**
 * Scale the body.
 *
 * @param aScaleFactors XYZ scale factors.
 * @param aScaleMass whether or not to scale mass properties
 */
void Body::scale(const SimTK::Vec3& aScaleFactors, bool aScaleMass)
{
    Super::scale(aScaleFactors);
    for(int i=0; i<3; i++) {
        upd_mass_center()[i] *= aScaleFactors[i];
    }

    scaleInertialProperties(aScaleFactors, aScaleMass);
}

//_____________________________________________________________________________
/**
 * Scale the body's mass and inertia tensor.
 *
 * @param aScaleFactors XYZ scale factors.
 * @param aScaleMass Whether or not to scale the mass
 */
void Body::scaleInertialProperties(const SimTK::Vec3& aScaleFactors, bool aScaleMass)
{
    // Save the unscaled mass for possible use later.
    double unscaledMass = get_mass();

    // Calculate and store the product of the scale factors.
    double massScaleFactor = abs(aScaleFactors[0] * aScaleFactors[1] * aScaleFactors[2]);

    // Scale the mass.
    if (aScaleMass)
        upd_mass() *= massScaleFactor;

    SimTK::SymMat33 inertia = _inertia.asSymMat33();

    // If the mass is zero, then make the inertia tensor zero as well.
    // If the X, Y, Z scale factors are equal, then you can scale the
    // inertia tensor exactly by the square of the scale factor (and
    // possibly by massScaleFactor), since each element in the tensor
    // is proportional to the square of one or more dimensional
    // measurements. For determining if the scale factors are equal,
    // ignore reflections-- look only at the absolute value of the factors.
    if (get_mass() <= SimTK::Eps) {
        inertia *= 0.0;
    }
    else if (SimTK::isNumericallyEqual(aScaleFactors[0], aScaleFactors[1])
             && SimTK::isNumericallyEqual(aScaleFactors[1], aScaleFactors[2])) {
        // If the mass is also being scaled, scale the inertia terms by massScaleFactor.
        if (aScaleMass) {
            inertia *= massScaleFactor;
        }

        // Now scale by the length-squared component.
        inertia *= (aScaleFactors[0] * aScaleFactors[0]);

    } else {
        // If the scale factors are not equal, then assume that the segment
        // is a cylinder and the inertia is calculated about one end of it.
        int axis;

        // 1. Find the smallest diagonal component. This dimension is the axis
        //    of the cylinder.
        if (inertia[0][0] <= inertia[1][1]){
            if (inertia[0][0] <= inertia[2][2])
                axis = 0;
            else
                axis = 2;

        } else if (inertia[1][1] <= inertia[2][2]) {
            axis = 1;

        } else {
            axis = 2;
        }

        // 2. The smallest inertia component is equal to 0.5 * mass * radius * radius,
        //    so you can rearrange and solve for the radius.
        int oa;
        double radius, rad_sqr, length;
        double term = 2.0 * inertia[axis][axis] / unscaledMass;
        if (term < 0.0)
            radius = 0.0;
        else
            radius = sqrt(term);

        // 3. Choose either of the other diagonal components and use it to solve for the
        //    length of the cylinder. This component is equal to:
        //    0.083 * mass * length * length  +  0.25 * mass * radius * radius
        if (axis == 0)
            oa = 1;
        else
            oa = 0;
        term = 12.0 * (inertia[oa][oa] - 0.25 * unscaledMass * radius * radius) / unscaledMass;
        if (term < 0.0)
            length = 0.0;
        else
            length = sqrt(term);

        // 4. Scale the radius and length, and recalculate the diagonal inertia terms.
        length *= aScaleFactors[axis];

        if (axis == 0) {
            rad_sqr = radius * (aScaleFactors[1]) * radius * (aScaleFactors[2]);
            inertia[0][0] = 0.5 * get_mass() * rad_sqr;
            inertia[1][1] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[2][2] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);

        } else if (axis == 1) {
            rad_sqr = radius * (aScaleFactors[0]) * radius * (aScaleFactors[2]);
            inertia[0][0] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[1][1] = 0.5 * get_mass() * rad_sqr;
            inertia[2][2] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);

        } else {
            rad_sqr = radius * (aScaleFactors[0]) * radius * (aScaleFactors[1]);
            inertia[0][0] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[1][1] = get_mass() * ((length * length / 12.0) + 0.25 * rad_sqr);
            inertia[2][2] = 0.5 * get_mass() * rad_sqr;
        }

        // 5. Scale the inertia products, in case some are non-zero. These are scaled by
        //    two scale factors for the length term (which two depend on the inertia term
        //    being scaled), and, if the mass is also scaled, by massScaleFactor.
        inertia[0][1] *= ((aScaleFactors[0] * aScaleFactors[1]));
        inertia[0][2] *= ((aScaleFactors[0] * aScaleFactors[2]));
        inertia[1][0] *= ((aScaleFactors[1] * aScaleFactors[0]));
        inertia[1][2] *= ((aScaleFactors[1] * aScaleFactors[2]));
        inertia[2][0] *= ((aScaleFactors[2] * aScaleFactors[0]));
        inertia[2][1] *= ((aScaleFactors[2] * aScaleFactors[1]));

        if (aScaleMass) {
            inertia[0][1] *= massScaleFactor;
            inertia[0][2] *= massScaleFactor;
            inertia[1][0] *= massScaleFactor;
            inertia[1][2] *= massScaleFactor;
            inertia[2][0] *= massScaleFactor;
            inertia[2][1] *= massScaleFactor;
        }
    }

    setInertia(SimTK::Inertia(inertia));
}

//_____________________________________________________________________________
/**
 * Scale the body's mass and inertia tensor (represents a scaling of the
 * body's density).
 *
 * @param aScaleFactors XYZ scale factors.
 */
void Body::scaleMass(double aScaleFactor)
{
    upd_mass() *= aScaleFactor;
    _inertia *= aScaleFactor;
    upd_inertia() *= aScaleFactor;
}

//=============================================================================
// UTILITY
//=============================================================================
SimTK::MassProperties Body::getMassProperties() const
{
    const double& m = get_mass();
    const Vec3& com = get_mass_center();

    try{
        const SimTK::Inertia& Icom = getInertia();

        SimTK::Inertia Ib = Icom;
        // If com and body, b, frame are coincident then don't bother shifting
        if (com.norm() > SimTK::Eps) {
            // shift if com has nonzero distance from b
            Ib = Icom.shiftFromMassCenter(com, m);
        }

        return SimTK::MassProperties(m, com, Ib);
    }
    catch (const std::exception& ex) {
        string msg = "Body " + getName() + " has invalid mass properties. ";
        msg += ex.what();
        throw Exception(msg, __FILE__, __LINE__);
    }
}

//=============================================================================
// I/O
//=============================================================================

void Body::updateFromXMLNode(SimTK::Xml::Element& aNode, int versionNumber)
{
    if (versionNumber < XMLDocument::getLatestVersion()) {
        if (versionNumber < 30500) {
            SimTK::Vec6 newInertia(1.0, 1.0, 1.0, 0., 0., 0.);
            std::string inertiaComponents[] = { "inertia_xx", "inertia_yy", "inertia_zz", "inertia_xy", "inertia_xz", "inertia_yz" };
            for (int i = 0; i < 6; ++i) {
                SimTK::Xml::element_iterator iIter = aNode.element_begin(inertiaComponents[i]);
                if (iIter != aNode.element_end()) {
                    newInertia[i] = iIter->getValueAs<double>();
                    aNode.removeNode(iIter);
                }
            }
            std::ostringstream strs;
            for (int i = 0; i < 6; ++i) {
                strs << newInertia[i];
                if (i < 5) strs << " ";
            }
            std::string strInertia = strs.str();
            SimTK::Xml::Element inertiaNode("inertia", strInertia);
            aNode.insertNodeAfter(aNode.element_end(), inertiaNode);
        }
    }
    Super::updateFromXMLNode(aNode, versionNumber);
}

Body* Body::addSlave()
{
    Body* slave = new Body();
    int count = (int)_slaves.size();

    stringstream name;
    name << getName() << "_slave_" << count;
    slave->setName(name.str());

    //add to internal list of references
    _slaves.push_back(SimTK::ReferencePtr<Body>(slave));

    //add to list of subcomponents to automatically add to system and initialize
    adoptSubcomponent(slave);

    return slave;
}
<|endoftext|>