<commit_before>/*************************************************************************
 *
 *  $RCSfile: macro_expander.cxx,v $
 *
 *  $Revision: 1.6 $
 *
 *  last change: $Author: obo $ $Date: 2004-06-04 03:21:29 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (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.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2002 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#include <rtl/bootstrap.hxx>

#include <cppuhelper/factory.hxx>
#include <cppuhelper/implbase3.hxx>
#include <cppuhelper/compbase3.hxx>
#include <cppuhelper/component_context.hxx>

#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/util/XMacroExpander.hpp>
#include "com/sun/star/uno/RuntimeException.hpp"

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )
#define SERVICE_NAME_A "com.sun.star.lang.MacroExpander"
#define SERVICE_NAME_B "com.sun.star.lang.BootstrapMacroExpander"
#define IMPL_NAME "com.sun.star.lang.comp.cppuhelper.BootstrapMacroExpander"

using namespace ::rtl;
using namespace ::osl;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

namespace cppu
{
//---- private forward -----------------------------------------------------------------------------
Bootstrap const & get_unorc() SAL_THROW( () );
}

namespace
{

// static stuff initialized when loading lib
static OUString s_impl_name = OUSTR(IMPL_NAME);
static Sequence< OUString > const & s_get_service_names()
{
    static Sequence< OUString > const * s_pnames = 0;
    if (! s_pnames)
    {
        MutexGuard guard( Mutex::getGlobalMutex() );
        if (! s_pnames)
        {
            static Sequence< OUString > s_names( 2 );
            s_names[ 0 ] = OUSTR(SERVICE_NAME_A);
            s_names[ 1 ] = OUSTR(SERVICE_NAME_B);
            s_pnames = &s_names;
        }
    }
    return *s_pnames;
}

typedef ::cppu::WeakComponentImplHelper3<
    util::XMacroExpander, lang::XServiceInfo, lang::XInitialization > t_uno_impl;

struct mutex_holder
{
    Mutex m_mutex;
};
class Bootstrap_MacroExpander : public mutex_holder, public t_uno_impl
{
    rtlBootstrapHandle m_bstrap;
    OUString m_rc_path;

protected:
    virtual void SAL_CALL disposing();

public:
    inline Bootstrap_MacroExpander( Reference< XComponentContext > const & ) SAL_THROW( () )
        : t_uno_impl( m_mutex ),
          m_bstrap( 0 )
        {}
    virtual ~Bootstrap_MacroExpander()
        SAL_THROW( () );

    // XMacroExpander impl
    virtual OUString SAL_CALL expandMacros( OUString const & exp )
        throw (lang::IllegalArgumentException);
    //  XInitialization impl
    virtual void SAL_CALL initialize(
        Sequence< Any > const & arguments )
        throw (Exception);
    // XServiceInfo impl
    virtual OUString SAL_CALL getImplementationName()
        throw (RuntimeException);
    virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
        throw (RuntimeException);
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
        throw (RuntimeException);
};

//__________________________________________________________________________________________________
void Bootstrap_MacroExpander::disposing()
{
    if (m_bstrap)
    {
        rtl_bootstrap_args_close( m_bstrap );
        m_bstrap = 0;
    }
}
//__________________________________________________________________________________________________
Bootstrap_MacroExpander::~Bootstrap_MacroExpander() SAL_THROW( () )
{
    if (m_bstrap)
    {
        rtl_bootstrap_args_close( m_bstrap );
        m_bstrap = 0;
    }
}

// XServiceInfo impl
//__________________________________________________________________________________________________
OUString Bootstrap_MacroExpander::getImplementationName()
    throw (RuntimeException)
{
    return s_impl_name;
}
//__________________________________________________________________________________________________
sal_Bool Bootstrap_MacroExpander::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    Sequence< OUString > const & service_names = s_get_service_names();
    OUString const * p = service_names.getConstArray();
    for ( sal_Int32 nPos = service_names.getLength(); nPos--; )
    {
        if (p[ nPos ].equals( serviceName ))
            return sal_True;
    }
    return sal_False;
}
//__________________________________________________________________________________________________
Sequence< OUString > Bootstrap_MacroExpander::getSupportedServiceNames()
    throw (RuntimeException)
{
    return s_get_service_names();
}
//  XInitialization impl
//__________________________________________________________________________________________________
void SAL_CALL Bootstrap_MacroExpander::initialize(
    Sequence< Any > const & arguments )
    throw (Exception)
{
    if (m_bstrap)
    {
        throw RuntimeException(
            OUSTR("already initialized!"),
            Reference< XInterface >() );
    }
    if (1 != arguments.getLength())
    {
        throw lang::IllegalArgumentException(
            OUSTR("invalid number of args given!  give single file url!"),
            Reference< XInterface >(),
            0 );
    }
    if (! (arguments[ 0 ] >>= m_rc_path))
    {
        throw lang::IllegalArgumentException(
            OUSTR("give file url!"),
            Reference< XInterface >(),
            0 );
    }
}

// XMacroExpander impl
//__________________________________________________________________________________________________
OUString Bootstrap_MacroExpander::expandMacros( OUString const & exp )
    throw (lang::IllegalArgumentException)
{
    // determine bootstrap handle
    rtlBootstrapHandle bstrap;
    if (m_rc_path.getLength())
    {
        // late init
        if (! m_bstrap)
        {
            rtlBootstrapHandle bstrap = rtl_bootstrap_args_open( m_rc_path.pData );
            ClearableMutexGuard guard( Mutex::getGlobalMutex() );
            if (m_bstrap)
            {
                guard.clear();
                rtl_bootstrap_args_close( bstrap );
            }
            else
            {
                m_bstrap = bstrap;
            }
        }
        bstrap = m_bstrap;
    }
    else
    {
        bstrap = ::cppu::get_unorc().getHandle();
    }

    // expand
    OUString ret( exp );
    rtl_bootstrap_expandMacros_from_handle( bstrap, &ret.pData );
    return ret;
}

//==================================================================================================
Reference< XInterface > SAL_CALL service_create(
    Reference< XComponentContext > const & xComponentContext )
    SAL_THROW( (RuntimeException) )
{
    return static_cast< ::cppu::OWeakObject * >( new Bootstrap_MacroExpander( xComponentContext ) );
}

}

namespace cppu
{

//##################################################################################################
Reference< lang::XSingleComponentFactory > create_boostrap_macro_expander_factory() SAL_THROW( () )
{
    return ::cppu::createSingleComponentFactory(
        service_create,
        s_impl_name,
        s_get_service_names() );
}

}
<commit_msg>INTEGRATION: CWS tune04 (1.5.28); FILE MERGED 2004/06/15 08:53:11 cmc 1.5.28.1: #i29636# turn global objects into local static data protected with swishy double-locked templated template<commit_after>/*************************************************************************
 *
 *  $RCSfile: macro_expander.cxx,v $
 *
 *  $Revision: 1.7 $
 *
 *  last change: $Author: hjs $ $Date: 2004-06-25 17:38:00 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (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.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2002 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#include <rtl/bootstrap.hxx>

#include <cppuhelper/factory.hxx>
#include <cppuhelper/implbase3.hxx>
#include <cppuhelper/compbase3.hxx>
#include <cppuhelper/component_context.hxx>

#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/util/XMacroExpander.hpp>
#include "com/sun/star/uno/RuntimeException.hpp"

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )
#define SERVICE_NAME_A "com.sun.star.lang.MacroExpander"
#define SERVICE_NAME_B "com.sun.star.lang.BootstrapMacroExpander"
#define IMPL_NAME "com.sun.star.lang.comp.cppuhelper.BootstrapMacroExpander"

using namespace ::rtl;
using namespace ::osl;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

namespace cppu
{
//---- private forward -----------------------------------------------------------------------------
Bootstrap const & get_unorc() SAL_THROW( () );
}

namespace
{
inline OUString s_impl_name() { return OUSTR(IMPL_NAME); }
static Sequence< OUString > const & s_get_service_names()
{
    static Sequence< OUString > const * s_pnames = 0;
    if (! s_pnames)
    {
        MutexGuard guard( Mutex::getGlobalMutex() );
        if (! s_pnames)
        {
            static Sequence< OUString > s_names( 2 );
            s_names[ 0 ] = OUSTR(SERVICE_NAME_A);
            s_names[ 1 ] = OUSTR(SERVICE_NAME_B);
            s_pnames = &s_names;
        }
    }
    return *s_pnames;
}

typedef ::cppu::WeakComponentImplHelper3<
    util::XMacroExpander, lang::XServiceInfo, lang::XInitialization > t_uno_impl;

struct mutex_holder
{
    Mutex m_mutex;
};
class Bootstrap_MacroExpander : public mutex_holder, public t_uno_impl
{
    rtlBootstrapHandle m_bstrap;
    OUString m_rc_path;

protected:
    virtual void SAL_CALL disposing();

public:
    inline Bootstrap_MacroExpander( Reference< XComponentContext > const & ) SAL_THROW( () )
        : t_uno_impl( m_mutex ),
          m_bstrap( 0 )
        {}
    virtual ~Bootstrap_MacroExpander()
        SAL_THROW( () );

    // XMacroExpander impl
    virtual OUString SAL_CALL expandMacros( OUString const & exp )
        throw (lang::IllegalArgumentException);
    //  XInitialization impl
    virtual void SAL_CALL initialize(
        Sequence< Any > const & arguments )
        throw (Exception);
    // XServiceInfo impl
    virtual OUString SAL_CALL getImplementationName()
        throw (RuntimeException);
    virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
        throw (RuntimeException);
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
        throw (RuntimeException);
};

//__________________________________________________________________________________________________
void Bootstrap_MacroExpander::disposing()
{
    if (m_bstrap)
    {
        rtl_bootstrap_args_close( m_bstrap );
        m_bstrap = 0;
    }
}
//__________________________________________________________________________________________________
Bootstrap_MacroExpander::~Bootstrap_MacroExpander() SAL_THROW( () )
{
    if (m_bstrap)
    {
        rtl_bootstrap_args_close( m_bstrap );
        m_bstrap = 0;
    }
}

// XServiceInfo impl
//__________________________________________________________________________________________________
OUString Bootstrap_MacroExpander::getImplementationName()
    throw (RuntimeException)
{
    return s_impl_name();
}
//__________________________________________________________________________________________________
sal_Bool Bootstrap_MacroExpander::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    Sequence< OUString > const & service_names = s_get_service_names();
    OUString const * p = service_names.getConstArray();
    for ( sal_Int32 nPos = service_names.getLength(); nPos--; )
    {
        if (p[ nPos ].equals( serviceName ))
            return sal_True;
    }
    return sal_False;
}
//__________________________________________________________________________________________________
Sequence< OUString > Bootstrap_MacroExpander::getSupportedServiceNames()
    throw (RuntimeException)
{
    return s_get_service_names();
}
//  XInitialization impl
//__________________________________________________________________________________________________
void SAL_CALL Bootstrap_MacroExpander::initialize(
    Sequence< Any > const & arguments )
    throw (Exception)
{
    if (m_bstrap)
    {
        throw RuntimeException(
            OUSTR("already initialized!"),
            Reference< XInterface >() );
    }
    if (1 != arguments.getLength())
    {
        throw lang::IllegalArgumentException(
            OUSTR("invalid number of args given!  give single file url!"),
            Reference< XInterface >(),
            0 );
    }
    if (! (arguments[ 0 ] >>= m_rc_path))
    {
        throw lang::IllegalArgumentException(
            OUSTR("give file url!"),
            Reference< XInterface >(),
            0 );
    }
}

// XMacroExpander impl
//__________________________________________________________________________________________________
OUString Bootstrap_MacroExpander::expandMacros( OUString const & exp )
    throw (lang::IllegalArgumentException)
{
    // determine bootstrap handle
    rtlBootstrapHandle bstrap;
    if (m_rc_path.getLength())
    {
        // late init
        if (! m_bstrap)
        {
            rtlBootstrapHandle bstrap = rtl_bootstrap_args_open( m_rc_path.pData );
            ClearableMutexGuard guard( Mutex::getGlobalMutex() );
            if (m_bstrap)
            {
                guard.clear();
                rtl_bootstrap_args_close( bstrap );
            }
            else
            {
                m_bstrap = bstrap;
            }
        }
        bstrap = m_bstrap;
    }
    else
    {
        bstrap = ::cppu::get_unorc().getHandle();
    }

    // expand
    OUString ret( exp );
    rtl_bootstrap_expandMacros_from_handle( bstrap, &ret.pData );
    return ret;
}

//==================================================================================================
Reference< XInterface > SAL_CALL service_create(
    Reference< XComponentContext > const & xComponentContext )
    SAL_THROW( (RuntimeException) )
{
    return static_cast< ::cppu::OWeakObject * >( new Bootstrap_MacroExpander( xComponentContext ) );
}

}

namespace cppu
{

//##################################################################################################
Reference< lang::XSingleComponentFactory > create_boostrap_macro_expander_factory() SAL_THROW( () )
{
    return ::cppu::createSingleComponentFactory(
        service_create,
        s_impl_name(),
        s_get_service_names() );
}

}
<|endoftext|>


<commit_before>#include <signal.h>
#include <google/profiler.h>

#include "thread.h"
#include "io_interface.h"

#include "bfs_graph.h"

const int VERTEX_BUF_SIZE = 1024;

class graph_config
{
	int num_threads;
	std::string prof_file;
public:
	graph_config() {
		num_threads = 4;
	}

	void print_help();
	void print();

	void init(const config_map &map);

	const std::string &get_prof_file() const {
		return prof_file;
	}

	int get_num_threads() const {
		return num_threads;
	}
} graph_conf;

/**
 * This callback is to process a vertex.
 */
class bfs_callback: public callback
{
	bfs_graph *graph;
public:
	bfs_callback(bfs_graph *graph) {
		this->graph = graph;
	}

	int invoke(io_request *reqs[], int num);
};

int bfs_callback::invoke(io_request *reqs[], int num)
{
	vertex_id_t max_id = graph->get_max_vertex_id();
	vertex_id_t min_id = graph->get_min_vertex_id();

	std::vector<vertex_id_t> neighbors;
	for (int i = 0; i < num; i++) {
		char *buf = reqs[i]->get_buf();
		ext_mem_undirected_vertex *v = ext_mem_undirected_vertex::deserialize(
				buf, reqs[i]->get_size());
		// We need to add the neighbors of the vertex to the queue of
		// the next level.
		for (int j = 0; j < v->get_num_edges(); j++) {
			vertex_id_t id = v->get_edge(j).get_to();
			assert(id >= min_id && id <= max_id);
			bfs_vertex &info = graph->get_vertex(id);
			// If the vertex has been visited, we can skip it.
			if (info.has_visited())
				continue;
			neighbors.push_back(id);
		}
		delete [] buf;
	}
	graph->add_next_vertices(neighbors.data(), neighbors.size());
//	printf("add %ld vertices to the next level\n", neighbors.size());
	return 0;
}

class worker_thread: public thread
{
	file_io_factory *factory;
	io_interface *io;
	bfs_graph *graph;
public:
	worker_thread(bfs_graph *graph, file_io_factory *factory,
			int node_id): thread("worker_thread", node_id) {
		this->graph = graph;
		this->io = NULL;
		this->factory = factory;
	}

	void run();
	void init();
};

void worker_thread::init()
{
	io = factory->create_io(this);
	io->init();
	io->set_callback(new bfs_callback(graph));
}

void worker_thread::run()
{
	vertex_id_t vertex_buf[VERTEX_BUF_SIZE];
	io_request reqs[VERTEX_BUF_SIZE];

	while (true) {
		int num_visited = 0;
		int num = graph->get_curr_vertices(vertex_buf, VERTEX_BUF_SIZE);
		num_visited += num;
		while (num > 0) {
			for (int i = 0; i < num; i++) {
				bfs_vertex &info = graph->get_vertex(vertex_buf[i]);
				reqs[i].init(new char[info.get_ext_mem_size()],
						info.get_ext_mem_off(),
						// TODO I might need to set the node id.
						info.get_ext_mem_size(), READ, io, -1);
			}
			io->access(reqs, num);
			int num2wait = num / 10;
			if (num2wait == 0)
				num2wait = 1;
			io->wait4complete(num2wait);
			num = graph->get_curr_vertices(vertex_buf, VERTEX_BUF_SIZE);
			num_visited += num;
		}
		io->wait4complete(io->num_pending_ios());
		printf("thread %d visited %d vertices\n", this->get_id(), num_visited);

		// Now we have finished this level, we can progress to the next level.
		bool completed = graph->progress_next_level();
		printf("thread %d finish in a level, completed? %d\n", get_id(), completed);
		if (completed)
			break;
	}
	stop();
	io->print_stat(graph_conf.get_num_threads());
}

bfs_graph::bfs_graph(int num_threads, int num_nodes,
		const std::string &graph_file, const std::string &index_file)
{
	is_complete = false;

	vertex_index *indices = vertex_index::load(index_file);
	vertices.resize(indices->get_num_vertices());
	for (size_t i = 0; i < vertices.size(); i++) {
		off_t off = indices->get_vertex_off(i);
		int size = indices->get_vertex_size(i);
		vertices[i] = bfs_vertex(i, off, size);
	}
	vertex_index::destroy(indices);

	queue = new thread_safe_FIFO_queue<vertex_id_t>(-1, PAGE_SIZE, INT_MAX);
	next_queue = new thread_safe_FIFO_queue<vertex_id_t>(-1, PAGE_SIZE, INT_MAX);

	pthread_mutex_init(&lock, NULL);
	pthread_barrier_init(&barrier1, NULL, num_threads);
	pthread_barrier_init(&barrier2, NULL, num_threads);

	file_io_factory *factory = create_io_factory(graph_file,
			GLOBAL_CACHE_ACCESS);
	assert(num_threads % num_nodes == 0);
	for (int i = 0; i < num_threads; i++) {
		worker_thread *t = new worker_thread(this, factory,
				num_threads % num_nodes);
		t->start();
		worker_threads.push_back(t);
	}
	first_thread = worker_threads[0];
}

void bfs_graph::start(vertex_id_t id)
{
	printf("start on vertex %ld\n", id);
	queue->add(&id, 1);
	num_visited_vertices.inc(1);
	worker_threads[0]->activate();
}

void bfs_graph::add_next_vertices(vertex_id_t vertices[], int num)
{
	vertex_id_t to_add[num];
	int num_to_add = 0;

	for (int i = 0; i < num; i++) {
		bfs_vertex &v = get_vertex(vertices[i]);
		// When a vertex is added to the queue, we mark it as visited.
		// Therefore, a vertex can only be added to the queue once and
		// can only be visited once.
		if (!v.set_visited(true))
			to_add[num_to_add++] = vertices[i];
	}
	num_visited_vertices.inc(num_to_add);
	int num_added = next_queue->add(to_add, num_to_add);
	assert(num_added == num_to_add);
}

bool bfs_graph::progress_next_level()
{
	// We have to make sure all threads have reach here, so we can switch
	// queues to progress to the next level.
	// If the queue of the next level is empty, the program can terminate.
	int rc = pthread_barrier_wait(&barrier1);
	if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
	{
		printf("Could not wait on barrier\n");
		exit(-1);
	}
	pthread_mutex_lock(&lock);
	if (thread::get_curr_thread() == first_thread) {
		assert(queue->is_empty());
		thread_safe_FIFO_queue<vertex_id_t> *tmp = queue;
		queue = next_queue;
		next_queue = tmp;
		level.inc(1);
		printf("progress to level %d, there are %d vertices in this level\n",
				level.get(), queue->get_num_entries());
		is_complete = queue->is_empty();
	}
	pthread_mutex_unlock(&lock);

	// We need to synchronize again. Only one thread can switch the queues.
	// We have to make sure that a thread checks the queue of the current level
	// after the first thread has switched the queues.
	rc = pthread_barrier_wait(&barrier2);
	if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
	{
		printf("Could not wait on barrier\n");
		exit(-1);
	}
	return is_complete;
}

void bfs_graph::wait4complete()
{
	for (unsigned i = 0; i < worker_threads.size(); i++) {
		worker_threads[i]->join();
	}
}

void graph_config::print_help()
{
	printf("Configuration parameters in graph algorithm.\n");
	printf("\tthreads: the number of threads processing the graph\n");
}

void graph_config::print()
{
	printf("Configuration parameters in graph algorithm.\n");
	printf("\tthreads: %d\n", num_threads);
}

void graph_config::init(const config_map &map)
{
	map.read_option_int("threads", num_threads);
	map.read_option("prof_file", prof_file);
}

void int_handler(int sig_num)
{
	if (!graph_conf.get_prof_file().empty())
		ProfilerStop();
	exit(0);
}

int main(int argc, char *argv[])
{
	if (argc < 5) {
		fprintf(stderr, "bfs conf_file graph_file index_file start_vertex\n");
		graph_conf.print_help();
		params.print_help();
		exit(-1);
	}

	std::string conf_file = argv[1];
	std::string graph_file = argv[2];
	std::string index_file = argv[3];
	vertex_id_t start_vertex = atoi(argv[4]);

	config_map configs(conf_file);
	configs.add_options(argv + 4, argc - 4);
	graph_conf.init(configs);
	graph_conf.print();

	signal(SIGINT, int_handler);
	init_io_system(configs);

	bfs_graph *graph = bfs_graph::create(graph_conf.get_num_threads(),
			params.get_num_nodes(), graph_file, index_file);
	printf("BFS starts\n");
	printf("prof_file: %s\n", graph_conf.get_prof_file().c_str());
	if (!graph_conf.get_prof_file().empty())
		ProfilerStart(graph_conf.get_prof_file().c_str());
	graph->start(start_vertex);
	graph->wait4complete();
	if (!graph_conf.get_prof_file().empty())
		ProfilerStop();
	print_io_thread_stat();
	printf("BFS from vertex 0 visits %d vertices\n", graph->get_num_visited_vertices());
}
<commit_msg>[Graph]: measure the running time of BFS.<commit_after>#include <signal.h>
#include <google/profiler.h>

#include "thread.h"
#include "io_interface.h"

#include "bfs_graph.h"

const int VERTEX_BUF_SIZE = 1024;

class graph_config
{
	int num_threads;
	std::string prof_file;
public:
	graph_config() {
		num_threads = 4;
	}

	void print_help();
	void print();

	void init(const config_map &map);

	const std::string &get_prof_file() const {
		return prof_file;
	}

	int get_num_threads() const {
		return num_threads;
	}
} graph_conf;

/**
 * This callback is to process a vertex.
 */
class bfs_callback: public callback
{
	bfs_graph *graph;
public:
	bfs_callback(bfs_graph *graph) {
		this->graph = graph;
	}

	int invoke(io_request *reqs[], int num);
};

int bfs_callback::invoke(io_request *reqs[], int num)
{
	vertex_id_t max_id = graph->get_max_vertex_id();
	vertex_id_t min_id = graph->get_min_vertex_id();

	std::vector<vertex_id_t> neighbors;
	for (int i = 0; i < num; i++) {
		char *buf = reqs[i]->get_buf();
		ext_mem_undirected_vertex *v = ext_mem_undirected_vertex::deserialize(
				buf, reqs[i]->get_size());
		// We need to add the neighbors of the vertex to the queue of
		// the next level.
		for (int j = 0; j < v->get_num_edges(); j++) {
			vertex_id_t id = v->get_edge(j).get_to();
			assert(id >= min_id && id <= max_id);
			bfs_vertex &info = graph->get_vertex(id);
			// If the vertex has been visited, we can skip it.
			if (info.has_visited())
				continue;
			neighbors.push_back(id);
		}
		delete [] buf;
	}
	graph->add_next_vertices(neighbors.data(), neighbors.size());
//	printf("add %ld vertices to the next level\n", neighbors.size());
	return 0;
}

class worker_thread: public thread
{
	file_io_factory *factory;
	io_interface *io;
	bfs_graph *graph;
public:
	worker_thread(bfs_graph *graph, file_io_factory *factory,
			int node_id): thread("worker_thread", node_id) {
		this->graph = graph;
		this->io = NULL;
		this->factory = factory;
	}

	void run();
	void init();
};

void worker_thread::init()
{
	io = factory->create_io(this);
	io->init();
	io->set_callback(new bfs_callback(graph));
}

void worker_thread::run()
{
	vertex_id_t vertex_buf[VERTEX_BUF_SIZE];
	io_request reqs[VERTEX_BUF_SIZE];

	while (true) {
		int num_visited = 0;
		int num = graph->get_curr_vertices(vertex_buf, VERTEX_BUF_SIZE);
		num_visited += num;
		while (num > 0) {
			for (int i = 0; i < num; i++) {
				bfs_vertex &info = graph->get_vertex(vertex_buf[i]);
				reqs[i].init(new char[info.get_ext_mem_size()],
						info.get_ext_mem_off(),
						// TODO I might need to set the node id.
						info.get_ext_mem_size(), READ, io, -1);
			}
			io->access(reqs, num);
			int num2wait = num / 10;
			if (num2wait == 0)
				num2wait = 1;
			io->wait4complete(num2wait);
			num = graph->get_curr_vertices(vertex_buf, VERTEX_BUF_SIZE);
			num_visited += num;
		}
		io->wait4complete(io->num_pending_ios());
		printf("thread %d visited %d vertices\n", this->get_id(), num_visited);

		// Now we have finished this level, we can progress to the next level.
		bool completed = graph->progress_next_level();
		printf("thread %d finish in a level, completed? %d\n", get_id(), completed);
		if (completed)
			break;
	}
	stop();
	io->print_stat(graph_conf.get_num_threads());
}

bfs_graph::bfs_graph(int num_threads, int num_nodes,
		const std::string &graph_file, const std::string &index_file)
{
	is_complete = false;

	vertex_index *indices = vertex_index::load(index_file);
	vertices.resize(indices->get_num_vertices());
	for (size_t i = 0; i < vertices.size(); i++) {
		off_t off = indices->get_vertex_off(i);
		int size = indices->get_vertex_size(i);
		vertices[i] = bfs_vertex(i, off, size);
	}
	vertex_index::destroy(indices);

	queue = new thread_safe_FIFO_queue<vertex_id_t>(-1, PAGE_SIZE, INT_MAX);
	next_queue = new thread_safe_FIFO_queue<vertex_id_t>(-1, PAGE_SIZE, INT_MAX);

	pthread_mutex_init(&lock, NULL);
	pthread_barrier_init(&barrier1, NULL, num_threads);
	pthread_barrier_init(&barrier2, NULL, num_threads);

	file_io_factory *factory = create_io_factory(graph_file,
			GLOBAL_CACHE_ACCESS);
	assert(num_threads % num_nodes == 0);
	for (int i = 0; i < num_threads; i++) {
		worker_thread *t = new worker_thread(this, factory,
				num_threads % num_nodes);
		t->start();
		worker_threads.push_back(t);
	}
	first_thread = worker_threads[0];
}

void bfs_graph::start(vertex_id_t id)
{
	printf("start on vertex %ld\n", id);
	queue->add(&id, 1);
	num_visited_vertices.inc(1);
	worker_threads[0]->activate();
}

void bfs_graph::add_next_vertices(vertex_id_t vertices[], int num)
{
	vertex_id_t to_add[num];
	int num_to_add = 0;

	for (int i = 0; i < num; i++) {
		bfs_vertex &v = get_vertex(vertices[i]);
		// When a vertex is added to the queue, we mark it as visited.
		// Therefore, a vertex can only be added to the queue once and
		// can only be visited once.
		if (!v.set_visited(true))
			to_add[num_to_add++] = vertices[i];
	}
	num_visited_vertices.inc(num_to_add);
	int num_added = next_queue->add(to_add, num_to_add);
	assert(num_added == num_to_add);
}

bool bfs_graph::progress_next_level()
{
	// We have to make sure all threads have reach here, so we can switch
	// queues to progress to the next level.
	// If the queue of the next level is empty, the program can terminate.
	int rc = pthread_barrier_wait(&barrier1);
	if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
	{
		printf("Could not wait on barrier\n");
		exit(-1);
	}
	pthread_mutex_lock(&lock);
	if (thread::get_curr_thread() == first_thread) {
		assert(queue->is_empty());
		thread_safe_FIFO_queue<vertex_id_t> *tmp = queue;
		queue = next_queue;
		next_queue = tmp;
		level.inc(1);
		printf("progress to level %d, there are %d vertices in this level\n",
				level.get(), queue->get_num_entries());
		is_complete = queue->is_empty();
	}
	pthread_mutex_unlock(&lock);

	// We need to synchronize again. Only one thread can switch the queues.
	// We have to make sure that a thread checks the queue of the current level
	// after the first thread has switched the queues.
	rc = pthread_barrier_wait(&barrier2);
	if(rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD)
	{
		printf("Could not wait on barrier\n");
		exit(-1);
	}
	return is_complete;
}

void bfs_graph::wait4complete()
{
	for (unsigned i = 0; i < worker_threads.size(); i++) {
		worker_threads[i]->join();
	}
}

void graph_config::print_help()
{
	printf("Configuration parameters in graph algorithm.\n");
	printf("\tthreads: the number of threads processing the graph\n");
}

void graph_config::print()
{
	printf("Configuration parameters in graph algorithm.\n");
	printf("\tthreads: %d\n", num_threads);
}

void graph_config::init(const config_map &map)
{
	map.read_option_int("threads", num_threads);
	map.read_option("prof_file", prof_file);
}

void int_handler(int sig_num)
{
	if (!graph_conf.get_prof_file().empty())
		ProfilerStop();
	exit(0);
}

int main(int argc, char *argv[])
{
	if (argc < 5) {
		fprintf(stderr, "bfs conf_file graph_file index_file start_vertex\n");
		graph_conf.print_help();
		params.print_help();
		exit(-1);
	}

	std::string conf_file = argv[1];
	std::string graph_file = argv[2];
	std::string index_file = argv[3];
	vertex_id_t start_vertex = atoi(argv[4]);

	config_map configs(conf_file);
	configs.add_options(argv + 4, argc - 4);
	graph_conf.init(configs);
	graph_conf.print();

	signal(SIGINT, int_handler);
	init_io_system(configs);

	bfs_graph *graph = bfs_graph::create(graph_conf.get_num_threads(),
			params.get_num_nodes(), graph_file, index_file);
	printf("BFS starts\n");
	printf("prof_file: %s\n", graph_conf.get_prof_file().c_str());
	if (!graph_conf.get_prof_file().empty())
		ProfilerStart(graph_conf.get_prof_file().c_str());

	struct timeval start, end;
	gettimeofday(&start, NULL);
	graph->start(start_vertex);
	graph->wait4complete();
	gettimeofday(&end, NULL);

	if (!graph_conf.get_prof_file().empty())
		ProfilerStop();
	print_io_thread_stat();
	printf("BFS from vertex %ld visits %d vertices. It takes %f seconds\n",
			start_vertex, graph->get_num_visited_vertices(),
			time_diff(start, end));
}
<|endoftext|>


<commit_before>// Copyright (c) 2017-2019 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "chainparams.h"
#include "server.h"
#include "validation.h"

#include "llmq/quorums.h"
#include "llmq/quorums_blockprocessor.h"
#include "llmq/quorums_debug.h"
#include "llmq/quorums_dkgsession.h"
#include "llmq/quorums_signing.h"

void quorum_list_help()
{
    throw std::runtime_error(
            "quorum list ( count )\n"
            "\nArguments:\n"
            "1. count           (number, optional) Number of quorums to list. Will list active quorums\n"
            "                   if \"count\" is not specified.\n"
            "\nResult:\n"
            "{\n"
            "  \"quorumName\" : [                    (array of strings) List of quorum hashes per some quorum type.\n"
            "     \"quorumHash\"                     (string) Quorum hash. Note: most recent quorums come first.\n"
            "     ,...\n"
            "  ],\n"
            "}\n"
            "\nExamples:\n"
            + HelpExampleCli("quorum", "list")
            + HelpExampleCli("quorum", "list 10")
            + HelpExampleRpc("quorum", "list, 10")
    );
}

UniValue quorum_list(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2))
        quorum_list_help();

    LOCK(cs_main);

    int count = -1;
    if (request.params.size() > 1) {
        count = ParseInt32V(request.params[1], "count");
        if (count < 0) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "count can't be negative");
        }
    }

    UniValue ret(UniValue::VOBJ);

    for (auto& p : Params().GetConsensus().llmqs) {
        UniValue v(UniValue::VARR);

        auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip(), count > -1 ? count : p.second.signingActiveQuorumCount);
        for (auto& q : quorums) {
            v.push_back(q->qc.quorumHash.ToString());
        }

        ret.push_back(Pair(p.second.name, v));
    }

    return ret;
}

void quorum_info_help()
{
    throw std::runtime_error(
            "quorum info llmqType \"quorumHash\" ( includeSkShare )\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"quorumHash\"          (string, required) Block hash of quorum.\n"
            "3. includeSkShare        (boolean, optional) Include secret key share in output.\n"
    );
}

UniValue quorum_info(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
        quorum_info_help();

    LOCK(cs_main);

    Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
    if (!Params().GetConsensus().llmqs.count(llmqType)) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
    }

    const auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);

    uint256 quorumHash = ParseHashV(request.params[2], "quorumHash");
    bool includeSkShare = false;
    if (request.params.size() > 3) {
        includeSkShare = ParseBoolV(request.params[3], "includeSkShare");
    }

    auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
    if (!quorum) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
    }

    UniValue ret(UniValue::VOBJ);

    ret.push_back(Pair("height", quorum->height));
    ret.push_back(Pair("quorumHash", quorum->qc.quorumHash.ToString()));
    ret.push_back(Pair("minedBlock", quorum->minedBlockHash.ToString()));

    UniValue membersArr(UniValue::VARR);
    for (size_t i = 0; i < quorum->members.size(); i++) {
        auto& dmn = quorum->members[i];
        UniValue mo(UniValue::VOBJ);
        mo.push_back(Pair("proTxHash", dmn->proTxHash.ToString()));
        mo.push_back(Pair("valid", quorum->qc.validMembers[i]));
        if (quorum->qc.validMembers[i]) {
            CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
            if (pubKey.IsValid()) {
                mo.push_back(Pair("pubKeyShare", pubKey.ToString()));
            }
        }
        membersArr.push_back(mo);
    }

    ret.push_back(Pair("members", membersArr));
    ret.push_back(Pair("quorumPublicKey", quorum->qc.quorumPublicKey.ToString()));
    CBLSSecretKey skShare = quorum->GetSkShare();
    if (includeSkShare && skShare.IsValid()) {
        ret.push_back(Pair("secretKeyShare", skShare.ToString()));
    }

    return ret;
}

void quorum_dkgstatus_help()
{
    throw std::runtime_error(
            "quorum dkgstatus ( detail_level )\n"
            "Return the status of the current DKG process.\n"
            "Works only when SPORK_17_QUORUM_DKG_ENABLED spork is ON.\n"
            "\nArguments:\n"
            "1. detail_level         (number, optional, default=0) Detail level of output.\n"
            "                        0=Only show counts. 1=Show member indexes. 2=Show member's ProTxHashes.\n"
    );
}

UniValue quorum_dkgstatus(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() < 1 || request.params.size() > 2)) {
        quorum_dkgstatus_help();
    }

    int detailLevel = 0;
    if (request.params.size() > 1) {
        detailLevel = ParseInt32V(request.params[1], "detail_level");
        if (detailLevel < 0 || detailLevel > 2) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid detail_level");
        }
    }

    llmq::CDKGDebugStatus status;
    llmq::quorumDKGDebugManager->GetLocalDebugStatus(status);

    auto ret = status.ToJson(detailLevel);

    LOCK(cs_main);
    int tipHeight = chainActive.Height();

    UniValue minableCommitments(UniValue::VOBJ);
    for (const auto& p : Params().GetConsensus().llmqs) {
        auto& params = p.second;
        llmq::CFinalCommitment fqc;
        if (llmq::quorumBlockProcessor->GetMinableCommitment(params.type, tipHeight, fqc)) {
            UniValue obj(UniValue::VOBJ);
            fqc.ToJson(obj);
            minableCommitments.push_back(Pair(params.name, obj));
        }
    }

    ret.push_back(Pair("minableCommitments", minableCommitments));

    return ret;
}

void quorum_sign_help()
{
    throw std::runtime_error(
            "quorum sign llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_hasrecsig_help()
{
    throw std::runtime_error(
            "quorum hasrecsig llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_getrecsig_help()
{
    throw std::runtime_error(
            "quorum getrecsig llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_isconflicting_help()
{
    throw std::runtime_error(
            "quorum isconflicting llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

UniValue quorum_sigs_cmd(const JSONRPCRequest& request)
{
    auto cmd = request.params[0].get_str();
    if (request.fHelp || (request.params.size() != 4)) {
        if (cmd == "sign") {
            quorum_sign_help();
        } else if (cmd == "hasrecsig") {
            quorum_hasrecsig_help();
        } else if (cmd == "getrecsig") {
            quorum_getrecsig_help();
        } else if (cmd == "isconflicting") {
            quorum_isconflicting_help();
        } else {
            // shouldn't happen as it's already handled by the caller
            throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
        }
    }

    Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
    if (!Params().GetConsensus().llmqs.count(llmqType)) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
    }

    uint256 id = ParseHashV(request.params[2], "id");
    uint256 msgHash = ParseHashV(request.params[3], "msgHash");

    if (cmd == "sign") {
        return llmq::quorumSigningManager->AsyncSignIfMember(llmqType, id, msgHash);
    } else if (cmd == "hasrecsig") {
        return llmq::quorumSigningManager->HasRecoveredSig(llmqType, id, msgHash);
    } else if (cmd == "getrecsig") {
        llmq::CRecoveredSig recSig;
        if (!llmq::quorumSigningManager->GetRecoveredSigForId(llmqType, id, recSig)) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
        }
        if (recSig.msgHash != msgHash) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
        }
        return recSig.ToJson();
    } else if (cmd == "isconflicting") {
        return llmq::quorumSigningManager->IsConflicting(llmqType, id, msgHash);
    } else {
        // shouldn't happen as it's already handled by the caller
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
    }
}

void quorum_dkgsimerror_help()
{
    throw std::runtime_error(
            "quorum dkgsimerror \"type\" rate\n"
            "This enables simulation of errors and malicious behaviour in the DKG. Do NOT use this on mainnet\n"
            "as you will get yourself very likely PoSe banned for this.\n"
            "\nArguments:\n"
            "1. \"type\"                (string, required) Error type.\n"
            "2. rate                  (number, required) Rate at which to simulate this error type.\n"
    );
}

UniValue quorum_dkgsimerror(const JSONRPCRequest& request)
{
    auto cmd = request.params[0].get_str();
    if (request.fHelp || (request.params.size() != 3)) {
        quorum_dkgsimerror_help();
    }

    std::string type = request.params[1].get_str();
    double rate = ParseDoubleV(request.params[2], "rate");

    if (rate < 0 || rate > 1) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid rate. Must be between 0 and 1");
    }

    llmq::SetSimulatedDKGErrorRate(type, rate);

    return UniValue();
}

[[ noreturn ]] void quorum_help()
{
    throw std::runtime_error(
            "quorum \"command\" ...\n"
            "Set of commands for quorums/LLMQs.\n"
            "To get help on individual commands, use \"help quorum command\".\n"
            "\nArguments:\n"
            "1. \"command\"        (string, required) The command to execute\n"
            "\nAvailable commands:\n"
            "  list              - List of on-chain quorums\n"
            "  info              - Return information about a quorum\n"
            "  dkgsimerror       - Simulates DKG errors and malicious behavior.\n"
            "  dkgstatus         - Return the status of the current DKG process\n"
            "  sign              - Threshold-sign a message\n"
            "  hasrecsig         - Test if a valid recovered signature is present\n"
            "  getrecsig         - Get a recovered signature\n"
            "  isconflicting     - Test if a conflict exists\n"
    );
}

UniValue quorum(const JSONRPCRequest& request)
{
    if (request.fHelp && request.params.empty()) {
        quorum_help();
    }

    std::string command;
    if (request.params.size() >= 1) {
        command = request.params[0].get_str();
    }

    if (command == "list") {
        return quorum_list(request);
    } else if (command == "info") {
        return quorum_info(request);
    } else if (command == "dkgstatus") {
        return quorum_dkgstatus(request);
    } else if (command == "sign" || command == "hasrecsig" || command == "getrecsig" || command == "isconflicting") {
        return quorum_sigs_cmd(request);
    } else if (command == "dkgsimerror") {
        return quorum_dkgsimerror(request);
    } else {
        quorum_help();
    }
}

static const CRPCCommand commands[] =
{ //  category              name                      actor (function)         okSafeMode
  //  --------------------- ------------------------  -----------------------  ----------
    { "evo",                "quorum",                 &quorum,                 false, {}  },
};

void RegisterQuorumsRPCCommands(CRPCTable &tableRPC)
{
    for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
        tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
<commit_msg>Implement "quorum memberof" (#3004)<commit_after>// Copyright (c) 2017-2019 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "chainparams.h"
#include "server.h"
#include "validation.h"

#include "llmq/quorums.h"
#include "llmq/quorums_blockprocessor.h"
#include "llmq/quorums_debug.h"
#include "llmq/quorums_dkgsession.h"
#include "llmq/quorums_signing.h"

void quorum_list_help()
{
    throw std::runtime_error(
            "quorum list ( count )\n"
            "\nArguments:\n"
            "1. count           (number, optional) Number of quorums to list. Will list active quorums\n"
            "                   if \"count\" is not specified.\n"
            "\nResult:\n"
            "{\n"
            "  \"quorumName\" : [                    (array of strings) List of quorum hashes per some quorum type.\n"
            "     \"quorumHash\"                     (string) Quorum hash. Note: most recent quorums come first.\n"
            "     ,...\n"
            "  ],\n"
            "}\n"
            "\nExamples:\n"
            + HelpExampleCli("quorum", "list")
            + HelpExampleCli("quorum", "list 10")
            + HelpExampleRpc("quorum", "list, 10")
    );
}

UniValue quorum_list(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2))
        quorum_list_help();

    LOCK(cs_main);

    int count = -1;
    if (request.params.size() > 1) {
        count = ParseInt32V(request.params[1], "count");
        if (count < 0) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "count can't be negative");
        }
    }

    UniValue ret(UniValue::VOBJ);

    for (auto& p : Params().GetConsensus().llmqs) {
        UniValue v(UniValue::VARR);

        auto quorums = llmq::quorumManager->ScanQuorums(p.first, chainActive.Tip(), count > -1 ? count : p.second.signingActiveQuorumCount);
        for (auto& q : quorums) {
            v.push_back(q->qc.quorumHash.ToString());
        }

        ret.push_back(Pair(p.second.name, v));
    }

    return ret;
}

void quorum_info_help()
{
    throw std::runtime_error(
            "quorum info llmqType \"quorumHash\" ( includeSkShare )\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"quorumHash\"          (string, required) Block hash of quorum.\n"
            "3. includeSkShare        (boolean, optional) Include secret key share in output.\n"
    );
}

UniValue BuildQuorumInfo(const llmq::CQuorumCPtr& quorum, bool includeMembers, bool includeSkShare)
{
    UniValue ret(UniValue::VOBJ);

    ret.push_back(Pair("height", quorum->height));
    ret.push_back(Pair("type", quorum->params.name));
    ret.push_back(Pair("quorumHash", quorum->qc.quorumHash.ToString()));
    ret.push_back(Pair("minedBlock", quorum->minedBlockHash.ToString()));

    if (includeMembers) {
        UniValue membersArr(UniValue::VARR);
        for (size_t i = 0; i < quorum->members.size(); i++) {
            auto& dmn = quorum->members[i];
            UniValue mo(UniValue::VOBJ);
            mo.push_back(Pair("proTxHash", dmn->proTxHash.ToString()));
            mo.push_back(Pair("valid", quorum->qc.validMembers[i]));
            if (quorum->qc.validMembers[i]) {
                CBLSPublicKey pubKey = quorum->GetPubKeyShare(i);
                if (pubKey.IsValid()) {
                    mo.push_back(Pair("pubKeyShare", pubKey.ToString()));
                }
            }
            membersArr.push_back(mo);
        }

        ret.push_back(Pair("members", membersArr));
    }
    ret.push_back(Pair("quorumPublicKey", quorum->qc.quorumPublicKey.ToString()));
    CBLSSecretKey skShare = quorum->GetSkShare();
    if (includeSkShare && skShare.IsValid()) {
        ret.push_back(Pair("secretKeyShare", skShare.ToString()));
    }
    return ret;
}

UniValue quorum_info(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() != 3 && request.params.size() != 4))
        quorum_info_help();

    LOCK(cs_main);

    Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
    if (!Params().GetConsensus().llmqs.count(llmqType)) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
    }

    const auto& llmqParams = Params().GetConsensus().llmqs.at(llmqType);

    uint256 quorumHash = ParseHashV(request.params[2], "quorumHash");
    bool includeSkShare = false;
    if (request.params.size() > 3) {
        includeSkShare = ParseBoolV(request.params[3], "includeSkShare");
    }

    auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash);
    if (!quorum) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "quorum not found");
    }

    return BuildQuorumInfo(quorum, true, includeSkShare);
}

void quorum_dkgstatus_help()
{
    throw std::runtime_error(
            "quorum dkgstatus ( detail_level )\n"
            "Return the status of the current DKG process.\n"
            "Works only when SPORK_17_QUORUM_DKG_ENABLED spork is ON.\n"
            "\nArguments:\n"
            "1. detail_level         (number, optional, default=0) Detail level of output.\n"
            "                        0=Only show counts. 1=Show member indexes. 2=Show member's ProTxHashes.\n"
    );
}

UniValue quorum_dkgstatus(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() < 1 || request.params.size() > 2)) {
        quorum_dkgstatus_help();
    }

    int detailLevel = 0;
    if (request.params.size() > 1) {
        detailLevel = ParseInt32V(request.params[1], "detail_level");
        if (detailLevel < 0 || detailLevel > 2) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid detail_level");
        }
    }

    llmq::CDKGDebugStatus status;
    llmq::quorumDKGDebugManager->GetLocalDebugStatus(status);

    auto ret = status.ToJson(detailLevel);

    LOCK(cs_main);
    int tipHeight = chainActive.Height();

    UniValue minableCommitments(UniValue::VOBJ);
    for (const auto& p : Params().GetConsensus().llmqs) {
        auto& params = p.second;
        llmq::CFinalCommitment fqc;
        if (llmq::quorumBlockProcessor->GetMinableCommitment(params.type, tipHeight, fqc)) {
            UniValue obj(UniValue::VOBJ);
            fqc.ToJson(obj);
            minableCommitments.push_back(Pair(params.name, obj));
        }
    }

    ret.push_back(Pair("minableCommitments", minableCommitments));

    return ret;
}

void quorum_memberof_help()
{
    throw std::runtime_error(
            "quorum memberof \"proTxHash\"\n"
            "Checks which quorums the given masternode is a member of.\n"
            "\nArguments:\n"
            "1. \"proTxHash\"                (string, required) ProTxHash of the masternode.\n"
    );
}

UniValue quorum_memberof(const JSONRPCRequest& request)
{
    if (request.fHelp || (request.params.size() != 2)) {
        quorum_memberof_help();
    }

    uint256 protxHash = ParseHashV(request.params[1], "proTxHash");

    const CBlockIndex* pindexTip;
    {
        LOCK(cs_main);
        pindexTip = chainActive.Tip();
    }

    auto mnList = deterministicMNManager->GetListForBlock(pindexTip->GetBlockHash());
    auto dmn = mnList.GetMN(protxHash);
    if (!dmn) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "masternode not found");
    }

    std::set<std::pair<Consensus::LLMQType, uint256>> quorumHashes;
    for (const auto& p : Params().GetConsensus().llmqs) {
        auto& params = p.second;
        auto quorums = llmq::quorumManager->ScanQuorums(params.type, params.signingActiveQuorumCount);
        for (auto& quorum : quorums) {
            for (auto& m : quorum->members) {
                if (m->proTxHash == dmn->proTxHash) {
                    quorumHashes.emplace(params.type, quorum->qc.quorumHash);
                }
            }
        }
    }

    UniValue result(UniValue::VARR);
    for (auto& p : quorumHashes) {
        auto quorum = llmq::quorumManager->GetQuorum(p.first, p.second);
        assert(quorum);
        result.push_back(BuildQuorumInfo(quorum, false, false));
    }

    return result;
}

void quorum_sign_help()
{
    throw std::runtime_error(
            "quorum sign llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_hasrecsig_help()
{
    throw std::runtime_error(
            "quorum hasrecsig llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_getrecsig_help()
{
    throw std::runtime_error(
            "quorum getrecsig llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

void quorum_isconflicting_help()
{
    throw std::runtime_error(
            "quorum isconflicting llmqType \"id\" \"msgHash\"\n"
            "\nArguments:\n"
            "1. llmqType              (int, required) LLMQ type.\n"
            "2. \"id\"                  (string, required) Request id.\n"
            "3. \"msgHash\"             (string, required) Message hash.\n"
    );
}

UniValue quorum_sigs_cmd(const JSONRPCRequest& request)
{
    auto cmd = request.params[0].get_str();
    if (request.fHelp || (request.params.size() != 4)) {
        if (cmd == "sign") {
            quorum_sign_help();
        } else if (cmd == "hasrecsig") {
            quorum_hasrecsig_help();
        } else if (cmd == "getrecsig") {
            quorum_getrecsig_help();
        } else if (cmd == "isconflicting") {
            quorum_isconflicting_help();
        } else {
            // shouldn't happen as it's already handled by the caller
            throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
        }
    }

    Consensus::LLMQType llmqType = (Consensus::LLMQType)ParseInt32V(request.params[1], "llmqType");
    if (!Params().GetConsensus().llmqs.count(llmqType)) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid LLMQ type");
    }

    uint256 id = ParseHashV(request.params[2], "id");
    uint256 msgHash = ParseHashV(request.params[3], "msgHash");

    if (cmd == "sign") {
        return llmq::quorumSigningManager->AsyncSignIfMember(llmqType, id, msgHash);
    } else if (cmd == "hasrecsig") {
        return llmq::quorumSigningManager->HasRecoveredSig(llmqType, id, msgHash);
    } else if (cmd == "getrecsig") {
        llmq::CRecoveredSig recSig;
        if (!llmq::quorumSigningManager->GetRecoveredSigForId(llmqType, id, recSig)) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
        }
        if (recSig.msgHash != msgHash) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "recovered signature not found");
        }
        return recSig.ToJson();
    } else if (cmd == "isconflicting") {
        return llmq::quorumSigningManager->IsConflicting(llmqType, id, msgHash);
    } else {
        // shouldn't happen as it's already handled by the caller
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid cmd");
    }
}

void quorum_dkgsimerror_help()
{
    throw std::runtime_error(
            "quorum dkgsimerror \"type\" rate\n"
            "This enables simulation of errors and malicious behaviour in the DKG. Do NOT use this on mainnet\n"
            "as you will get yourself very likely PoSe banned for this.\n"
            "\nArguments:\n"
            "1. \"type\"                (string, required) Error type.\n"
            "2. rate                  (number, required) Rate at which to simulate this error type.\n"
    );
}

UniValue quorum_dkgsimerror(const JSONRPCRequest& request)
{
    auto cmd = request.params[0].get_str();
    if (request.fHelp || (request.params.size() != 3)) {
        quorum_dkgsimerror_help();
    }

    std::string type = request.params[1].get_str();
    double rate = ParseDoubleV(request.params[2], "rate");

    if (rate < 0 || rate > 1) {
        throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid rate. Must be between 0 and 1");
    }

    llmq::SetSimulatedDKGErrorRate(type, rate);

    return UniValue();
}

[[ noreturn ]] void quorum_help()
{
    throw std::runtime_error(
            "quorum \"command\" ...\n"
            "Set of commands for quorums/LLMQs.\n"
            "To get help on individual commands, use \"help quorum command\".\n"
            "\nArguments:\n"
            "1. \"command\"        (string, required) The command to execute\n"
            "\nAvailable commands:\n"
            "  list              - List of on-chain quorums\n"
            "  info              - Return information about a quorum\n"
            "  dkgsimerror       - Simulates DKG errors and malicious behavior.\n"
            "  dkgstatus         - Return the status of the current DKG process\n"
            "  memberof          - Checks which quorums the given masternode is a member of\n"
            "  sign              - Threshold-sign a message\n"
            "  hasrecsig         - Test if a valid recovered signature is present\n"
            "  getrecsig         - Get a recovered signature\n"
            "  isconflicting     - Test if a conflict exists\n"
    );
}

UniValue quorum(const JSONRPCRequest& request)
{
    if (request.fHelp && request.params.empty()) {
        quorum_help();
    }

    std::string command;
    if (request.params.size() >= 1) {
        command = request.params[0].get_str();
    }

    if (command == "list") {
        return quorum_list(request);
    } else if (command == "info") {
        return quorum_info(request);
    } else if (command == "dkgstatus") {
        return quorum_dkgstatus(request);
    } else if (command == "memberof") {
        return quorum_memberof(request);
    } else if (command == "sign" || command == "hasrecsig" || command == "getrecsig" || command == "isconflicting") {
        return quorum_sigs_cmd(request);
    } else if (command == "dkgsimerror") {
        return quorum_dkgsimerror(request);
    } else {
        quorum_help();
    }
}

static const CRPCCommand commands[] =
{ //  category              name                      actor (function)         okSafeMode
  //  --------------------- ------------------------  -----------------------  ----------
    { "evo",                "quorum",                 &quorum,                 false, {}  },
};

void RegisterQuorumsRPCCommands(CRPCTable &tableRPC)
{
    for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
        tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
<|endoftext|>


<commit_before>/// HEADER
#include "rqt_evaluation.h"

/// PROJECT
#include <csapex/manager/connection_type_manager.h>
#include <csapex/view/designer.h>
#include <csapex/model/graph.h>
#include <csapex/core/graphio.h>
#include <csapex/view/design_board.h>
#include <csapex/view/designer.h>
#include <csapex/core/settings.h>
#include <csapex/manager/box_manager.h>
#include <csapex/view/widget_controller.h>
#include <csapex/view/overlay.h>

/// SYSTEM
#include <pluginlib/class_list_macros.h>
#include <QStringList>
#include <QMenuBar>
#include <QBoxLayout>

PLUGINLIB_DECLARE_CLASS(csapex_rqt, CsApex, csapex_rqt::CsApex, rqt_gui_cpp::Plugin)

using namespace csapex_rqt;
using namespace csapex;

CsApex::CsApex()
    : graph_(new Graph(settings_)),
      widget_controller_(new csapex::WidgetController(graph_)),
      dispatcher_(new CommandDispatcher(graph_, widget_controller_)),
      core_(settings_, graph_, dispatcher_.get()),
      drag_io_(graph_.get(), dispatcher_.get(), widget_controller_),
      overlay_(new Overlay(graph_, dispatcher_.get(), widget_controller_)),
      board_ (new DesignBoard(graph_, dispatcher_.get(), widget_controller_, drag_io_, overlay_)),
      designer_(new Designer(graph_, dispatcher_.get(), widget_controller_, board_))
{
    BoxManager::instance().settings_ = &settings_;

    widget_controller_->setDesigner(designer_);
}

CsApex::~CsApex()
{
    delete eva_;
}

void CsApex::initPlugin(qt_gui_cpp::PluginContext& context)
{
    context_ = &context;

    eva_ = new CsApexWindow(core_, dispatcher_.get(), widget_controller_, graph_, designer_);
    eva_->showMenu();

    context_->addWidget(eva_);
}

void CsApex::shutdownPlugin()
{
}

void CsApex::saveSettings(qt_gui_cpp::Settings& plugin_settings, qt_gui_cpp::Settings& instance_settings) const
{
    instance_settings.setValue("file", core_.getSettings().getConfig().c_str());
}

void CsApex::restoreSettings(const qt_gui_cpp::Settings& plugin_settings, const qt_gui_cpp::Settings& instance_settings)
{
    QString file = instance_settings.value("file").toString();
    if(!file.isEmpty()) {
        core_.getSettings().setCurrentConfig(file.toStdString());
        eva_->reload();
    }
}
<commit_msg>central, persistent settings<commit_after>/// HEADER
#include "rqt_evaluation.h"

/// PROJECT
#include <csapex/manager/connection_type_manager.h>
#include <csapex/view/designer.h>
#include <csapex/model/graph.h>
#include <csapex/core/graphio.h>
#include <csapex/view/design_board.h>
#include <csapex/view/designer.h>
#include <csapex/core/settings.h>
#include <csapex/manager/box_manager.h>
#include <csapex/view/widget_controller.h>
#include <csapex/view/overlay.h>

/// SYSTEM
#include <pluginlib/class_list_macros.h>
#include <QStringList>
#include <QMenuBar>
#include <QBoxLayout>

PLUGINLIB_DECLARE_CLASS(csapex_rqt, CsApex, csapex_rqt::CsApex, rqt_gui_cpp::Plugin)

using namespace csapex_rqt;
using namespace csapex;

CsApex::CsApex()
    : graph_(new Graph(settings_)),
      widget_controller_(new csapex::WidgetController(graph_)),
      dispatcher_(new CommandDispatcher(graph_, widget_controller_)),
      core_(settings_, graph_, dispatcher_.get()),
      drag_io_(graph_.get(), dispatcher_.get(), widget_controller_),
      overlay_(new Overlay(graph_, dispatcher_.get(), widget_controller_)),
      board_ (new DesignBoard(graph_, dispatcher_.get(), widget_controller_, drag_io_, overlay_)),
      designer_(new Designer(settings_, graph_, dispatcher_.get(), widget_controller_, board_))
{
    BoxManager::instance().settings_ = &settings_;

    widget_controller_->setDesigner(designer_);
}

CsApex::~CsApex()
{
    delete eva_;
}

void CsApex::initPlugin(qt_gui_cpp::PluginContext& context)
{
    context_ = &context;

    eva_ = new CsApexWindow(core_, dispatcher_.get(), widget_controller_, graph_, designer_);
    eva_->showMenu();

    context_->addWidget(eva_);
}

void CsApex::shutdownPlugin()
{
}

void CsApex::saveSettings(qt_gui_cpp::Settings& plugin_settings, qt_gui_cpp::Settings& instance_settings) const
{
    instance_settings.setValue("file", core_.getSettings().getConfig().c_str());
}

void CsApex::restoreSettings(const qt_gui_cpp::Settings& plugin_settings, const qt_gui_cpp::Settings& instance_settings)
{
    QString file = instance_settings.value("file").toString();
    if(!file.isEmpty()) {
        core_.getSettings().setCurrentConfig(file.toStdString());
        eva_->reload();
    }
}
<|endoftext|>


<commit_before>// Copyright 2005 Google Inc. All Rights Reserved.
//
// 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.
//

// Author: ericv@google.com (Eric Veach)

#include "s2/s1angle.h"

#include "s2/base/commandlineflags.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/logging.h"
#include <gtest/gtest.h>
#include "s2/s2latlng.h"
#include "s2/s2testing.h"

DEFINE_int32(iters,
             (google::DEBUG_MODE ? 100 : 1000) * (1000 * 1000),
             "Run timing tests with this many iterations");

TEST(S1Angle, DefaultConstructor) {
  // Check that the default constructor returns an angle of 0.
  S1Angle a;
  EXPECT_EQ(0, a.radians());
}

TEST(S1Angle, Infinity) {
  EXPECT_LT(S1Angle::Radians(1e30), S1Angle::Infinity());
  EXPECT_LT(-S1Angle::Infinity(), S1Angle::Zero());
  EXPECT_EQ(S1Angle::Infinity(), S1Angle::Infinity());
}

TEST(S1Angle, Zero) {
  EXPECT_EQ(S1Angle::Radians(0), S1Angle::Zero());
}

TEST(S1Angle, PiRadiansExactly180Degrees) {
  // Check that the conversion between Pi radians and 180 degrees is exact.
  EXPECT_EQ(M_PI, S1Angle::Radians(M_PI).radians());
  EXPECT_EQ(180.0, S1Angle::Radians(M_PI).degrees());
  EXPECT_EQ(M_PI, S1Angle::Degrees(180).radians());
  EXPECT_EQ(180.0, S1Angle::Degrees(180).degrees());

  EXPECT_EQ(90.0, S1Angle::Radians(M_PI_2).degrees());

  // Check negative angles.
  EXPECT_EQ(-90.0, S1Angle::Radians(-M_PI_2).degrees());
  EXPECT_EQ(-M_PI_4, S1Angle::Degrees(-45).radians());
}

TEST(S1Angle, E5E6E7Representations) {
  // Check that E5/E6/E7 representations work as expected.
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(-45).radians(),
                   S1Angle::E5(-4500000).radians());
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(-60).radians(),
                   S1Angle::E6(-60000000).radians());
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(75).radians(),
                   S1Angle::E7(750000000).radians());
  EXPECT_EQ(-17256123, S1Angle::Degrees(-172.56123).e5());
  EXPECT_EQ(12345678, S1Angle::Degrees(12.345678).e6());
  EXPECT_EQ(-123456789, S1Angle::Degrees(-12.3456789).e7());
}

TEST(S1Angle, E6E7RepresentationsUnsigned) {
  // Check that unsigned E6/E7 representations work as expected.
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(60).radians(),
      S1Angle::UnsignedE6(static_cast<uint32>(60000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(-60).radians(),
      S1Angle::UnsignedE6(static_cast<uint32>(-60000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(75).radians(),
      S1Angle::UnsignedE7(static_cast<uint32>(750000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(-75).radians(),
      S1Angle::UnsignedE7(static_cast<uint32>(-750000000)).radians());
}

TEST(S1Angle, NormalizeCorrectlyCanonicalizesAngles) {
  EXPECT_DOUBLE_EQ(0.0, S1Angle::Degrees(360.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(-180.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(180.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(540.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(90.0, S1Angle::Degrees(-270.0).Normalized().degrees());
}

TEST(S1Angle, ArithmeticOperationsOnAngles) {
  EXPECT_DOUBLE_EQ(0.3, S1Angle::Radians(-0.3).abs().radians());
  EXPECT_DOUBLE_EQ(-0.1, (-S1Angle::Radians(0.1)).radians());
  EXPECT_DOUBLE_EQ(0.4,
                   (S1Angle::Radians(0.1) + S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(-0.2,
                   (S1Angle::Radians(0.1) - S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(0.6, (2 * S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(0.6, (S1Angle::Radians(0.3) * 2).radians());
  EXPECT_DOUBLE_EQ(0.15, (S1Angle::Radians(0.3) / 2).radians());
  EXPECT_DOUBLE_EQ(0.5, (S1Angle::Radians(0.3) / S1Angle::Radians(0.6)));

  S1Angle tmp = S1Angle::Radians(1.0);
  tmp += S1Angle::Radians(0.5);
  EXPECT_DOUBLE_EQ(1.5, tmp.radians());
  tmp -= S1Angle::Radians(1.0);
  EXPECT_DOUBLE_EQ(0.5, tmp.radians());
  tmp *= 5;
  EXPECT_DOUBLE_EQ(2.5, tmp.radians());
  tmp /= 2;
  EXPECT_DOUBLE_EQ(1.25, tmp.radians());
}

TEST(S1Angle, Trigonometry) {
  // Spot check a few angles to ensure that the correct function is called.
  EXPECT_DOUBLE_EQ(1, cos(S1Angle::Degrees(0)));
  EXPECT_DOUBLE_EQ(1, sin(S1Angle::Degrees(90)));
  EXPECT_DOUBLE_EQ(1, tan(S1Angle::Degrees(45)));
}

TEST(S1Angle, ConstructorsThatMeasureAngles) {
  EXPECT_DOUBLE_EQ(M_PI_2,
                   S1Angle(S2Point(1, 0, 0), S2Point(0, 0, 2)).radians());
  EXPECT_DOUBLE_EQ(0.0, S1Angle(S2Point(1, 0, 0), S2Point(1, 0, 0)).radians());
  EXPECT_NEAR(50.0,
              S1Angle(S2LatLng::FromDegrees(20, 20),
                      S2LatLng::FromDegrees(70, 20)).degrees(),
              1e-13);
}

TEST(S1Angle, TestFormatting) {
  std::ostringstream ss;
  ss << S1Angle::Degrees(180.0);
  EXPECT_EQ("180.0000000", ss.str());
}

TEST(S1Angle, TestPerformance) {
  // Verify that the conversion to E5/E6/E7 is not much slower than the
  // conversion from E5/E6/E7.  (Float-to-integer conversions can be quite
  // slow on some platforms.)  We only check the times for E6; the times for
  // E5/E7 should be similar.

  // To reduce the impact of loop overhead, we do kOpsPerLoop ops per loop.
  static const int kOpsPerLoop = 8;

  // Time conversion from E6 to radians.
  double rad_sum = 0;
  const double from_e6_start = S2Testing::GetCpuTime();
  for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
    // We structure both loops so that all the conversions can be done in
    // parallel.  Otherwise on some platforms the optimizer happens to do a
    // much better job of parallelizing one loop than the other.
    double r0 = S1Angle::E6(i-0).radians();
    double r1 = S1Angle::E6(i-1).radians();
    double r2 = S1Angle::E6(i-2).radians();
    double r3 = S1Angle::E6(i-3).radians();
    double r4 = S1Angle::E6(i-4).radians();
    double r5 = S1Angle::E6(i-5).radians();
    double r6 = S1Angle::E6(i-6).radians();
    double r7 = S1Angle::E6(i-7).radians();
    rad_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
  }
  const double from_e6_time = S2Testing::GetCpuTime() - from_e6_start;
  EXPECT_NE(rad_sum, 0);  // Don't let the sum get optimized away.
  S2_LOG(INFO) << "From E6: "
            << (FLAGS_iters / from_e6_time)
            << " values per second";

  // Time conversion from radians to E6.
  const double delta = (2 * M_PI) / (FLAGS_iters - 1);
  double angle = -M_PI;
  int64 e6_sum = 0;
  const double to_e6_start = S2Testing::GetCpuTime();
  for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
    int64 r0 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r1 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r2 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r3 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r4 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r5 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r6 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r7 = S1Angle::Radians(angle).e6(); angle += delta;
    e6_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
  }
  const double to_e6_time = S2Testing::GetCpuTime() - to_e6_start;
  EXPECT_NE(e6_sum + angle, 0);  // Don't let them get optimized away.
  S2_LOG(INFO) << "  To E6: "
            << (FLAGS_iters / to_e6_time)
            << " values per second";

  // Make sure that the To/From E6 times are not much different.
  // The difference factor slightly less than 2 on an x86_64.
  EXPECT_LE(from_e6_time / to_e6_time, 3);
  EXPECT_LE(to_e6_time / from_e6_time, 3);
}

// The current implementation guarantees exact conversions between
// Degrees() and E6() when the Degrees() argument is an integer.
TEST(S1Angle, DegreesVsE6) {
  for (int i = 0; i <= 180; ++i) {
    EXPECT_EQ(S1Angle::Degrees(i), S1Angle::E6(1000000 * i));
  }
}

// The current implementation guarantees exact conversions between
// Degrees() and E7() when the Degrees() argument is an integer.
TEST(S1Angle, DegreesVsE7) {
  for (int i = 0; i <= 180; ++i) {
    EXPECT_EQ(S1Angle::Degrees(i), S1Angle::E7(10000000 * i));
  }
}

// The current implementation guarantees exact conversions between
// E6() and E7() when the E6() argument is an integer.
TEST(S1Angle, E6VsE7) {
  S2Testing::rnd.Reset(FLAGS_s2_random_seed);
  for (int iter = 0; iter < 1000; ++iter) {
    int i = S2Testing::rnd.Uniform(180000000);
    EXPECT_EQ(S1Angle::E6(i), S1Angle::E7(10 * i));
  }
}

// The current implementation guarantees certain exact conversions between
// degrees and radians (see the header file for details).
TEST(S1Angle, DegreesVsRadians) {
  for (int k = -8; k <= 8; ++k) {
    EXPECT_EQ(S1Angle::Degrees(45 * k), S1Angle::Radians(k * M_PI / 4));
    EXPECT_EQ(45 * k, S1Angle::Degrees(45 * k).degrees());
  }
  for (int k = 0; k <= 30; ++k) {
    int n = 1 << k;
    EXPECT_EQ(S1Angle::Degrees(180. / n), S1Angle::Radians(M_PI / n));
    EXPECT_EQ(S1Angle::Degrees(60. / n), S1Angle::Radians(M_PI / (3. * n)));
    EXPECT_EQ(S1Angle::Degrees(36. / n), S1Angle::Radians(M_PI / (5. * n)));
    EXPECT_EQ(S1Angle::Degrees(20. / n), S1Angle::Radians(M_PI / (9. * n)));
    EXPECT_EQ(S1Angle::Degrees(4. / n), S1Angle::Radians(M_PI / (45. * n)));
  }
  // We also spot check a couple of non-identities.
  EXPECT_NE(S1Angle::Degrees(3), S1Angle::Radians(M_PI / 60));
  EXPECT_NE(60, S1Angle::Degrees(60).degrees());
}
<commit_msg>S1Angle: Add test that Normalized(-90) => -90.<commit_after>// Copyright 2005 Google Inc. All Rights Reserved.
//
// 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.
//

// Author: ericv@google.com (Eric Veach)

#include "s2/s1angle.h"

#include "s2/base/commandlineflags.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/logging.h"
#include <gtest/gtest.h>
#include "s2/s2latlng.h"
#include "s2/s2testing.h"

DEFINE_int32(iters,
             (google::DEBUG_MODE ? 100 : 1000) * (1000 * 1000),
             "Run timing tests with this many iterations");

TEST(S1Angle, DefaultConstructor) {
  // Check that the default constructor returns an angle of 0.
  S1Angle a;
  EXPECT_EQ(0, a.radians());
}

TEST(S1Angle, Infinity) {
  EXPECT_LT(S1Angle::Radians(1e30), S1Angle::Infinity());
  EXPECT_LT(-S1Angle::Infinity(), S1Angle::Zero());
  EXPECT_EQ(S1Angle::Infinity(), S1Angle::Infinity());
}

TEST(S1Angle, Zero) {
  EXPECT_EQ(S1Angle::Radians(0), S1Angle::Zero());
}

TEST(S1Angle, PiRadiansExactly180Degrees) {
  // Check that the conversion between Pi radians and 180 degrees is exact.
  EXPECT_EQ(M_PI, S1Angle::Radians(M_PI).radians());
  EXPECT_EQ(180.0, S1Angle::Radians(M_PI).degrees());
  EXPECT_EQ(M_PI, S1Angle::Degrees(180).radians());
  EXPECT_EQ(180.0, S1Angle::Degrees(180).degrees());

  EXPECT_EQ(90.0, S1Angle::Radians(M_PI_2).degrees());

  // Check negative angles.
  EXPECT_EQ(-90.0, S1Angle::Radians(-M_PI_2).degrees());
  EXPECT_EQ(-M_PI_4, S1Angle::Degrees(-45).radians());
}

TEST(S1Angle, E5E6E7Representations) {
  // Check that E5/E6/E7 representations work as expected.
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(-45).radians(),
                   S1Angle::E5(-4500000).radians());
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(-60).radians(),
                   S1Angle::E6(-60000000).radians());
  EXPECT_DOUBLE_EQ(S1Angle::Degrees(75).radians(),
                   S1Angle::E7(750000000).radians());
  EXPECT_EQ(-17256123, S1Angle::Degrees(-172.56123).e5());
  EXPECT_EQ(12345678, S1Angle::Degrees(12.345678).e6());
  EXPECT_EQ(-123456789, S1Angle::Degrees(-12.3456789).e7());
}

TEST(S1Angle, E6E7RepresentationsUnsigned) {
  // Check that unsigned E6/E7 representations work as expected.
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(60).radians(),
      S1Angle::UnsignedE6(static_cast<uint32>(60000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(-60).radians(),
      S1Angle::UnsignedE6(static_cast<uint32>(-60000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(75).radians(),
      S1Angle::UnsignedE7(static_cast<uint32>(750000000)).radians());
  EXPECT_DOUBLE_EQ(
      S1Angle::Degrees(-75).radians(),
      S1Angle::UnsignedE7(static_cast<uint32>(-750000000)).radians());
}

TEST(S1Angle, NormalizeCorrectlyCanonicalizesAngles) {
  EXPECT_DOUBLE_EQ(0.0, S1Angle::Degrees(360.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(-90.0, S1Angle::Degrees(-90.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(-180.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(180.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(540.0).Normalized().degrees());
  EXPECT_DOUBLE_EQ(90.0, S1Angle::Degrees(-270.0).Normalized().degrees());
}

TEST(S1Angle, ArithmeticOperationsOnAngles) {
  EXPECT_DOUBLE_EQ(0.3, S1Angle::Radians(-0.3).abs().radians());
  EXPECT_DOUBLE_EQ(-0.1, (-S1Angle::Radians(0.1)).radians());
  EXPECT_DOUBLE_EQ(0.4,
                   (S1Angle::Radians(0.1) + S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(-0.2,
                   (S1Angle::Radians(0.1) - S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(0.6, (2 * S1Angle::Radians(0.3)).radians());
  EXPECT_DOUBLE_EQ(0.6, (S1Angle::Radians(0.3) * 2).radians());
  EXPECT_DOUBLE_EQ(0.15, (S1Angle::Radians(0.3) / 2).radians());
  EXPECT_DOUBLE_EQ(0.5, (S1Angle::Radians(0.3) / S1Angle::Radians(0.6)));

  S1Angle tmp = S1Angle::Radians(1.0);
  tmp += S1Angle::Radians(0.5);
  EXPECT_DOUBLE_EQ(1.5, tmp.radians());
  tmp -= S1Angle::Radians(1.0);
  EXPECT_DOUBLE_EQ(0.5, tmp.radians());
  tmp *= 5;
  EXPECT_DOUBLE_EQ(2.5, tmp.radians());
  tmp /= 2;
  EXPECT_DOUBLE_EQ(1.25, tmp.radians());
}

TEST(S1Angle, Trigonometry) {
  // Spot check a few angles to ensure that the correct function is called.
  EXPECT_DOUBLE_EQ(1, cos(S1Angle::Degrees(0)));
  EXPECT_DOUBLE_EQ(1, sin(S1Angle::Degrees(90)));
  EXPECT_DOUBLE_EQ(1, tan(S1Angle::Degrees(45)));
}

TEST(S1Angle, ConstructorsThatMeasureAngles) {
  EXPECT_DOUBLE_EQ(M_PI_2,
                   S1Angle(S2Point(1, 0, 0), S2Point(0, 0, 2)).radians());
  EXPECT_DOUBLE_EQ(0.0, S1Angle(S2Point(1, 0, 0), S2Point(1, 0, 0)).radians());
  EXPECT_NEAR(50.0,
              S1Angle(S2LatLng::FromDegrees(20, 20),
                      S2LatLng::FromDegrees(70, 20)).degrees(),
              1e-13);
}

TEST(S1Angle, TestFormatting) {
  std::ostringstream ss;
  ss << S1Angle::Degrees(180.0);
  EXPECT_EQ("180.0000000", ss.str());
}

TEST(S1Angle, TestPerformance) {
  // Verify that the conversion to E5/E6/E7 is not much slower than the
  // conversion from E5/E6/E7.  (Float-to-integer conversions can be quite
  // slow on some platforms.)  We only check the times for E6; the times for
  // E5/E7 should be similar.

  // To reduce the impact of loop overhead, we do kOpsPerLoop ops per loop.
  static const int kOpsPerLoop = 8;

  // Time conversion from E6 to radians.
  double rad_sum = 0;
  const double from_e6_start = S2Testing::GetCpuTime();
  for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
    // We structure both loops so that all the conversions can be done in
    // parallel.  Otherwise on some platforms the optimizer happens to do a
    // much better job of parallelizing one loop than the other.
    double r0 = S1Angle::E6(i-0).radians();
    double r1 = S1Angle::E6(i-1).radians();
    double r2 = S1Angle::E6(i-2).radians();
    double r3 = S1Angle::E6(i-3).radians();
    double r4 = S1Angle::E6(i-4).radians();
    double r5 = S1Angle::E6(i-5).radians();
    double r6 = S1Angle::E6(i-6).radians();
    double r7 = S1Angle::E6(i-7).radians();
    rad_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
  }
  const double from_e6_time = S2Testing::GetCpuTime() - from_e6_start;
  EXPECT_NE(rad_sum, 0);  // Don't let the sum get optimized away.
  S2_LOG(INFO) << "From E6: "
            << (FLAGS_iters / from_e6_time)
            << " values per second";

  // Time conversion from radians to E6.
  const double delta = (2 * M_PI) / (FLAGS_iters - 1);
  double angle = -M_PI;
  int64 e6_sum = 0;
  const double to_e6_start = S2Testing::GetCpuTime();
  for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
    int64 r0 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r1 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r2 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r3 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r4 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r5 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r6 = S1Angle::Radians(angle).e6(); angle += delta;
    int64 r7 = S1Angle::Radians(angle).e6(); angle += delta;
    e6_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
  }
  const double to_e6_time = S2Testing::GetCpuTime() - to_e6_start;
  EXPECT_NE(e6_sum + angle, 0);  // Don't let them get optimized away.
  S2_LOG(INFO) << "  To E6: "
            << (FLAGS_iters / to_e6_time)
            << " values per second";

  // Make sure that the To/From E6 times are not much different.
  // The difference factor slightly less than 2 on an x86_64.
  EXPECT_LE(from_e6_time / to_e6_time, 3);
  EXPECT_LE(to_e6_time / from_e6_time, 3);
}

// The current implementation guarantees exact conversions between
// Degrees() and E6() when the Degrees() argument is an integer.
TEST(S1Angle, DegreesVsE6) {
  for (int i = 0; i <= 180; ++i) {
    EXPECT_EQ(S1Angle::Degrees(i), S1Angle::E6(1000000 * i));
  }
}

// The current implementation guarantees exact conversions between
// Degrees() and E7() when the Degrees() argument is an integer.
TEST(S1Angle, DegreesVsE7) {
  for (int i = 0; i <= 180; ++i) {
    EXPECT_EQ(S1Angle::Degrees(i), S1Angle::E7(10000000 * i));
  }
}

// The current implementation guarantees exact conversions between
// E6() and E7() when the E6() argument is an integer.
TEST(S1Angle, E6VsE7) {
  S2Testing::rnd.Reset(FLAGS_s2_random_seed);
  for (int iter = 0; iter < 1000; ++iter) {
    int i = S2Testing::rnd.Uniform(180000000);
    EXPECT_EQ(S1Angle::E6(i), S1Angle::E7(10 * i));
  }
}

// The current implementation guarantees certain exact conversions between
// degrees and radians (see the header file for details).
TEST(S1Angle, DegreesVsRadians) {
  for (int k = -8; k <= 8; ++k) {
    EXPECT_EQ(S1Angle::Degrees(45 * k), S1Angle::Radians(k * M_PI / 4));
    EXPECT_EQ(45 * k, S1Angle::Degrees(45 * k).degrees());
  }
  for (int k = 0; k <= 30; ++k) {
    int n = 1 << k;
    EXPECT_EQ(S1Angle::Degrees(180. / n), S1Angle::Radians(M_PI / n));
    EXPECT_EQ(S1Angle::Degrees(60. / n), S1Angle::Radians(M_PI / (3. * n)));
    EXPECT_EQ(S1Angle::Degrees(36. / n), S1Angle::Radians(M_PI / (5. * n)));
    EXPECT_EQ(S1Angle::Degrees(20. / n), S1Angle::Radians(M_PI / (9. * n)));
    EXPECT_EQ(S1Angle::Degrees(4. / n), S1Angle::Radians(M_PI / (45. * n)));
  }
  // We also spot check a couple of non-identities.
  EXPECT_NE(S1Angle::Degrees(3), S1Angle::Radians(M_PI / 60));
  EXPECT_NE(60, S1Angle::Degrees(60).degrees());
}
<|endoftext|>


<commit_before>//
//
// MIT License
//
// Copyright (c) 2020 Stellacore Corporation.
//
// 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.
//
//

/*! \file
\brief Definitions for cam::fit
*/

#include "libcam/fit.h"

#include "libdat/ops.h"
#include "libmath/math.h"
#include "libmath/QuadEq.h"

#include "libio/stream.h"
#include "libdat/info.h"

namespace cam
{

namespace fit
{

namespace normalized
{
	//! Perform detailed computations (assuming data are reasonably normalized)
	std::vector<double>
	principalDistanceFor
		( std::pair<dat::Spot, dat::Spot> const & meaPair
		, double const & angleMag
		, dat::Spot const & detCenter
		)
	{
		std::vector<double> fitPDs;
		if ( dat::isValid(detCenter)
		  && dat::isValid(angleMag)
		  && dat::isValid(meaPair.first)
		  && dat::isValid(meaPair.second)
		   )
		{
			// compute various intermediate quantities
			double const gam{ 2. * std::cos(angleMag) };
			double const gamSq{ math::sq(gam) };

			dat::Spot const & m1 = meaPair.first;
			dat::Spot const & m2 = meaPair.second;
			double const m1Sq{ dat::magSq(m1) };
			double const m2Sq{ dat::magSq(m2) };

			using dat::operator-;
			double const rSq{ dat::magSq(m2 - m1) };
			double const r4{ math::sq(rSq) };

			double const & etaRow = detCenter[0];
			double const & etaCol = detCenter[1];
			double const rho1Sq{ m1[0]*etaRow + m1[1]*etaCol };
			double const rho2Sq{ m2[0]*etaRow + m2[1]*etaCol };

			double const nuSq{ .5 * (m1Sq + m2Sq) - (rho1Sq + rho2Sq) };
			double const nu4{ math::sq(nuSq) };

			double const om4{ (m1Sq - 2.*rho1Sq) * (m2Sq - 2.*rho2Sq) };

			// form quadratic equation in h^2
			double const ka{ 4. - gamSq };
			double const kb{ 4.*nuSq - nuSq*gamSq - 2.*rSq };
			double const kc{ 4.*nu4 - 4.*nuSq*rSq + r4 - om4*gamSq };
			math::QuadEq const quad(ka, 2.*kb, kc);

			// get unique roots
			// NOTE: this algorithms is not necessarily numerically stable
			// (no attention given to order of operations)
			static double const tol{ std::sqrt(math::eps) };
			std::vector<double> const hSqs{ quad.realUniqueRoots(tol) };
			if (! hSqs.empty())
			{
				fitPDs.reserve(hSqs.size());
				for (double const & hSq : hSqs)
				{
					// extract component perpendicular to detector
					double const detRadSq
						{ math::sq(etaRow) + math::sq(etaCol) };
					double const etaSq{ hSq - detRadSq };
					if (0. < etaSq)
					{
						fitPDs.emplace_back(std::sqrt(etaSq));
					}
				}
			}
		}

		return fitPDs;
	}
}

std::vector<double>
principalDistanceFor
	( std::pair<dat::Spot, dat::Spot> const & meaPair
	, double const & angleMag
	, dat::Extents const & detSize
	)
{
	std::vector<double> fitPDs;
	if ( dat::isValid(detSize)
	  && dat::isValid(angleMag)
	  && dat::isValid(meaPair.first)
	  && dat::isValid(meaPair.second)
	   )
	{
		// compute normalization factor
		double const restPerNorm{ dat::diagonalMag(detSize) };
		double const normPerRest{ 1. / restPerNorm };

		// normalize input data
		using dat::operator*;
		dat::Spot const normCenter{ normPerRest * dat::centerOf(detSize) };
		std::pair<dat::Spot, dat::Spot> const normMeaPair
			{ normPerRest * meaPair.first
			, normPerRest * meaPair.second
			};

		// fit principal distance to normalized data
		std::vector<double> const normPDs
			{ normalized::principalDistanceFor
				(normMeaPair, angleMag, normCenter)
			};

		// restore principal distance magnitudes
		if (! normPDs.empty())
		{
			fitPDs.reserve(normPDs.size());
			for (double const & normPD : normPDs)
			{
				fitPDs.emplace_back(restPerNorm * normPD);
			}
		}
	}
	return fitPDs;
}

} // fit

} // cam

<commit_msg>libcam/fit principalDistanceFor() - added check to eliminate spurious root<commit_after>//
//
// MIT License
//
// Copyright (c) 2020 Stellacore Corporation.
//
// 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.
//
//

/*! \file
\brief Definitions for cam::fit
*/

#include "libcam/fit.h"

#include "libdat/ops.h"
#include "libmath/math.h"
#include "libmath/QuadEq.h"

#include "libio/stream.h"
#include "libdat/info.h"

namespace cam
{

namespace fit
{

namespace normalized
{
	//! Compare solution values for consistency with input data
	struct Checker
	{
		std::pair<dat::Spot, dat::Spot> const & theMeaPair;
		double const & theExpGam;
		dat::Spot const & theDetCenter;

		//! Angle indicator: gamma = 2*cos(beta)
		double
		gammaFor
			( double const & gotPD
			) const
		{
			double gam{ dat::nullValue<double>() };
			if (math::eps < gotPD) // consider only positive values
			{
				using dat::operator-;
				double const radSq1
					{ dat::magSq(theMeaPair.first - theDetCenter) };
				double const radSq2
					{ dat::magSq(theMeaPair.second - theDetCenter) };
				double const pdSq{ math::sq(gotPD) };
				double const muSq1{ pdSq + radSq1 };
				double const muSq2{ pdSq + radSq2 };
				double const delSq
					{ dat::magSq(theMeaPair.second - theMeaPair.first) };
				// delSq = muSq1 + muSq2 - mu1*mu2*(2.*cosBeta)
				// gamma = 2*cosBeta
				// delSq - muSq1 - muSq2 = - mu1*mu2*gamma
				// (-delSq + muSq1 + muSq2)/(mu1*mu2) = gamma
				gam = (muSq1 + muSq2 - delSq) / std::sqrt(muSq1 * muSq2);
			}
			return gam;
		}

		//! True if gotPD produces vertex angle of theExpGam within tol
		bool
		isConsistent
			( double const & gotPD
			, double const & tol = { math::eps }
			) const
		{
			double const & expGam = theExpGam;
			double const gotGam{ gammaFor(gotPD) };
			return dat::nearlyEquals(gotGam, expGam, tol);
		}

	}; // Checker

	//! Perform detailed computations (assuming data are reasonably normalized)
	std::vector<double>
	principalDistanceFor
		( std::pair<dat::Spot, dat::Spot> const & meaPair
		, double const & angleMag
		, dat::Spot const & detCenter
		)
	{
		std::vector<double> fitPDs;
		if ( dat::isValid(detCenter)
		  && dat::isValid(angleMag)
		  && dat::isValid(meaPair.first)
		  && dat::isValid(meaPair.second)
		   )
		{
			// compute various intermediate quantities
			double const gam{ 2. * std::cos(angleMag) };
			double const gamSq{ math::sq(gam) };

			dat::Spot const & m1 = meaPair.first;
			dat::Spot const & m2 = meaPair.second;
			double const m1Sq{ dat::magSq(m1) };
			double const m2Sq{ dat::magSq(m2) };

			using dat::operator-;
			double const rSq{ dat::magSq(m2 - m1) };
			double const r4{ math::sq(rSq) };

			double const & etaRow = detCenter[0];
			double const & etaCol = detCenter[1];
			double const rho1Sq{ m1[0]*etaRow + m1[1]*etaCol };
			double const rho2Sq{ m2[0]*etaRow + m2[1]*etaCol };

			double const nuSq{ .5 * (m1Sq + m2Sq) - (rho1Sq + rho2Sq) };
			double const nu4{ math::sq(nuSq) };

			double const om4{ (m1Sq - 2.*rho1Sq) * (m2Sq - 2.*rho2Sq) };

			// form quadratic equation in h^2
			double const ka{ 4. - gamSq };
			double const kb{ 4.*nuSq - nuSq*gamSq - 2.*rSq };
			double const kc{ 4.*nu4 - 4.*nuSq*rSq + r4 - om4*gamSq };
			math::QuadEq const quad(ka, 2.*kb, kc);

			// get unique roots
			// NOTE: this algorithms is not necessarily numerically stable
			// (no attention given to order of operations)
			static double const tol{ std::sqrt(math::eps) };
			std::vector<double> const hSqs{ quad.realUniqueRoots(tol) };
			if (! hSqs.empty())
			{
				fitPDs.reserve(hSqs.size());
				Checker const config{ meaPair, gam, detCenter };
				for (double const & hSq : hSqs)
				{
					// extract component perpendicular to detector
					double const detRadSq
						{ math::sq(etaRow) + math::sq(etaCol) };
					double const etaSq{ hSq - detRadSq };
					if (0. < etaSq)
					{
						double const gotPD{ std::sqrt(etaSq) };
						if (config.isConsistent(gotPD, tol))
						{
							fitPDs.emplace_back(gotPD);
						}
					}
				}
			}
		}

		return fitPDs;
	}
}

std::vector<double>
principalDistanceFor
	( std::pair<dat::Spot, dat::Spot> const & meaPair
	, double const & angleMag
	, dat::Extents const & detSize
	)
{
	std::vector<double> fitPDs;
	if ( dat::isValid(detSize)
	  && dat::isValid(angleMag)
	  && dat::isValid(meaPair.first)
	  && dat::isValid(meaPair.second)
	   )
	{
		// compute normalization factor
		double const restPerNorm{ dat::diagonalMag(detSize) };
		double const normPerRest{ 1. / restPerNorm };

		// normalize input data
		using dat::operator*;
		dat::Spot const normCenter{ normPerRest * dat::centerOf(detSize) };
		std::pair<dat::Spot, dat::Spot> const normMeaPair
			{ normPerRest * meaPair.first
			, normPerRest * meaPair.second
			};

		// fit principal distance to normalized data
		std::vector<double> const normPDs
			{ normalized::principalDistanceFor
				(normMeaPair, angleMag, normCenter)
			};

		// restore principal distance magnitudes
		if (! normPDs.empty())
		{
			fitPDs.reserve(normPDs.size());
			for (double const & normPD : normPDs)
			{
				fitPDs.emplace_back(restPerNorm * normPD);
			}
		}
	}
	return fitPDs;
}

} // fit

} // cam

<|endoftext|>