<commit_before>#include "ParallelHash.h"
#include "../Interface/Server.h"
#include "../Interface/ThreadPool.h"
#include "ClientHash.h"
#include <algorithm>
#include "database.h"
#include "../stringtools.h"

//#define HASH_CBT_CHECK

namespace
{
	const size_t max_modify_file_buffer_size = 2 * 1024 * 1024;
	const int64 file_buffer_commit_interval = 120 * 1000;
	const int64 link_file_min_size = 2048;
}

ParallelHash::ParallelHash(SQueueRef* phash_queue, int sha_version)
	: do_quit(false), phash_queue(phash_queue), phash_queue_pos(0),
	stdout_buf_size(0), stdout_buf_pos(0), mutex(Server->createMutex()),
	last_file_buffer_commit_time(0), sha_version(sha_version), eof(false)
{
	stdout_buf.resize(4090);
	ticket = Server->getThreadPool()->execute(this, "phash");
}

bool ParallelHash::getExitCode(int & exit_code)
{
	Server->getThreadPool()->waitFor(ticket);
	exit_code = 0;
	return true;
}

void ParallelHash::forceExit()
{
	do_quit = true;
	//Server->getThreadPool()->waitFor(ticket);
}

bool ParallelHash::readStdoutIntoBuffer(char * buf, size_t buf_avail, size_t & read_bytes)
{
	while(!do_quit)
	{
		IScopedLock lock(mutex.get());
		if (stdout_buf_size > stdout_buf_pos)
		{
			read_bytes = (std::min)(stdout_buf_size - stdout_buf_pos, buf_avail);
			memcpy(buf, &stdout_buf[stdout_buf_pos], read_bytes);
			stdout_buf_pos += read_bytes;

			if (stdout_buf_pos == stdout_buf_size)
			{
				stdout_buf_pos = 0;
				stdout_buf_size = 0;

				if (eof)
				{
					return false;
				}
			}

			return true;
		}
		else
		{
			if (stdout_buf_pos == stdout_buf_size
				&& eof)
			{
				return false;
			}

			lock.relock(NULL);
			Server->wait(1000);
		}
	}

	return false;
}

void ParallelHash::finishStdout()
{
}

bool ParallelHash::readStderrIntoBuffer(char * buf, size_t buf_avail, size_t & read_bytes)
{
	while (!do_quit
		&& !eof)
	{
		Server->wait(1000);
	}

	return false;
}

void ParallelHash::operator()()
{
	ClientDAO clientdao(Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT));

	int mode = MODE_READ_SEQUENTIAL;
#ifdef _WIN32
	mode = MODE_READ_DEVICE;
#endif
	std::auto_ptr<IFile> phashf(Server->openFile(phash_queue->phash_queue->getFilename(), mode));

	while (!do_quit
		&& phashf.get()!=NULL)
	{
		bool had_msg = false;
		if (phashf->Size() >= phash_queue_pos + static_cast<int64>(sizeof(_u32)))
		{
			_u32 msg_size;
			if (phashf->Read(phash_queue_pos, reinterpret_cast<char*>(&msg_size), sizeof(msg_size))
				== sizeof(msg_size))
			{
				if (phashf->Size() >= phash_queue_pos + static_cast<int64>(sizeof(_u32)) + msg_size)
				{
					had_msg = true;

					std::string msg = phashf->Read(phash_queue_pos + sizeof(_u32), msg_size);
					CRData data(msg.data(), msg.size());
					phash_queue_pos += sizeof(_u32) + msg_size;

					hashFile(data, clientdao);

					if (eof)
					{
						break;
					}
				}
			}
		}
		if (!had_msg)
		{
			Server->wait(1000);
			CWData data;
			data.addUShort(1);
			data.addChar(0);
			addToStdoutBuf(data.getDataPtr(), data.getDataSize());
		}
	}

	commitModifyFileBuffer(clientdao);

	if (phash_queue->deref())
	{
		delete phash_queue;
		phash_queue = NULL;
	}
}

bool ParallelHash::hashFile(CRData & data, ClientDAO& clientdao)
{
	char id;
	if (!data.getChar(&id))
		return false;

	if (id == ID_SET_CURR_DIRS)
	{
		curr_files.clear();

		if (!data.getStr2(&curr_dir)
			|| !data.getInt(&curr_tgroup)
			|| !data.getStr2(&curr_snapshot_dir))
		{
			return false;
		}

		return true;
	}
	else if (id == ID_FINISH_CURR_DIR)
	{
		int64 target_generation;
		if (!data.getVarInt(&target_generation))
		{
			curr_files.clear();
			return false;
		}

#ifndef _WIN32
		std::string path_lower = curr_dir + os_file_sep();
#else
		std::string path_lower = strlower(curr_dir + os_file_sep());
#endif

		std::vector<SFileAndHash> files;
		int64 generation = -1;
		if (clientdao.getFiles(path_lower, curr_tgroup, files, generation))
		{
			if (generation != target_generation)
			{
				curr_files.clear();
				return true;
			}

			std::sort(curr_files.begin(), curr_files.end());

			bool added_hash = false;
			for (size_t i = 0; i < files.size(); ++i)
			{
				if (files[i].hash.empty())
				{
					std::vector<SFileAndHash>::iterator it =
						std::lower_bound(curr_files.begin(), curr_files.end(), files[i]);
					if (it != curr_files.end()
						&& it->name == files[i].name)
					{
						files[i].hash = it->hash;
						added_hash = true;
					}
				}
			}

			if (added_hash)
			{
				addModifyFileBuffer(clientdao, path_lower, curr_tgroup, files, target_generation);
			}

			curr_files.clear();
			return true;
		}

		curr_files.clear();
		return false;
	}
	else if (id == ID_INIT_HASH)
	{
		client_hash.reset(new ClientHash(NULL, false, 0, NULL, 0));
		return true;
	}
	else if (id == ID_CBT_DATA)
	{
		IFile* index_hdat_file;
		int64 index_hdat_fs_block_size;
		size_t* snapshot_sequence_id;
		int64 snapshot_sequence_id_reference;
		if (!data.getVoidPtr(reinterpret_cast<void**>(&index_hdat_file))
			|| !data.getVarInt(&index_hdat_fs_block_size)
			|| !data.getVoidPtr(reinterpret_cast<void**>(&snapshot_sequence_id))
			|| !data.getVarInt(&snapshot_sequence_id_reference))
		{
			return false;
		}

		client_hash.reset(new ClientHash(index_hdat_file, true, index_hdat_fs_block_size,
			snapshot_sequence_id, static_cast<size_t>(snapshot_sequence_id_reference)));
		return true;
	}
	else if (id == ID_PHASH_FINISH)
	{
		eof = true;
		return true;
	}

	if (id != ID_HASH_FILE)
	{
		return false;
	}

	int64 file_id;
	if (!data.getVarInt(&file_id))
	{
		return false;
	}

	std::string fn;
	if (!data.getStr2(&fn))
	{
		return false;
	}

	std::string full_path = curr_snapshot_dir + os_file_sep() + fn;

	std::auto_ptr<IFsFile>  f(Server->openFile(os_file_prefix(fn), MODE_READ_SEQUENTIAL_BACKUP));

	SFileAndHash fandhash;
	if (f.get() != NULL && f->Size() < link_file_min_size)
	{
		f.reset();
	}
	else if (sha_version == 256)
	{
		f.reset();

		HashSha256 hash_256;
		if (!client_hash->getShaBinary(full_path, hash_256, false))
		{
			Server->Log("Error hashing file (0) " + full_path + ". " + os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = hash_256.finalize();
		}
	}
	else if (sha_version == 528)
	{
		f.reset();

		TreeHash treehash(client_hash->hasCbtFile() ? client_hash.get() : NULL);
		if (!client_hash->getShaBinary(full_path, treehash, client_hash->hasCbtFile()))
		{
			Server->Log("Error hashing file (1) " + full_path+". "+os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = treehash.finalize();
		}

#ifdef HASH_CBT_CHECK
		TreeHash treehash2(client_hash->hasCbtFile() ? client_hash.get() : NULL);
		client_hash->getShaBinary(full_path, treehash2, false);

		std::string other_hash = treehash2.finalize();
		if (other_hash != fandhash.hash)
		{
			Server->Log("Treehash compare without CBT failed at file \"" + full_path
				+ "\". Real hash: "+ base64_encode_dash(other_hash), LL_ERROR);
		}
#endif
	}
	else
	{
		f.reset();

		HashSha512 hash_512;
		if (!client_hash->getShaBinary(full_path, hash_512, false))
		{
			Server->Log("Error hashing file (2) " + full_path + ". " + os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = hash_512.finalize();
		}
	}

	CWData wdata;
	wdata.addUShort(0);
	wdata.addChar(1);
	wdata.addVarInt(file_id);
	wdata.addString2(fandhash.hash);
	fandhash.name = fn;
	*reinterpret_cast<_u16*>(wdata.getDataPtr()) = little_endian(static_cast<_u16>(wdata.getDataSize() - sizeof(_u16)));
	curr_files.push_back(fandhash);

	Server->Log("Parallel hash \"" + full_path + "\" id=" + convert(file_id) + " hash=" + base64_encode_dash(fandhash.hash), LL_DEBUG);

	addToStdoutBuf(wdata.getDataPtr(), wdata.getDataSize());

	return true;
}

void ParallelHash::addToStdoutBuf(const char * ptr, size_t size)
{
	IScopedLock lock(mutex.get());

	while (stdout_buf_size + size > 32 * 1024)
	{
		lock.relock(NULL);
		Server->wait(1000);
		lock.relock(mutex.get());
	}

	if (stdout_buf_size + size > stdout_buf.size())
	{
		stdout_buf.resize(stdout_buf_size + size);
	}

	memcpy(&stdout_buf[stdout_buf_size], ptr, size);
	stdout_buf_size += size;
}

size_t ParallelHash::calcBufferSize(const std::string &path, const std::vector<SFileAndHash> &data)
{
	size_t add_size = path.size() + sizeof(std::string) + sizeof(int) + sizeof(int64);
	for (size_t i = 0; i<data.size(); ++i)
	{
		add_size += data[i].name.size();
		add_size += sizeof(SFileAndHash);
		add_size += data[i].hash.size();
	}
	add_size += sizeof(std::vector<SFile>);

	return add_size;
}

void ParallelHash::addModifyFileBuffer(ClientDAO& clientdao, const std::string & path, int tgroup,
	const std::vector<SFileAndHash>& files, int64 target_generation)
{
	modify_file_buffer_size += calcBufferSize(path, files);

	modify_file_buffer.push_back(SBufferItem(path, tgroup, files, target_generation));

	if (last_file_buffer_commit_time == 0)
	{
		last_file_buffer_commit_time = Server->getTimeMS();
	}

	if (modify_file_buffer_size>max_modify_file_buffer_size
		|| Server->getTimeMS() - last_file_buffer_commit_time>file_buffer_commit_interval)
	{
		commitModifyFileBuffer(clientdao);
	}
}

void ParallelHash::commitModifyFileBuffer(ClientDAO& clientdao)
{
	DBScopedWriteTransaction trans(clientdao.getDatabase());
	for (size_t i = 0; i<modify_file_buffer.size(); ++i)
	{
		clientdao.modifyFiles(modify_file_buffer[i].path, modify_file_buffer[i].tgroup,
			modify_file_buffer[i].files, modify_file_buffer[i].target_generation);
	}

	modify_file_buffer.clear();
	modify_file_buffer_size = 0;
	last_file_buffer_commit_time = Server->getTimeMS();
}
<commit_msg>Fix phash skipping of small files<commit_after>#include "ParallelHash.h"
#include "../Interface/Server.h"
#include "../Interface/ThreadPool.h"
#include "ClientHash.h"
#include <algorithm>
#include "database.h"
#include "../stringtools.h"

//#define HASH_CBT_CHECK

namespace
{
	const size_t max_modify_file_buffer_size = 2 * 1024 * 1024;
	const int64 file_buffer_commit_interval = 120 * 1000;
	const int64 link_file_min_size = 2048;
}

ParallelHash::ParallelHash(SQueueRef* phash_queue, int sha_version)
	: do_quit(false), phash_queue(phash_queue), phash_queue_pos(0),
	stdout_buf_size(0), stdout_buf_pos(0), mutex(Server->createMutex()),
	last_file_buffer_commit_time(0), sha_version(sha_version), eof(false)
{
	stdout_buf.resize(4090);
	ticket = Server->getThreadPool()->execute(this, "phash");
}

bool ParallelHash::getExitCode(int & exit_code)
{
	Server->getThreadPool()->waitFor(ticket);
	exit_code = 0;
	return true;
}

void ParallelHash::forceExit()
{
	do_quit = true;
	//Server->getThreadPool()->waitFor(ticket);
}

bool ParallelHash::readStdoutIntoBuffer(char * buf, size_t buf_avail, size_t & read_bytes)
{
	while(!do_quit)
	{
		IScopedLock lock(mutex.get());
		if (stdout_buf_size > stdout_buf_pos)
		{
			read_bytes = (std::min)(stdout_buf_size - stdout_buf_pos, buf_avail);
			memcpy(buf, &stdout_buf[stdout_buf_pos], read_bytes);
			stdout_buf_pos += read_bytes;

			if (stdout_buf_pos == stdout_buf_size)
			{
				stdout_buf_pos = 0;
				stdout_buf_size = 0;

				if (eof)
				{
					return false;
				}
			}

			return true;
		}
		else
		{
			if (stdout_buf_pos == stdout_buf_size
				&& eof)
			{
				return false;
			}

			lock.relock(NULL);
			Server->wait(1000);
		}
	}

	return false;
}

void ParallelHash::finishStdout()
{
}

bool ParallelHash::readStderrIntoBuffer(char * buf, size_t buf_avail, size_t & read_bytes)
{
	while (!do_quit
		&& !eof)
	{
		Server->wait(1000);
	}

	return false;
}

void ParallelHash::operator()()
{
	ClientDAO clientdao(Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT));

	int mode = MODE_READ_SEQUENTIAL;
#ifdef _WIN32
	mode = MODE_READ_DEVICE;
#endif
	std::auto_ptr<IFile> phashf(Server->openFile(phash_queue->phash_queue->getFilename(), mode));

	while (!do_quit
		&& phashf.get()!=NULL)
	{
		bool had_msg = false;
		if (phashf->Size() >= phash_queue_pos + static_cast<int64>(sizeof(_u32)))
		{
			_u32 msg_size;
			if (phashf->Read(phash_queue_pos, reinterpret_cast<char*>(&msg_size), sizeof(msg_size))
				== sizeof(msg_size))
			{
				if (phashf->Size() >= phash_queue_pos + static_cast<int64>(sizeof(_u32)) + msg_size)
				{
					had_msg = true;

					std::string msg = phashf->Read(phash_queue_pos + sizeof(_u32), msg_size);
					CRData data(msg.data(), msg.size());
					phash_queue_pos += sizeof(_u32) + msg_size;

					hashFile(data, clientdao);

					if (eof)
					{
						break;
					}
				}
			}
		}
		if (!had_msg)
		{
			Server->wait(1000);
			CWData data;
			data.addUShort(1);
			data.addChar(0);
			addToStdoutBuf(data.getDataPtr(), data.getDataSize());
		}
	}

	commitModifyFileBuffer(clientdao);

	if (phash_queue->deref())
	{
		delete phash_queue;
		phash_queue = NULL;
	}
}

bool ParallelHash::hashFile(CRData & data, ClientDAO& clientdao)
{
	char id;
	if (!data.getChar(&id))
		return false;

	if (id == ID_SET_CURR_DIRS)
	{
		curr_files.clear();

		if (!data.getStr2(&curr_dir)
			|| !data.getInt(&curr_tgroup)
			|| !data.getStr2(&curr_snapshot_dir))
		{
			return false;
		}

		return true;
	}
	else if (id == ID_FINISH_CURR_DIR)
	{
		int64 target_generation;
		if (!data.getVarInt(&target_generation))
		{
			curr_files.clear();
			return false;
		}

#ifndef _WIN32
		std::string path_lower = curr_dir + os_file_sep();
#else
		std::string path_lower = strlower(curr_dir + os_file_sep());
#endif

		std::vector<SFileAndHash> files;
		int64 generation = -1;
		if (clientdao.getFiles(path_lower, curr_tgroup, files, generation))
		{
			if (generation != target_generation)
			{
				curr_files.clear();
				return true;
			}

			std::sort(curr_files.begin(), curr_files.end());

			bool added_hash = false;
			for (size_t i = 0; i < files.size(); ++i)
			{
				if (files[i].hash.empty())
				{
					std::vector<SFileAndHash>::iterator it =
						std::lower_bound(curr_files.begin(), curr_files.end(), files[i]);
					if (it != curr_files.end()
						&& it->name == files[i].name)
					{
						files[i].hash = it->hash;
						added_hash = true;
					}
				}
			}

			if (added_hash)
			{
				addModifyFileBuffer(clientdao, path_lower, curr_tgroup, files, target_generation);
			}

			curr_files.clear();
			return true;
		}

		curr_files.clear();
		return false;
	}
	else if (id == ID_INIT_HASH)
	{
		client_hash.reset(new ClientHash(NULL, false, 0, NULL, 0));
		return true;
	}
	else if (id == ID_CBT_DATA)
	{
		IFile* index_hdat_file;
		int64 index_hdat_fs_block_size;
		size_t* snapshot_sequence_id;
		int64 snapshot_sequence_id_reference;
		if (!data.getVoidPtr(reinterpret_cast<void**>(&index_hdat_file))
			|| !data.getVarInt(&index_hdat_fs_block_size)
			|| !data.getVoidPtr(reinterpret_cast<void**>(&snapshot_sequence_id))
			|| !data.getVarInt(&snapshot_sequence_id_reference))
		{
			return false;
		}

		client_hash.reset(new ClientHash(index_hdat_file, true, index_hdat_fs_block_size,
			snapshot_sequence_id, static_cast<size_t>(snapshot_sequence_id_reference)));
		return true;
	}
	else if (id == ID_PHASH_FINISH)
	{
		eof = true;
		return true;
	}

	if (id != ID_HASH_FILE)
	{
		return false;
	}

	int64 file_id;
	if (!data.getVarInt(&file_id))
	{
		return false;
	}

	std::string fn;
	if (!data.getStr2(&fn))
	{
		return false;
	}

	std::string full_path = curr_snapshot_dir + os_file_sep() + fn;

	std::auto_ptr<IFsFile>  f(Server->openFile(os_file_prefix(full_path), MODE_READ_SEQUENTIAL_BACKUP));

	SFileAndHash fandhash;
	if (f.get() != NULL && f->Size() < link_file_min_size)
	{
		f.reset();
	}
	else if (sha_version == 256)
	{
		f.reset();

		HashSha256 hash_256;
		if (!client_hash->getShaBinary(full_path, hash_256, false))
		{
			Server->Log("Error hashing file (0) " + full_path + ". " + os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = hash_256.finalize();
		}
	}
	else if (sha_version == 528)
	{
		f.reset();

		TreeHash treehash(client_hash->hasCbtFile() ? client_hash.get() : NULL);
		if (!client_hash->getShaBinary(full_path, treehash, client_hash->hasCbtFile()))
		{
			Server->Log("Error hashing file (1) " + full_path+". "+os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = treehash.finalize();
		}

#ifdef HASH_CBT_CHECK
		TreeHash treehash2(client_hash->hasCbtFile() ? client_hash.get() : NULL);
		client_hash->getShaBinary(full_path, treehash2, false);

		std::string other_hash = treehash2.finalize();
		if (other_hash != fandhash.hash)
		{
			Server->Log("Treehash compare without CBT failed at file \"" + full_path
				+ "\". Real hash: "+ base64_encode_dash(other_hash), LL_ERROR);
		}
#endif
	}
	else
	{
		f.reset();

		HashSha512 hash_512;
		if (!client_hash->getShaBinary(full_path, hash_512, false))
		{
			Server->Log("Error hashing file (2) " + full_path + ". " + os_last_error_str(), LL_DEBUG);
		}
		else
		{
			fandhash.hash = hash_512.finalize();
		}
	}

	CWData wdata;
	wdata.addUShort(0);
	wdata.addChar(1);
	wdata.addVarInt(file_id);
	wdata.addString2(fandhash.hash);
	fandhash.name = fn;
	*reinterpret_cast<_u16*>(wdata.getDataPtr()) = little_endian(static_cast<_u16>(wdata.getDataSize() - sizeof(_u16)));
	curr_files.push_back(fandhash);

	Server->Log("Parallel hash \"" + full_path + "\" id=" + convert(file_id) + " hash=" + base64_encode_dash(fandhash.hash), LL_DEBUG);

	addToStdoutBuf(wdata.getDataPtr(), wdata.getDataSize());

	return true;
}

void ParallelHash::addToStdoutBuf(const char * ptr, size_t size)
{
	IScopedLock lock(mutex.get());

	while (stdout_buf_size + size > 32 * 1024)
	{
		lock.relock(NULL);
		Server->wait(1000);
		lock.relock(mutex.get());
	}

	if (stdout_buf_size + size > stdout_buf.size())
	{
		stdout_buf.resize(stdout_buf_size + size);
	}

	memcpy(&stdout_buf[stdout_buf_size], ptr, size);
	stdout_buf_size += size;
}

size_t ParallelHash::calcBufferSize(const std::string &path, const std::vector<SFileAndHash> &data)
{
	size_t add_size = path.size() + sizeof(std::string) + sizeof(int) + sizeof(int64);
	for (size_t i = 0; i<data.size(); ++i)
	{
		add_size += data[i].name.size();
		add_size += sizeof(SFileAndHash);
		add_size += data[i].hash.size();
	}
	add_size += sizeof(std::vector<SFile>);

	return add_size;
}

void ParallelHash::addModifyFileBuffer(ClientDAO& clientdao, const std::string & path, int tgroup,
	const std::vector<SFileAndHash>& files, int64 target_generation)
{
	modify_file_buffer_size += calcBufferSize(path, files);

	modify_file_buffer.push_back(SBufferItem(path, tgroup, files, target_generation));

	if (last_file_buffer_commit_time == 0)
	{
		last_file_buffer_commit_time = Server->getTimeMS();
	}

	if (modify_file_buffer_size>max_modify_file_buffer_size
		|| Server->getTimeMS() - last_file_buffer_commit_time>file_buffer_commit_interval)
	{
		commitModifyFileBuffer(clientdao);
	}
}

void ParallelHash::commitModifyFileBuffer(ClientDAO& clientdao)
{
	DBScopedWriteTransaction trans(clientdao.getDatabase());
	for (size_t i = 0; i<modify_file_buffer.size(); ++i)
	{
		clientdao.modifyFiles(modify_file_buffer[i].path, modify_file_buffer[i].tgroup,
			modify_file_buffer[i].files, modify_file_buffer[i].target_generation);
	}

	modify_file_buffer.clear();
	modify_file_buffer_size = 0;
	last_file_buffer_commit_time = Server->getTimeMS();
}
<|endoftext|>


<commit_before>#include "dreal/optimization/nlopt_optimizer.h"

#include <sstream>
#include <utility>

#include "dreal/util/exception.h"

namespace dreal {

using std::make_pair;
using std::make_unique;
using std::move;
using std::ostream;
using std::ostringstream;
using std::pair;
using std::unique_ptr;
using std::vector;

namespace {
/// A function passed to nlopt (type = nlopt_func). Given a vector @p
/// x, returns the evaluation of @p f_data at @p x and updates @p grad
/// which is the gradient of the function @p f_data with respect to @p
/// x.
///
/// @see
/// http://nlopt.readthedocs.io/en/latest/NLopt_Reference/#objective-function.
// double NloptOptimizerEvaluate(unsigned n, const double* x, double* grad,
//                               void* f_data);
double NloptOptimizerEvaluate(const unsigned n, const double* x, double* grad,
                              void* const f_data) {
  assert(f_data);
  auto& expression{*static_cast<CachedExpression*>(f_data)};
  const Box& box{expression.box()};
  assert(n == static_cast<size_t>(box.size()));
  // Set up an environment.
  Environment& env{expression.mutable_environment()};
  for (size_t i = 0; i < n; ++i) {
    const Variable& v{box.variable(i)};
    env[v] = x[i];
  }
  // Set up gradients.
  if (grad) {
    for (int i = 0; i < box.size(); ++i) {
      const Variable& v{box.variable(i)};
      grad[i] = expression.Differentiate(v).Evaluate(env);
    }
  }
  // Return evaluation.
  return expression.Evaluate(env);
}
}  // namespace

// ----------------
// CachedExpression
// ----------------
CachedExpression::CachedExpression(Expression e, const Box& box)
    : expression_{move(e)}, box_{&box} {
  assert(box_);
}

const Box& CachedExpression::box() const {
  assert(box_);
  return *box_;
}

Environment& CachedExpression::mutable_environment() { return environment_; }

const Environment& CachedExpression::environment() const {
  return environment_;
}

double CachedExpression::Evaluate(const Environment& env) const {
  return expression_.Evaluate(env);
}

const Expression& CachedExpression::Differentiate(const Variable& x) {
  auto it = gradient_.find(x);
  if (it == gradient_.end()) {
    // Not found.
    return gradient_.emplace_hint(it, x, expression_.Differentiate(x))->second;
  } else {
    return it->second;
  }
}

ostream& operator<<(ostream& os, const CachedExpression& expression) {
  return os << expression.expression_;
}

// --------------
// NloptOptimizer
// --------------
NloptOptimizer::NloptOptimizer(const nlopt_algorithm algorithm, Box bound,
                               const double delta)
    : box_{move(bound)}, delta_{delta} {
  assert(delta_ > 0.0);
  opt_ = nlopt_create(algorithm, box_.size());

  // Set tolerance.
  nlopt_set_ftol_rel(opt_, delta_);

  // Set bounds.
  const auto lower_bounds = make_unique<double[]>(box_.size());
  const auto upper_bounds = make_unique<double[]>(box_.size());
  for (int i = 0; i < box_.size(); ++i) {
    lower_bounds[i] = box_[i].lb();
    upper_bounds[i] = box_[i].ub();
  }
  const nlopt_result nlopt_result_lb{
      nlopt_set_lower_bounds(opt_, lower_bounds.get())};
  assert(nlopt_result_lb == NLOPT_SUCCESS);
  const nlopt_result nlopt_result_ub{
      nlopt_set_upper_bounds(opt_, upper_bounds.get())};
  assert(nlopt_result_ub == NLOPT_SUCCESS);
}

NloptOptimizer::~NloptOptimizer() { nlopt_destroy(opt_); }

void NloptOptimizer::SetMinObjective(const Expression& objective) {
  objective_ = CachedExpression{objective, box_};
  const nlopt_result result{nlopt_set_min_objective(
      opt_, NloptOptimizerEvaluate, static_cast<void*>(&objective_))};
  assert(result == NLOPT_SUCCESS);
}

void NloptOptimizer::AddConstraint(const Formula& formula) {
  if (is_conjunction(formula)) {
    for (const Formula& f : get_operands(formula)) {
      AddConstraint(f);
    }
    return;
  }
  if (is_relational(formula)) {
    return AddRelationalConstraint(formula);
  }
  if (is_negation(formula)) {
    const Formula& negated_formula{get_operand(formula)};
    if (is_relational(negated_formula)) {
      return AddRelationalConstraint(nnfizer_.Convert(negated_formula));
    }
  }
  ostringstream oss;
  oss << "NloptOptimizer::AddConstraint: "
      << "Unsupported formula: " << formula;
  throw DREAL_RUNTIME_ERROR(oss.str());
}

void NloptOptimizer::AddRelationalConstraint(const Formula& formula) {
  bool equality{false};
  if (is_greater_than(formula) || is_greater_than_or_equal_to(formula)) {
    // f := e₁ > e₂  –>  e₂ - e₁ < 0.
    auto cached_expression = make_unique<CachedExpression>(
        get_rhs_expression(formula) - get_lhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
  } else if (is_less_than(formula) || is_less_than_or_equal_to(formula)) {
    // f := e₁ < e₂  –>  e₁ - e₂ < 0.
    auto cached_expression = make_unique<CachedExpression>(
        get_lhs_expression(formula) - get_rhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
  } else if (is_equal_to(formula)) {
    // f := e₁ == e₂  -> e₁ - e₂ == 0
    auto cached_expression = make_unique<CachedExpression>(
        get_lhs_expression(formula) - get_rhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
    equality = true;
  } else {
    ostringstream oss;
    oss << "NloptOptimizer::AddRelationalConstraint: "
        << "Unsupported formula: " << formula;
    throw DREAL_RUNTIME_ERROR(oss.str());
  }

  if (equality) {
    nlopt_add_equality_constraint(opt_, NloptOptimizerEvaluate,
                                  static_cast<void*>(constraints_.back().get()),
                                  delta_);
  } else {
    nlopt_add_inequality_constraint(
        opt_, NloptOptimizerEvaluate,
        static_cast<void*>(constraints_.back().get()), delta_);
  }
}

void NloptOptimizer::AddConstraints(const vector<Formula>& formulas) {
  for (const Formula& formula : formulas) {
    AddConstraint(formula);
  }
}

nlopt_result NloptOptimizer::Optimize(vector<double>* const x,
                                      double* const opt_f) {
  return nlopt_optimize(opt_, x->data(), opt_f);
}

}  // namespace dreal
<commit_msg>fix(dreal/optimization/nlopt_optimizer.cc): gcc-4.9 issue<commit_after>#include "dreal/optimization/nlopt_optimizer.h"

#include <sstream>
#include <utility>

#include "dreal/util/exception.h"

namespace dreal {

using std::make_pair;
using std::make_unique;
using std::move;
using std::ostream;
using std::ostringstream;
using std::pair;
using std::unique_ptr;
using std::vector;

namespace {
/// A function passed to nlopt (type = nlopt_func). Given a vector @p
/// x, returns the evaluation of @p f_data at @p x and updates @p grad
/// which is the gradient of the function @p f_data with respect to @p
/// x.
///
/// @see
/// http://nlopt.readthedocs.io/en/latest/NLopt_Reference/#objective-function.
// double NloptOptimizerEvaluate(unsigned n, const double* x, double* grad,
//                               void* f_data);
double NloptOptimizerEvaluate(const unsigned n, const double* x, double* grad,
                              void* const f_data) {
  assert(f_data);
  auto& expression = *static_cast<CachedExpression*>(f_data);
  const Box& box{expression.box()};
  assert(n == static_cast<size_t>(box.size()));
  // Set up an environment.
  Environment& env{expression.mutable_environment()};
  for (size_t i = 0; i < n; ++i) {
    const Variable& v{box.variable(i)};
    env[v] = x[i];
  }
  // Set up gradients.
  if (grad) {
    for (int i = 0; i < box.size(); ++i) {
      const Variable& v{box.variable(i)};
      grad[i] = expression.Differentiate(v).Evaluate(env);
    }
  }
  // Return evaluation.
  return expression.Evaluate(env);
}
}  // namespace

// ----------------
// CachedExpression
// ----------------
CachedExpression::CachedExpression(Expression e, const Box& box)
    : expression_{move(e)}, box_{&box} {
  assert(box_);
}

const Box& CachedExpression::box() const {
  assert(box_);
  return *box_;
}

Environment& CachedExpression::mutable_environment() { return environment_; }

const Environment& CachedExpression::environment() const {
  return environment_;
}

double CachedExpression::Evaluate(const Environment& env) const {
  return expression_.Evaluate(env);
}

const Expression& CachedExpression::Differentiate(const Variable& x) {
  auto it = gradient_.find(x);
  if (it == gradient_.end()) {
    // Not found.
    return gradient_.emplace_hint(it, x, expression_.Differentiate(x))->second;
  } else {
    return it->second;
  }
}

ostream& operator<<(ostream& os, const CachedExpression& expression) {
  return os << expression.expression_;
}

// --------------
// NloptOptimizer
// --------------
NloptOptimizer::NloptOptimizer(const nlopt_algorithm algorithm, Box bound,
                               const double delta)
    : box_{move(bound)}, delta_{delta} {
  assert(delta_ > 0.0);
  opt_ = nlopt_create(algorithm, box_.size());

  // Set tolerance.
  nlopt_set_ftol_rel(opt_, delta_);

  // Set bounds.
  const auto lower_bounds = make_unique<double[]>(box_.size());
  const auto upper_bounds = make_unique<double[]>(box_.size());
  for (int i = 0; i < box_.size(); ++i) {
    lower_bounds[i] = box_[i].lb();
    upper_bounds[i] = box_[i].ub();
  }
  const nlopt_result nlopt_result_lb{
      nlopt_set_lower_bounds(opt_, lower_bounds.get())};
  assert(nlopt_result_lb == NLOPT_SUCCESS);
  const nlopt_result nlopt_result_ub{
      nlopt_set_upper_bounds(opt_, upper_bounds.get())};
  assert(nlopt_result_ub == NLOPT_SUCCESS);
}

NloptOptimizer::~NloptOptimizer() { nlopt_destroy(opt_); }

void NloptOptimizer::SetMinObjective(const Expression& objective) {
  objective_ = CachedExpression{objective, box_};
  const nlopt_result result{nlopt_set_min_objective(
      opt_, NloptOptimizerEvaluate, static_cast<void*>(&objective_))};
  assert(result == NLOPT_SUCCESS);
}

void NloptOptimizer::AddConstraint(const Formula& formula) {
  if (is_conjunction(formula)) {
    for (const Formula& f : get_operands(formula)) {
      AddConstraint(f);
    }
    return;
  }
  if (is_relational(formula)) {
    return AddRelationalConstraint(formula);
  }
  if (is_negation(formula)) {
    const Formula& negated_formula{get_operand(formula)};
    if (is_relational(negated_formula)) {
      return AddRelationalConstraint(nnfizer_.Convert(negated_formula));
    }
  }
  ostringstream oss;
  oss << "NloptOptimizer::AddConstraint: "
      << "Unsupported formula: " << formula;
  throw DREAL_RUNTIME_ERROR(oss.str());
}

void NloptOptimizer::AddRelationalConstraint(const Formula& formula) {
  bool equality{false};
  if (is_greater_than(formula) || is_greater_than_or_equal_to(formula)) {
    // f := e₁ > e₂  –>  e₂ - e₁ < 0.
    auto cached_expression = make_unique<CachedExpression>(
        get_rhs_expression(formula) - get_lhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
  } else if (is_less_than(formula) || is_less_than_or_equal_to(formula)) {
    // f := e₁ < e₂  –>  e₁ - e₂ < 0.
    auto cached_expression = make_unique<CachedExpression>(
        get_lhs_expression(formula) - get_rhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
  } else if (is_equal_to(formula)) {
    // f := e₁ == e₂  -> e₁ - e₂ == 0
    auto cached_expression = make_unique<CachedExpression>(
        get_lhs_expression(formula) - get_rhs_expression(formula), box_);
    constraints_.push_back(move(cached_expression));
    equality = true;
  } else {
    ostringstream oss;
    oss << "NloptOptimizer::AddRelationalConstraint: "
        << "Unsupported formula: " << formula;
    throw DREAL_RUNTIME_ERROR(oss.str());
  }

  if (equality) {
    nlopt_add_equality_constraint(opt_, NloptOptimizerEvaluate,
                                  static_cast<void*>(constraints_.back().get()),
                                  delta_);
  } else {
    nlopt_add_inequality_constraint(
        opt_, NloptOptimizerEvaluate,
        static_cast<void*>(constraints_.back().get()), delta_);
  }
}

void NloptOptimizer::AddConstraints(const vector<Formula>& formulas) {
  for (const Formula& formula : formulas) {
    AddConstraint(formula);
  }
}

nlopt_result NloptOptimizer::Optimize(vector<double>* const x,
                                      double* const opt_f) {
  return nlopt_optimize(opt_, x->data(), opt_f);
}

}  // namespace dreal
<|endoftext|>


<commit_before>// This file is distributed under the MIT license.
// See the LICENSE file for details.

#include <cassert>

#include <visionaray/math/math.h>

#include <gtest/gtest.h>

using namespace visionaray;

//-------------------------------------------------------------------------------------------------
// Helper functions
//

// nested for loop over matrices --------------------------

template <typename Func>
void for_each_mat2_e(Func f)
{
    for (int i = 0; i < 2; ++i)
    {
        for (int j = 0; j < 2; ++j)
        {
            f(i, j);
        }
    }
}

template <typename Func>
void for_each_mat3_e(Func f)
{
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            f(i, j);
        }
    }
}

template <typename Func>
void for_each_mat4_e(Func f)
{
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            f(i, j);
        }
    }
}

// get rows and columns -----------------------------------

template <size_t Dim>
vector<Dim, float> get_row(matrix<Dim, Dim, float> const& m, int i)
{
    assert( i >= 0 && i < 4 );

    vector<Dim, float> result;

    for (size_t d = 0; d < Dim; ++d)
    {
        result[d] = m(i, d);
    }

    return result;
}

template <size_t Dim>
vector<Dim, float> get_col(matrix<Dim, Dim, float> const& m, int j)
{
    assert( j >= 0 && j < 4 );

    return m(j);
}

TEST(Matrix, Inverse)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        mat2 I = mat2::identity();

        // make some non-singular matrix
        mat2 A(1, 2, 3, 4);
        mat2 B = inverse(A);
        mat2 C = A * B;

        for_each_mat2_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        mat3 I = mat3::identity();

        // make some non-singular matrix
        mat3 A = mat3::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat3 B = inverse(A);
        mat3 C = A * B;

        for_each_mat3_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        mat4 I = mat4::identity();

        // make some non-singular matrix
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = inverse(A);
        mat4 C = A * B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }
}

TEST(Matrix, Mult)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        // make some matrices
        mat2 A = mat2::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat2 B = mat2::identity(); B(0, 0) = 11.0f; B(1, 0) = 6.28f; B(1, 1) = 3.0f;
        mat2 C = A * B;

        for_each_mat2_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );
    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        // make some matrices
        mat3 A = mat3::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat3 B = mat3::identity(); B(0, 1) = 11.0f; B(2, 1) = 6.28f; B(2, 2) = 3.0f;
        mat3 C = A * B;

        for_each_mat3_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );
    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some matrices
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = mat4::translation(vec3(3, 4, 5));
        mat4 C = A * B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );

    }
}

TEST(Matrix, Add)
{
    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some matrices
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = mat4::translation(vec3(3, 4, 5));
        mat4 C = A + B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), A(i, j) + B(i, j));
            }
            );

    }
}

TEST(Matrix, Transpose)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        // make some non-singular matrix
        mat2 A = mat2::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat2 B = transpose(A);

        for_each_mat2_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        // make some non-singular matrix
        mat3 A = mat3::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat3 B = transpose(A);

        for_each_mat3_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some non-singular matrix
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = transpose(A);

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }
}
<commit_msg>Refactoring<commit_after>// This file is distributed under the MIT license.
// See the LICENSE file for details.

#include <cassert>

#include <visionaray/math/math.h>

#include <gtest/gtest.h>

using namespace visionaray;

//-------------------------------------------------------------------------------------------------
// Helper functions
//

// nested for loop over matrices --------------------------

template <typename Func>
void for_each_mat2_e(Func f)
{
    for (int i = 0; i < 2; ++i)
    {
        for (int j = 0; j < 2; ++j)
        {
            f(i, j);
        }
    }
}

template <typename Func>
void for_each_mat3_e(Func f)
{
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            f(i, j);
        }
    }
}

template <typename Func>
void for_each_mat4_e(Func f)
{
    for (int i = 0; i < 4; ++i)
    {
        for (int j = 0; j < 4; ++j)
        {
            f(i, j);
        }
    }
}

// get rows and columns -----------------------------------

template <size_t Dim>
vector<Dim, float> get_row(matrix<Dim, Dim, float> const& m, int i)
{
    assert( i >= 0 && i < 4 );

    vector<Dim, float> result;

    for (size_t d = 0; d < Dim; ++d)
    {
        result[d] = m(i, d);
    }

    return result;
}

template <size_t Dim>
vector<Dim, float> get_col(matrix<Dim, Dim, float> const& m, int j)
{
    assert( j >= 0 && j < 4 );

    return m(j);
}

TEST(Matrix, Inverse)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        mat2 I = mat2::identity();

        // make some non-singular matrix
        mat2 A(1, 2, 3, 4);
        mat2 B = inverse(A);
        mat2 C = A * B;

        for_each_mat2_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        mat3 I = mat3::identity();

        // make some non-singular matrix
        mat3 A = mat3::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat3 B = inverse(A);
        mat3 C = A * B;

        for_each_mat3_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        mat4 I = mat4::identity();

        // make some non-singular matrix
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = inverse(A);
        mat4 C = A * B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), I(i, j));
            }
            );

    }
}

TEST(Matrix, Mul)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        // make some matrices
        mat2 A = mat2::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat2 B = mat2::identity(); B(0, 0) = 11.0f; B(1, 0) = 6.28f; B(1, 1) = 3.0f;
        mat2 C = A * B;

        for_each_mat2_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );
    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        // make some matrices
        mat3 A = mat3::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat3 B = mat3::identity(); B(0, 1) = 11.0f; B(2, 1) = 6.28f; B(2, 2) = 3.0f;
        mat3 C = A * B;

        for_each_mat3_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );
    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some matrices
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = mat4::translation(vec3(3, 4, 5));
        mat4 C = A * B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                float d = dot(get_row(A, i), get_col(B, j));
                EXPECT_FLOAT_EQ(C(i, j), d);
            }
            );

    }
}

TEST(Matrix, Add)
{
    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some matrices
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = mat4::translation(vec3(3, 4, 5));
        mat4 C = A + B;

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(C(i, j), A(i, j) + B(i, j));
            }
            );

    }
}

TEST(Matrix, Transpose)
{

    //-------------------------------------------------------------------------
    // mat2
    //

    {

        // make some non-singular matrix
        mat2 A = mat2::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat2 B = transpose(A);

        for_each_mat2_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat3
    //

    {

        // make some non-singular matrix
        mat3 A = mat3::identity(); A(0, 0) = 2.0f;  A(1, 0) = 3.14f; A(1, 1) = 3.0f;
        mat3 B = transpose(A);

        for_each_mat3_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }

    //-------------------------------------------------------------------------
    // mat4
    //

    {

        // make some non-singular matrix
        mat4 A = mat4::rotation(vec3(1, 0, 0), constants::pi<float>() / 4);
        mat4 B = transpose(A);

        for_each_mat4_e(
            [&](int i, int j)
            {
                EXPECT_FLOAT_EQ(A(i, j), B(j, i));
            }
            );

    }
}
<|endoftext|>


<commit_before>/*
  This file is part of the Groupware/KOrganizer integration.

  Requires the Qt and KDE widget libraries, available at no cost at
  http://www.trolltech.com and http://www.kde.org respectively

  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
        <info@klaralvdalens-datakonsult.se>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  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 General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA  02111-1307, USA.

  In addition, as a special exception, the copyright holders give
  permission to link the code of this program with any edition of
  the Qt library by Trolltech AS, Norway (or with modified versions
  of Qt that use the same license as Qt), and distribute linked
  combinations including the two.  You must obey the GNU General
  Public License in all respects for all of the code used other than
  Qt.  If you modify this file, you may extend this exception to
  your version of the file, but you are not obligated to do so.  If
  you do not wish to do so, delete this exception statement from
  your version.
*/

#include "kogroupware.h"
#include "freebusymanager.h"
#include "calendarview.h"
#include "mailscheduler.h"
#include "koprefs.h"
#include <libkdepim/email.h>
#include <libkcal/attendee.h>
#include <libkcal/journal.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kdirwatch.h>
#include <qfile.h>
#include <qregexp.h>
#include <qdir.h>

FreeBusyManager *KOGroupware::mFreeBusyManager = 0;

KOGroupware *KOGroupware::mInstance = 0;

KOGroupware *KOGroupware::create( CalendarView *view,
                                  KCal::Calendar *calendar )
{
  if( !mInstance )
    mInstance = new KOGroupware( view, calendar );
  return mInstance;
}

KOGroupware *KOGroupware::instance()
{
  // Doesn't create, that is the task of create()
  Q_ASSERT( mInstance );
  return mInstance;
}

KOGroupware::KOGroupware( CalendarView* view, KCal::Calendar* calendar )
  : QObject( 0, "kmgroupware_instance" )
{
  mView = view;
  mCalendar = calendar;

  // Set up the dir watch of the three incoming dirs
  KDirWatch* watcher = KDirWatch::self();
  watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) );
  connect( watcher, SIGNAL( dirty( const QString& ) ),
           this, SLOT( incomingDirChanged( const QString& ) ) );
  // Now set the ball rolling
  incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) );
}

FreeBusyManager *KOGroupware::freeBusyManager()
{
  if ( !mFreeBusyManager ) {
    mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
    mFreeBusyManager->setCalendar( mCalendar );
    connect( mCalendar, SIGNAL( calendarChanged() ),
             mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
  }

  return mFreeBusyManager;
}

void KOGroupware::incomingDirChanged( const QString& path )
{
  const QString incomingDirName = locateLocal( "data","korganizer/" )
                                  + "income.";
  if ( !path.startsWith( incomingDirName ) ) {
    kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
    return;
  }
  QString action = path.mid( incomingDirName.length() );
  while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
    // Strip slashes at the end
    action.truncate( action.length()-1 );

  // Handle accepted invitations
  QDir dir( path );
  QStringList files = dir.entryList( QDir::Files );
  if ( files.count() == 0 )
    // No more files here
    return;

  // Read the file and remove it
  QFile f( path + "/" + files[0] );
  if (!f.open(IO_ReadOnly)) {
    kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
    return;
  }
  QTextStream t(&f);
  t.setEncoding( QTextStream::UnicodeUTF8 );
  QString receiver = KPIM::getEmailAddr( t.readLine() );
  QString iCal = t.read();

  ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
  if ( !message ) {
    QString errorMessage;
    if (mFormat.exception())
      errorMessage = "\nError message: " + mFormat.exception()->message();
    kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing"
                  << errorMessage << endl;
    f.close();
    return;
  } else
    f.remove();

  KCal::Scheduler::Method method =
    static_cast<KCal::Scheduler::Method>( message->method() );
  KCal::ScheduleMessage::Status status = message->status();
  KCal::Incidence* incidence =
    dynamic_cast<KCal::Incidence*>( message->event() );
  KCal::MailScheduler scheduler( mCalendar );
  if ( action.startsWith( "accepted" ) ) {
    // Find myself and set to answered and accepted
    KCal::Attendee::List attendees = incidence->attendees();
    KCal::Attendee::List::ConstIterator it;
    for ( it = attendees.begin(); it != attendees.end(); ++it ) {
      if( (*it)->email() == receiver ) {
        (*it)->setStatus( KCal::Attendee::Accepted );
        (*it)->setRSVP(false);
        break;
      }
    }
    scheduler.acceptTransaction( incidence, method, status );
  } else if ( action.startsWith( "tentative" ) ) {
    // Find myself and set to answered and tentative
    KCal::Attendee::List attendees = incidence->attendees();
    KCal::Attendee::List::ConstIterator it;
    for ( it = attendees.begin(); it != attendees.end(); ++it ) {
      if( (*it)->email() == receiver ) {
        (*it)->setStatus( KCal::Attendee::Tentative );
        (*it)->setRSVP(false);
        break;
      }
    }
    scheduler.acceptTransaction( incidence, method, status );
  } else if ( action.startsWith( "cancel" ) )
    // TODO: Could this be done like the others?
    mCalendar->deleteIncidence( incidence );
  else if ( action.startsWith( "reply" ) )
    scheduler.acceptTransaction( incidence, method, status );
  else
    kdError(5850) << "Unknown incoming action " << action << endl;
  mView->updateView();
}

/* This function sends mails if necessary, and makes sure the user really
 * want to change his calendar.
 *
 * Return true means accept the changes
 * Return false means revert the changes
 */
bool KOGroupware::sendICalMessage( QWidget* parent,
                                   KCal::Scheduler::Method method,
                                   Incidence* incidence, bool isDeleting,
                                   bool statusChanged )
{
  // If there are no attendees, don't bother
  if( incidence->attendees().isEmpty() )
    return true;

  bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
  int rc = 0;
  /*
   * There are two scenarios:
   * o "we" are the organizer, where "we" means any of the identities or mail
   *   addresses known to Kontact/PIM. If there are attendees, we need to mail
   *   them all, even if one or more of them are also "us". Otherwise there
   *   would be no way to invite a resource or our boss, other identities we
   *   also manage.
   * o "we: are not the organizer, which means we changed the completion status
   *   of a todo, or we changed our attendee status from, say, tentative to
   *   accepted. In both cases we only mail the organizer. All other changes
   *   bring us out of sync with the organizer, so we won't mail, if the user
   *   insists on applying them.
   */

  if ( isOrganizer ) {
    /* We are the organizer. If there is more than one attendee, or if there is
     * only one, and it's not the same as the organizer, ask the user to send
     * mail. */
    if ( incidence->attendees().count() > 1
        || incidence->attendees().first()->email() != incidence->organizer().email() ) {
      QString type;
      if( incidence->type() == "Event") type = i18n("event");
      else if( incidence->type() == "Todo" ) type = i18n("task");
      else if( incidence->type() == "Journal" ) type = i18n("journal entry");
      else type = incidence->type();
      QString txt = i18n( "This %1 includes other people. "
          "Should email be sent out to the attendees?" )
        .arg( type );
      rc = KMessageBox::questionYesNoCancel( parent, txt,
          i18n("Group scheduling email") );
    } else {
      return true;
    }
  } else if( incidence->type() == "Todo" ) {
    if( method == Scheduler::Request )
      // This is an update to be sent to the organizer
      method = Scheduler::Reply;

    // Ask if the user wants to tell the organizer about the current status
    QString txt = i18n( "Do you want to send a status update to the "
                        "organizer of this task?");
    rc = KMessageBox::questionYesNo( parent, txt );
  } else if( incidence->type() == "Event" ) {
    QString txt;
    if ( statusChanged && method == Scheduler::Request ) {
      txt = i18n( "Your status as an attendee of this event "
          "changed. Do you want to send a status update to the "
          "organizer of this event?" );
      method = Scheduler::Reply;
      rc = KMessageBox::questionYesNo( parent, txt );
    } else {
      if( isDeleting )
        txt = i18n( "You are not the organizer of this event. "
            "Deleting it will bring your calendar out of sync "
            "with the organizers calendar. Do you really want "
            "to delete it?" );
      else
        txt = i18n( "You are not the organizer of this event. "
            "Editing it will bring your calendar out of sync "
            "with the organizers calendar. Do you really want "
            "to edit it?" );
      rc = KMessageBox::questionYesNo( parent, txt );
      return ( rc == KMessageBox::Yes );
    }
  } else {
    kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
    return true;
  }
  if( rc == KMessageBox::Yes ) {
    // We will be sending out a message here. Now make sure there is
    // some summary
    if( incidence->summary().isEmpty() )
      incidence->setSummary( i18n("<No summary given>") );

    // Send the mail
    KCal::MailScheduler scheduler( mCalendar );
    scheduler.performTransaction( incidence, method );

    return true;
  } else if( rc == KMessageBox::No )
    return true;
  else
    return false;
}

#include "kogroupware.moc"
<commit_msg>Don't when processing a mail in one of the incoming dirs fails, remove the offending mail, otherwise it will block all following mails. Show an error dialog with a Details button which contains the parse error, if there was one.<commit_after>/*
  This file is part of the Groupware/KOrganizer integration.

  Requires the Qt and KDE widget libraries, available at no cost at
  http://www.trolltech.com and http://www.kde.org respectively

  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
        <info@klaralvdalens-datakonsult.se>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  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 General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA  02111-1307, USA.

  In addition, as a special exception, the copyright holders give
  permission to link the code of this program with any edition of
  the Qt library by Trolltech AS, Norway (or with modified versions
  of Qt that use the same license as Qt), and distribute linked
  combinations including the two.  You must obey the GNU General
  Public License in all respects for all of the code used other than
  Qt.  If you modify this file, you may extend this exception to
  your version of the file, but you are not obligated to do so.  If
  you do not wish to do so, delete this exception statement from
  your version.
*/

#include "kogroupware.h"
#include "freebusymanager.h"
#include "calendarview.h"
#include "mailscheduler.h"
#include "koprefs.h"
#include <libkdepim/email.h>
#include <libkcal/attendee.h>
#include <libkcal/journal.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <kdirwatch.h>
#include <qfile.h>
#include <qregexp.h>
#include <qdir.h>

FreeBusyManager *KOGroupware::mFreeBusyManager = 0;

KOGroupware *KOGroupware::mInstance = 0;

KOGroupware *KOGroupware::create( CalendarView *view,
                                  KCal::Calendar *calendar )
{
  if( !mInstance )
    mInstance = new KOGroupware( view, calendar );
  return mInstance;
}

KOGroupware *KOGroupware::instance()
{
  // Doesn't create, that is the task of create()
  Q_ASSERT( mInstance );
  return mInstance;
}

KOGroupware::KOGroupware( CalendarView* view, KCal::Calendar* calendar )
  : QObject( 0, "kmgroupware_instance" )
{
  mView = view;
  mCalendar = calendar;

  // Set up the dir watch of the three incoming dirs
  KDirWatch* watcher = KDirWatch::self();
  watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) );
  watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) );
  connect( watcher, SIGNAL( dirty( const QString& ) ),
           this, SLOT( incomingDirChanged( const QString& ) ) );
  // Now set the ball rolling
  incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) );
  incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) );
}

FreeBusyManager *KOGroupware::freeBusyManager()
{
  if ( !mFreeBusyManager ) {
    mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
    mFreeBusyManager->setCalendar( mCalendar );
    connect( mCalendar, SIGNAL( calendarChanged() ),
             mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
  }

  return mFreeBusyManager;
}

void KOGroupware::incomingDirChanged( const QString& path )
{
  const QString incomingDirName = locateLocal( "data","korganizer/" )
                                  + "income.";
  if ( !path.startsWith( incomingDirName ) ) {
    kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
    return;
  }
  QString action = path.mid( incomingDirName.length() );
  while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
    // Strip slashes at the end
    action.truncate( action.length()-1 );

  // Handle accepted invitations
  QDir dir( path );
  QStringList files = dir.entryList( QDir::Files );
  if ( files.count() == 0 )
    // No more files here
    return;

  // Read the file and remove it
  QFile f( path + "/" + files[0] );
  if (!f.open(IO_ReadOnly)) {
    kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
    return;
  }
  QTextStream t(&f);
  t.setEncoding( QTextStream::UnicodeUTF8 );
  QString receiver = KPIM::getEmailAddr( t.readLine() );
  QString iCal = t.read();

  f.remove();

  ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
  if ( !message ) {
    QString errorMessage;
    if (mFormat.exception())
      errorMessage = "\nError message: " + mFormat.exception()->message();
    kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing"
                  << errorMessage << endl;
    KMessageBox::detailedError( mView,
        i18n("Error while processing an invitation or update."),
        errorMessage );
    return;
  }

  KCal::Scheduler::Method method =
    static_cast<KCal::Scheduler::Method>( message->method() );
  KCal::ScheduleMessage::Status status = message->status();
  KCal::Incidence* incidence =
    dynamic_cast<KCal::Incidence*>( message->event() );
  KCal::MailScheduler scheduler( mCalendar );
  if ( action.startsWith( "accepted" ) ) {
    // Find myself and set to answered and accepted
    KCal::Attendee::List attendees = incidence->attendees();
    KCal::Attendee::List::ConstIterator it;
    for ( it = attendees.begin(); it != attendees.end(); ++it ) {
      if( (*it)->email() == receiver ) {
        (*it)->setStatus( KCal::Attendee::Accepted );
        (*it)->setRSVP(false);
        break;
      }
    }
    scheduler.acceptTransaction( incidence, method, status );
  } else if ( action.startsWith( "tentative" ) ) {
    // Find myself and set to answered and tentative
    KCal::Attendee::List attendees = incidence->attendees();
    KCal::Attendee::List::ConstIterator it;
    for ( it = attendees.begin(); it != attendees.end(); ++it ) {
      if( (*it)->email() == receiver ) {
        (*it)->setStatus( KCal::Attendee::Tentative );
        (*it)->setRSVP(false);
        break;
      }
    }
    scheduler.acceptTransaction( incidence, method, status );
  } else if ( action.startsWith( "cancel" ) )
    // TODO: Could this be done like the others?
    mCalendar->deleteIncidence( incidence );
  else if ( action.startsWith( "reply" ) )
    scheduler.acceptTransaction( incidence, method, status );
  else
    kdError(5850) << "Unknown incoming action " << action << endl;
  mView->updateView();
}

/* This function sends mails if necessary, and makes sure the user really
 * want to change his calendar.
 *
 * Return true means accept the changes
 * Return false means revert the changes
 */
bool KOGroupware::sendICalMessage( QWidget* parent,
                                   KCal::Scheduler::Method method,
                                   Incidence* incidence, bool isDeleting,
                                   bool statusChanged )
{
  // If there are no attendees, don't bother
  if( incidence->attendees().isEmpty() )
    return true;

  bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
  int rc = 0;
  /*
   * There are two scenarios:
   * o "we" are the organizer, where "we" means any of the identities or mail
   *   addresses known to Kontact/PIM. If there are attendees, we need to mail
   *   them all, even if one or more of them are also "us". Otherwise there
   *   would be no way to invite a resource or our boss, other identities we
   *   also manage.
   * o "we: are not the organizer, which means we changed the completion status
   *   of a todo, or we changed our attendee status from, say, tentative to
   *   accepted. In both cases we only mail the organizer. All other changes
   *   bring us out of sync with the organizer, so we won't mail, if the user
   *   insists on applying them.
   */

  if ( isOrganizer ) {
    /* We are the organizer. If there is more than one attendee, or if there is
     * only one, and it's not the same as the organizer, ask the user to send
     * mail. */
    if ( incidence->attendees().count() > 1
        || incidence->attendees().first()->email() != incidence->organizer().email() ) {
      QString type;
      if( incidence->type() == "Event") type = i18n("event");
      else if( incidence->type() == "Todo" ) type = i18n("task");
      else if( incidence->type() == "Journal" ) type = i18n("journal entry");
      else type = incidence->type();
      QString txt = i18n( "This %1 includes other people. "
          "Should email be sent out to the attendees?" )
        .arg( type );
      rc = KMessageBox::questionYesNoCancel( parent, txt,
          i18n("Group scheduling email") );
    } else {
      return true;
    }
  } else if( incidence->type() == "Todo" ) {
    if( method == Scheduler::Request )
      // This is an update to be sent to the organizer
      method = Scheduler::Reply;

    // Ask if the user wants to tell the organizer about the current status
    QString txt = i18n( "Do you want to send a status update to the "
                        "organizer of this task?");
    rc = KMessageBox::questionYesNo( parent, txt );
  } else if( incidence->type() == "Event" ) {
    QString txt;
    if ( statusChanged && method == Scheduler::Request ) {
      txt = i18n( "Your status as an attendee of this event "
          "changed. Do you want to send a status update to the "
          "organizer of this event?" );
      method = Scheduler::Reply;
      rc = KMessageBox::questionYesNo( parent, txt );
    } else {
      if( isDeleting )
        txt = i18n( "You are not the organizer of this event. "
            "Deleting it will bring your calendar out of sync "
            "with the organizers calendar. Do you really want "
            "to delete it?" );
      else
        txt = i18n( "You are not the organizer of this event. "
            "Editing it will bring your calendar out of sync "
            "with the organizers calendar. Do you really want "
            "to edit it?" );
      rc = KMessageBox::questionYesNo( parent, txt );
      return ( rc == KMessageBox::Yes );
    }
  } else {
    kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
    return true;
  }
  if( rc == KMessageBox::Yes ) {
    // We will be sending out a message here. Now make sure there is
    // some summary
    if( incidence->summary().isEmpty() )
      incidence->setSummary( i18n("<No summary given>") );

    // Send the mail
    KCal::MailScheduler scheduler( mCalendar );
    scheduler.performTransaction( incidence, method );

    return true;
  } else if( rc == KMessageBox::No )
    return true;
  else
    return false;
}

#include "kogroupware.moc"
<|endoftext|>


<commit_before>
#pragma once

#include <vector>

#include "base/not_null.hpp"
#include "geometry/named_quantities.hpp"
#include "integrators/ordinary_differential_equations.hpp"
#include "ksp_plugin/burn.hpp"
#include "ksp_plugin/frames.hpp"
#include "ksp_plugin/manœuvre.hpp"
#include "physics/degrees_of_freedom.hpp"
#include "physics/discrete_trajectory.hpp"
#include "physics/ephemeris.hpp"
#include "quantities/named_quantities.hpp"
#include "quantities/quantities.hpp"
#include "serialization/ksp_plugin.pb.h"

namespace principia {

using base::not_null;
using geometry::Instant;
using integrators::AdaptiveStepSizeIntegrator;
using physics::DegreesOfFreedom;
using physics::DiscreteTrajectory;
using physics::Ephemeris;
using quantities::Length;
using quantities::Mass;
using quantities::Speed;

namespace ksp_plugin {

// A stack of |Burn|s that manages a chain of trajectories obtained by executing
// the corresponding |NavigationManœuvre|s.
class FlightPlan {
 public:
  // Creates a |FlightPlan| with no burns starting at |initial_time| with
  // |initial_degrees_of_freedom| and with the given |initial_mass|.  The
  // trajectories are computed using the given |integrator| in the given
  // |ephemeris|.
  FlightPlan(Mass const& initial_mass,
             Instant const& initial_time,
             DegreesOfFreedom<Barycentric> const& initial_degrees_of_freedom,
             Instant const& final_time,
             not_null<Ephemeris<Barycentric>*> const ephemeris,
             Ephemeris<Barycentric>::AdaptiveStepParameters const&
                 adaptive_step_parameters);

  virtual Instant initial_time() const;
  virtual Instant final_time() const;

  virtual int number_of_manœuvres() const;
  // |index| must be in [0, number_of_manœuvres()[.
  virtual NavigationManœuvre const& GetManœuvre(int const index) const;

  // The following two functions return false and have no effect if the given
  // |burn| would start before |initial_time_| or before the end of the previous
  // burn, or end after |final_time_|, or if the integration of the coasting
  // phase times out or is singular before the burn.
  virtual bool Append(Burn burn);

  // Forgets the flight plan at least before |time|.  The actual cutoff time
  // will be in a coast trajectory and maybe after |time|.  |on_empty| is run
  // if the flight plan would become empty (it is not modified before running
  // |on_empty|).
  virtual void ForgetBefore(Instant const& time,
                            std::function<void()> const& on_empty);

  // |size()| must be greater than 0.
  virtual void RemoveLast();
  // |size()| must be greater than 0.
  virtual bool ReplaceLast(Burn burn);

  // Returns false and has no effect if |final_time| is before the end of the
  // last manœuvre or before |initial_time_|.
  virtual bool SetFinalTime(Instant const& final_time);

  virtual Ephemeris<Barycentric>::AdaptiveStepParameters const&
  adaptive_step_parameters() const;

  // Sets the parameters used to compute the trajectories.  The trajectories are
  // recomputed.  Returns false (and doesn't change this object) if the
  // parameters would make it impossible to recompute the trajectories.
  virtual bool SetAdaptiveStepParameters(
      Ephemeris<Barycentric>::AdaptiveStepParameters const&
          adaptive_step_parameters);

  // Returns the number of trajectory segments in this object.
  virtual int number_of_segments() const;

  // |index| must be in [0, number_of_segments()[.  Sets the iterators to denote
  // the given trajectory segment.
  virtual void GetSegment(
      int const index,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> begin,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> end) const;
  virtual void GetAllSegments(
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> begin,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> end) const;

  void WriteToMessage(not_null<serialization::FlightPlan*> const message) const;

  // This may return a null pointer if the flight plan contained in the
  // |message| is anomalous.
  static std::unique_ptr<FlightPlan> ReadFromMessage(
      serialization::FlightPlan const& message,
      not_null<DiscreteTrajectory<Barycentric>*> const root,
      not_null<Ephemeris<Barycentric>*> const ephemeris);

  static std::int64_t constexpr max_ephemeris_steps_per_frame = 1000;

 protected:
  // For mocking.
  FlightPlan();

 private:
  // Appends |manœuvre| to |manœuvres_|, adds a burn and a coast segment.
  // |manœuvre| must fit between |start_of_last_coast()| and |final_time_|,
  // the last coast segment must end at |manœuvre.initial_time()|.
  void Append(NavigationManœuvre manœuvre);

  // Recomputes all trajectories in |segments_|.  Returns false if the
  // recomputation resulted in more than 2 anomalous segments.
  bool RecomputeSegments();

  // Flows the last segment for the duration of |manœuvre| using its intrinsic
  // acceleration.
  void BurnLastSegment(NavigationManœuvre const& manœuvre);
  // Flows the last segment until |final_time| with no intrinsic acceleration.
  void CoastLastSegment(Instant const& final_time);

  // Replaces the last segment with |segment|.  |segment| must be forked from
  // the same trajectory as the last segment, and at the same time.  |segment|
  // must not be anomalous.
  void ReplaceLastSegment(
      not_null<DiscreteTrajectory<Barycentric>*> const segment);

  // Adds a trajectory to |segments_|, forked at the end of the last one.
  void AddSegment();
  // Forgets the last segment after its fork.
  void ResetLastSegment();

  // Deletes the last segment and removes it from |segments_|.
  void PopLastSegment();

  // If the integration of a coast from the fork of |coast| until
  // |manœuvre.initial_time()| reaches the end, returns the integrated
  // trajectory.  Otherwise, returns null.
  DiscreteTrajectory<Barycentric>* CoastIfReachesManœuvreInitialTime(
      DiscreteTrajectory<Barycentric>& coast,
      NavigationManœuvre const& manœuvre);

  Instant start_of_last_coast() const;
  Instant start_of_penultimate_coast() const;

  DiscreteTrajectory<Barycentric>& last_coast();
  DiscreteTrajectory<Barycentric>& penultimate_coast();

  Mass const initial_mass_;
  Instant initial_time_;
  DegreesOfFreedom<Barycentric> initial_degrees_of_freedom_;
  Instant final_time_;
  // The root of the flight plan.  Contains a single point, not part of
  // |segments_|.  Owns all the |segments_|.
  not_null<std::unique_ptr<DiscreteTrajectory<Barycentric>>> root_;
  // Never empty; Starts and ends with a coasting segment; coasting and burning
  // alternate.  This simulates a stack.  Each segment is a fork of the previous
  // one.
  std::vector<not_null<DiscreteTrajectory<Barycentric>*>> segments_;
  std::vector<NavigationManœuvre> manœuvres_;
  not_null<Ephemeris<Barycentric>*> ephemeris_;
  Ephemeris<Barycentric>::AdaptiveStepParameters adaptive_step_parameters_;
  // The last |anomalous_segments_| of |segments_| are anomalous, i.e. they
  // either end prematurely or follow an anomalous segment; in the latter case
  // they are empty.
  // The contract of |Append| and |ReplaceLast| implies that
  // |anomalous_segments_| is at most 2: the penultimate coast is never
  // anomalous.
  int anomalous_segments_ = 0;
};

}  // namespace ksp_plugin
}  // namespace principia
<commit_msg>Typo.<commit_after>
#pragma once

#include <vector>

#include "base/not_null.hpp"
#include "geometry/named_quantities.hpp"
#include "integrators/ordinary_differential_equations.hpp"
#include "ksp_plugin/burn.hpp"
#include "ksp_plugin/frames.hpp"
#include "ksp_plugin/manœuvre.hpp"
#include "physics/degrees_of_freedom.hpp"
#include "physics/discrete_trajectory.hpp"
#include "physics/ephemeris.hpp"
#include "quantities/named_quantities.hpp"
#include "quantities/quantities.hpp"
#include "serialization/ksp_plugin.pb.h"

namespace principia {

using base::not_null;
using geometry::Instant;
using integrators::AdaptiveStepSizeIntegrator;
using physics::DegreesOfFreedom;
using physics::DiscreteTrajectory;
using physics::Ephemeris;
using quantities::Length;
using quantities::Mass;
using quantities::Speed;

namespace ksp_plugin {

// A stack of |Burn|s that manages a chain of trajectories obtained by executing
// the corresponding |NavigationManœuvre|s.
class FlightPlan {
 public:
  // Creates a |FlightPlan| with no burns starting at |initial_time| with
  // |initial_degrees_of_freedom| and with the given |initial_mass|.  The
  // trajectories are computed using the given |integrator| in the given
  // |ephemeris|.
  FlightPlan(Mass const& initial_mass,
             Instant const& initial_time,
             DegreesOfFreedom<Barycentric> const& initial_degrees_of_freedom,
             Instant const& final_time,
             not_null<Ephemeris<Barycentric>*> const ephemeris,
             Ephemeris<Barycentric>::AdaptiveStepParameters const&
                 adaptive_step_parameters);

  virtual Instant initial_time() const;
  virtual Instant final_time() const;

  virtual int number_of_manœuvres() const;
  // |index| must be in [0, number_of_manœuvres()[.
  virtual NavigationManœuvre const& GetManœuvre(int const index) const;

  // The following two functions return false and have no effect if the given
  // |burn| would start before |initial_time_| or before the end of the previous
  // burn, or end after |final_time_|, or if the integration of the coasting
  // phase times out or is singular before the burn.
  virtual bool Append(Burn burn);

  // Forgets the flight plan at least before |time|.  The actual cutoff time
  // will be in a coast trajectory and may be after |time|.  |on_empty| is run
  // if the flight plan would become empty (it is not modified before running
  // |on_empty|).
  virtual void ForgetBefore(Instant const& time,
                            std::function<void()> const& on_empty);

  // |size()| must be greater than 0.
  virtual void RemoveLast();
  // |size()| must be greater than 0.
  virtual bool ReplaceLast(Burn burn);

  // Returns false and has no effect if |final_time| is before the end of the
  // last manœuvre or before |initial_time_|.
  virtual bool SetFinalTime(Instant const& final_time);

  virtual Ephemeris<Barycentric>::AdaptiveStepParameters const&
  adaptive_step_parameters() const;

  // Sets the parameters used to compute the trajectories.  The trajectories are
  // recomputed.  Returns false (and doesn't change this object) if the
  // parameters would make it impossible to recompute the trajectories.
  virtual bool SetAdaptiveStepParameters(
      Ephemeris<Barycentric>::AdaptiveStepParameters const&
          adaptive_step_parameters);

  // Returns the number of trajectory segments in this object.
  virtual int number_of_segments() const;

  // |index| must be in [0, number_of_segments()[.  Sets the iterators to denote
  // the given trajectory segment.
  virtual void GetSegment(
      int const index,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> begin,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> end) const;
  virtual void GetAllSegments(
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> begin,
      not_null<DiscreteTrajectory<Barycentric>::Iterator*> end) const;

  void WriteToMessage(not_null<serialization::FlightPlan*> const message) const;

  // This may return a null pointer if the flight plan contained in the
  // |message| is anomalous.
  static std::unique_ptr<FlightPlan> ReadFromMessage(
      serialization::FlightPlan const& message,
      not_null<DiscreteTrajectory<Barycentric>*> const root,
      not_null<Ephemeris<Barycentric>*> const ephemeris);

  static std::int64_t constexpr max_ephemeris_steps_per_frame = 1000;

 protected:
  // For mocking.
  FlightPlan();

 private:
  // Appends |manœuvre| to |manœuvres_|, adds a burn and a coast segment.
  // |manœuvre| must fit between |start_of_last_coast()| and |final_time_|,
  // the last coast segment must end at |manœuvre.initial_time()|.
  void Append(NavigationManœuvre manœuvre);

  // Recomputes all trajectories in |segments_|.  Returns false if the
  // recomputation resulted in more than 2 anomalous segments.
  bool RecomputeSegments();

  // Flows the last segment for the duration of |manœuvre| using its intrinsic
  // acceleration.
  void BurnLastSegment(NavigationManœuvre const& manœuvre);
  // Flows the last segment until |final_time| with no intrinsic acceleration.
  void CoastLastSegment(Instant const& final_time);

  // Replaces the last segment with |segment|.  |segment| must be forked from
  // the same trajectory as the last segment, and at the same time.  |segment|
  // must not be anomalous.
  void ReplaceLastSegment(
      not_null<DiscreteTrajectory<Barycentric>*> const segment);

  // Adds a trajectory to |segments_|, forked at the end of the last one.
  void AddSegment();
  // Forgets the last segment after its fork.
  void ResetLastSegment();

  // Deletes the last segment and removes it from |segments_|.
  void PopLastSegment();

  // If the integration of a coast from the fork of |coast| until
  // |manœuvre.initial_time()| reaches the end, returns the integrated
  // trajectory.  Otherwise, returns null.
  DiscreteTrajectory<Barycentric>* CoastIfReachesManœuvreInitialTime(
      DiscreteTrajectory<Barycentric>& coast,
      NavigationManœuvre const& manœuvre);

  Instant start_of_last_coast() const;
  Instant start_of_penultimate_coast() const;

  DiscreteTrajectory<Barycentric>& last_coast();
  DiscreteTrajectory<Barycentric>& penultimate_coast();

  Mass const initial_mass_;
  Instant initial_time_;
  DegreesOfFreedom<Barycentric> initial_degrees_of_freedom_;
  Instant final_time_;
  // The root of the flight plan.  Contains a single point, not part of
  // |segments_|.  Owns all the |segments_|.
  not_null<std::unique_ptr<DiscreteTrajectory<Barycentric>>> root_;
  // Never empty; Starts and ends with a coasting segment; coasting and burning
  // alternate.  This simulates a stack.  Each segment is a fork of the previous
  // one.
  std::vector<not_null<DiscreteTrajectory<Barycentric>*>> segments_;
  std::vector<NavigationManœuvre> manœuvres_;
  not_null<Ephemeris<Barycentric>*> ephemeris_;
  Ephemeris<Barycentric>::AdaptiveStepParameters adaptive_step_parameters_;
  // The last |anomalous_segments_| of |segments_| are anomalous, i.e. they
  // either end prematurely or follow an anomalous segment; in the latter case
  // they are empty.
  // The contract of |Append| and |ReplaceLast| implies that
  // |anomalous_segments_| is at most 2: the penultimate coast is never
  // anomalous.
  int anomalous_segments_ = 0;
};

}  // namespace ksp_plugin
}  // namespace principia
<|endoftext|>


<commit_before>
#pragma once

#include "base/not_null.hpp"
#include "geometry/named_quantities.hpp"
#include "geometry/orthogonal_map.hpp"
#include "geometry/perspective.hpp"
#include "geometry/rp2_point.hpp"
#include "geometry/sphere.hpp"
#include "ksp_plugin/frames.hpp"
#include "physics/degrees_of_freedom.hpp"
#include "physics/discrete_trajectory.hpp"
#include "physics/rigid_motion.hpp"
#include "physics/trajectory.hpp"
#include "quantities/quantities.hpp"

namespace principia {
namespace ksp_plugin {
namespace internal_planetarium {

using base::not_null;
using geometry::Instant;
using geometry::OrthogonalMap;
using geometry::Perspective;
using geometry::RP2Point;
using geometry::Sphere;
using physics::DegreesOfFreedom;
using physics::DiscreteTrajectory;
using physics::RigidMotion;
using physics::Trajectory;
using quantities::Length;

// A planetarium is a system of spheres together with a perspective.  In this
// setting it is possible to draw trajectories in the projective plane.
class Planetarium final {
 public:
  // TODO(phl): All this Navigation is weird.  Should it be named Plotting?
  // In particular Navigration vs. NavigationFrame is a mess.
  // TODO(phl): Maybe replace the spheres with an ephemeris.
  Planetarium(std::vector<Sphere<Length, Barycentric>> const& spheres,
              Perspective<Navigation, Camera, Length, OrthogonalMap> const&
                  perspective,
              not_null<NavigationFrame*> plotting_frame);

  // A no-op method that just returns all the points in the |trajectory|.
  std::vector<RP2Point<Length, Camera>> PlotMethod0(
      DiscreteTrajectory<Barycentric> const& trajectory,
      Instant const& now) const;

  // A nave method that doesn't pay any attention to the perspective but tries
  // to ensure that the points before the perspective are separated by less than
  // |tolerance|.
  std::vector<RP2Point<Length, Camera>> PlotMethod1(
      Trajectory<Barycentric> const& trajectory,
      Instant const& now,
      Length const& tolerance) const;

 private:
  std::vector<Sphere<Length, Navigation>> ComputePlottableSpheres(
      Instant const& now) const;

  void AppendRP2PointIfNeeded(
      Instant const& t,
      DegreesOfFreedom<Barycentric> const& barycentric_degrees_of_freedom,
      std::vector<Sphere<Length, Navigation>> const& plottable_spheres,
      std::vector<RP2Point<Length, Camera>>& rp2_points) const;

  std::vector<Sphere<Length, Barycentric>> const spheres_;
  Perspective<Navigation, Camera, Length, OrthogonalMap> const
      perspective_;
  not_null<NavigationFrame*> const plotting_frame_;
};

}  // namespace internal_planetarium

using internal_planetarium::Planetarium;

}  // namespace ksp_plugin
}  // namespace principia
<commit_msg>Readying.<commit_after>
#pragma once

#include "base/not_null.hpp"
#include "geometry/named_quantities.hpp"
#include "geometry/orthogonal_map.hpp"
#include "geometry/perspective.hpp"
#include "geometry/rp2_point.hpp"
#include "geometry/sphere.hpp"
#include "ksp_plugin/frames.hpp"
#include "physics/degrees_of_freedom.hpp"
#include "physics/discrete_trajectory.hpp"
#include "physics/rigid_motion.hpp"
#include "physics/trajectory.hpp"
#include "quantities/quantities.hpp"

namespace principia {
namespace ksp_plugin {
namespace internal_planetarium {

using base::not_null;
using geometry::Instant;
using geometry::OrthogonalMap;
using geometry::Perspective;
using geometry::RP2Point;
using geometry::Sphere;
using physics::DegreesOfFreedom;
using physics::DiscreteTrajectory;
using physics::RigidMotion;
using physics::Trajectory;
using quantities::Length;

// A planetarium is a system of spheres together with a perspective.  In this
// setting it is possible to draw trajectories in the projective plane.
class Planetarium final {
 public:
  // TODO(phl): All this Navigation is weird.  Should it be named Plotting?
  // In particular Navigration vs. NavigationFrame is a mess.
  // TODO(phl): Maybe replace the spheres with an ephemeris.
  Planetarium(std::vector<Sphere<Length, Barycentric>> const& spheres,
              Perspective<Navigation, Camera, Length, OrthogonalMap> const&
                  perspective,
              not_null<NavigationFrame*> plotting_frame);

  // A no-op method that just returns all the points in the |trajectory|.
  std::vector<RP2Point<Length, Camera>> PlotMethod0(
      DiscreteTrajectory<Barycentric> const& trajectory,
      Instant const& now) const;

  // A nave method that doesn't pay any attention to the perspective but tries
  // to ensure that the points before the perspective are separated by less than
  // |tolerance|.
  std::vector<RP2Point<Length, Camera>> PlotMethod1(
      Trajectory<Barycentric> const& trajectory,
      Instant const& now,
      Length const& tolerance) const;

 private:
  // Computes the coordinates of the |spheres_| in the |plotting_frame_| at time
  // |now|.
  std::vector<Sphere<Length, Navigation>> ComputePlottableSpheres(
      Instant const& now) const;

  // Appends to |rp2_points| a point corresponding to the
  // |barycentric_degrees_of_freedom| transformed in the |plotting_frame_| at
  // time |t|, but only if that point is not hidden by a sphere.
  void AppendRP2PointIfNeeded(
      Instant const& t,
      DegreesOfFreedom<Barycentric> const& barycentric_degrees_of_freedom,
      std::vector<Sphere<Length, Navigation>> const& plottable_spheres,
      std::vector<RP2Point<Length, Camera>>& rp2_points) const;

  std::vector<Sphere<Length, Barycentric>> const spheres_;
  Perspective<Navigation, Camera, Length, OrthogonalMap> const
      perspective_;
  not_null<NavigationFrame*> const plotting_frame_;
};

}  // namespace internal_planetarium

using internal_planetarium::Planetarium;

}  // namespace ksp_plugin
}  // namespace principia
<|endoftext|>