diff --git a/Service/EventSeeder/CMakeLists.txt b/Service/EventSeeder/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..8bf504d85fbab7043e0fcd82e7b45043fc9b7347 --- /dev/null +++ b/Service/EventSeeder/CMakeLists.txt @@ -0,0 +1,14 @@ +gaudi_subdir(EventSeeder v0r0) + +#find_package(GEAR REQUIRED) + +set(EventSeeder_srcs + src/*.cpp +) + +gaudi_install_headers(EventSeeder) + +gaudi_add_module(EventSeeder ${EventSeeder_srcs} + INCLUDE_DIRS GaudiKernel + LINK_LIBRARIES GaudiKernel +) diff --git a/Service/EventSeeder/EventSeeder/IEventSeeder.h b/Service/EventSeeder/EventSeeder/IEventSeeder.h new file mode 100644 index 0000000000000000000000000000000000000000..20365853bbddea35304d7b8937d3a9c9c04847e9 --- /dev/null +++ b/Service/EventSeeder/EventSeeder/IEventSeeder.h @@ -0,0 +1,21 @@ +#ifndef I_EVENT_SEEDER_H +#define I_EVENT_SEEDER_H + +#include "GaudiKernel/IService.h" + +class Algorithm; + +class IEventSeeder: virtual public IService { +public: + DeclareInterfaceID(IEventSeeder, 0, 1); // major/minor version + + virtual ~IEventSeeder() = default; + + // Register an algorithm for the seeding service + virtual void registerAlg( Algorithm* alg ) = 0; + + // Get the seed corelated to current event and algorithm + virtual unsigned int getSeed(Algorithm* alg, int eventNumber, int runNumber) = 0; +}; + +#endif diff --git a/Service/EventSeeder/README.md b/Service/EventSeeder/README.md new file mode 100644 index 0000000000000000000000000000000000000000..79d3d8e72ee35defe3dcfd8e83a79770183ca3ed --- /dev/null +++ b/Service/EventSeeder/README.md @@ -0,0 +1,5 @@ +# EventSeeder + +This package is used to provide independent pseudo-randomly generated seeds for registered algorithms on an event by event basis. + +Currently the only implementation, MarlinEvtSeeder, is migrated from marlin::ProcessorEventSeeder. Please refer to https://github.com/iLCSoft/Marlin diff --git a/Service/EventSeeder/src/MarlinEvtSeeder.cpp b/Service/EventSeeder/src/MarlinEvtSeeder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7acfdadf00ea47e20a83a3a4d21422d0d8741f0 --- /dev/null +++ b/Service/EventSeeder/src/MarlinEvtSeeder.cpp @@ -0,0 +1,82 @@ +#include "MarlinEvtSeeder.h" +#include "jenkinsHash.h" +#include "GaudiKernel/Algorithm.h" + +#include <stdlib.h> +#include <limits> +#include <algorithm> + +DECLARE_COMPONENT(MarlinEvtSeeder) + +MarlinEvtSeeder::MarlinEvtSeeder(const std::string& name, ISvcLocator* svc) + : base_class(name, svc), + _evtNo(-1), + _runNo(-1) +{ +} + +void MarlinEvtSeeder::registerAlg ( Algorithm* alg ) +{ + srand( _globalSeed ); + debug() << "initialised with global seed " << _globalSeed << endmsg; + + _vAlgSeed.push_back( std::make_pair( alg, rand() ) ); + debug() << alg->name() << "registered for random seed service. Allocated " + << _vAlgSeed.back().second << " as initial seed." + << endmsg; +} + +void MarlinEvtSeeder::refreshSeeds() +{ + // get hashed seed using jenkins_hash + unsigned int seed = 0 ; // initial state + unsigned int eventNumber = _evtNo; //evt->getEventNumber() ; + unsigned int runNumber = _runNo; //evt->getRunNumber() ; + + unsigned char * c = (unsigned char *) &eventNumber ; + seed = jenkins_hash( c, sizeof eventNumber, seed) ; + + c = (unsigned char *) &runNumber ; + seed = jenkins_hash( c, sizeof runNumber, seed) ; + + int _global_seed = _globalSeed; + c = (unsigned char *) &_global_seed ; + seed = jenkins_hash( c, sizeof _global_seed, seed) ; + + // set the seed for rand() for this event + if ( seed == 1 ) seed = 123456789 ; // can't used a seed value of 1 as srand(1) sets rand() back the state of the last call to srand( seed ). + + debug() << "MarlinEvtSeeder: Refresh Seeds using " << seed << " as seed for srand( seed )" << endmsg; + srand( seed ); + + // fill vector with seeds for each registered processor using rand() + for( auto& iAlgSeed : _vAlgSeed ) + { + iAlgSeed.second = rand(); + } +} + +unsigned int MarlinEvtSeeder::getSeed(Algorithm* alg, int eventNumber, int runNumber) +{ + if ( eventNumber != _evtNo || runNumber != _runNo ) { + _evtNo = eventNumber; + _runNo = runNumber; + refreshSeeds(); + } + + typedef std::pair<Algorithm*, unsigned int> Pair; + + auto it = find_if( _vAlgSeed.begin(), _vAlgSeed.end(), [&](Pair const& pair){ return pair.first == alg; } ); + + return ( it != _vAlgSeed.end() ? it->second : throw ) ; +} + +StatusCode MarlinEvtSeeder::initialize() +{ + return StatusCode::SUCCESS; +} + +StatusCode MarlinEvtSeeder::finalize() +{ + return StatusCode::SUCCESS; +} diff --git a/Service/EventSeeder/src/MarlinEvtSeeder.h b/Service/EventSeeder/src/MarlinEvtSeeder.h new file mode 100644 index 0000000000000000000000000000000000000000..ed4a5d6ed2e10b9d8b25df1788654e742ed2e16b --- /dev/null +++ b/Service/EventSeeder/src/MarlinEvtSeeder.h @@ -0,0 +1,86 @@ +#ifndef MARLIN_EVT_SEEDER_H +#define MARLIN_EVT_SEEDER_H 1 + +#include "EventSeeder/IEventSeeder.h" +#include <GaudiKernel/Service.h> +#include <vector> +#include <map> + +/** Processor event seeder - provides independent pseudo-randomly generated seeds + * for registered processors on an event by event basis. + * + * This Class is used to provide individual pseudo-random numbers to Processors on + * an event-by-event and run-by-run basis. These may be used by Processors to + * seed their random number generator of choice. In order to use this service + * a Processor must register itself in the init method using: + * + * Global::EVENTSEEDER->registerProcessor(this); + * + * and should retrieve its allocated event seed during processEvent using: + * + * int eventSeed = Global::EVENTSEEDER->getSeed(this); + * + * and include the header file: + * + * #include "marlin/ProcessorEventSeeder.h" + * + * The global seed is used for a complete job and is set in the Global steering parameters thus: + * + * <parameter name="RandomSeed" value="1234567890"/> + * + * Note that the value must be a positive integer, with max value 2,147,483,647 + * A pseudo-random event seed is generated using a three step hashing function of unsigned ints, + * in the following order: event_number, run_number, RandomSeed. The hashed int from each step + * in the above order is used as input to the next hash step. This is used to ensure that in + * the likely event of similar values of event_number, run_number and RandomSeed, different + * event seeds will be generated. + * + * The event seed is then used to seed rand via srand(seed) and then rand is used to + * generate one seed per registered processor. + * + * This mechanism ensures reproducible results for every event, regardless of the sequence + * in which the event is processed in a Marlin job, whilst maintaining the full 32bit range + * for event and run numbers. + * + * If a call is made to getSeed( Processor* ) preceededing a call to registerProcessor( Processor* ) + * an exception will be thrown. + * + * @author S.J. Aplin, DESY + */ + +class MarlinEvtSeeder : public extends<Service, IEventSeeder> +{ + public: + + /** Constructor and Destructor */ + MarlinEvtSeeder(const std::string& name, ISvcLocator* svc); + ~MarlinEvtSeeder() { } ; + + /** Called by Algorithms to register themselves for the seeding service. + */ + void registerAlg( Algorithm* alg ) override; + + /** Called by Algorithms to obtain seed assigned to it for the current event. + */ + unsigned int getSeed(Algorithm* alg, int eventNumber, int runNumber) override; + + StatusCode initialize() override; + StatusCode finalize() override; + + private: + + void refreshSeeds(); + + int _evtNo; + int _runNo; + + /** Global seed for current Job. Set in steering file. + */ + Gaudi::Property<int> _globalSeed{this, "RandomSeed", 0}; + + /** Vector to hold pair of pointers to the registered processors and their assigned seeds + */ + std::vector< std::pair<Algorithm*, unsigned int> > _vAlgSeed; +} ; + +#endif diff --git a/Service/EventSeeder/src/jenkinsHash.h b/Service/EventSeeder/src/jenkinsHash.h new file mode 100644 index 0000000000000000000000000000000000000000..19b5722044a4af33f82d11fca28f785c1ff289e7 --- /dev/null +++ b/Service/EventSeeder/src/jenkinsHash.h @@ -0,0 +1,131 @@ +#ifndef JENKINSHASH_INCLUDED +#define JENKINSHASH_INCLUDED + +/* + +Original source by Bob Jenkins + +http://www.burtleburtle.net/bob/hash/doobs.html + +Hash a variable-length key into a 32-bit value + +*/ + +#define hashsize(n) ( 1U << (n) ) +#define hashmask(n) ( hashsize ( n ) - 1 ) + + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bits set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* +-------------------------------------------------------------------- +jenkins_hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 6*len+35 instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h); + +By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +code any way you wish, private, educational, or commercial. It's free. + +See http://burtleburtle.net/bob/hash/evahash.html +Use for hash table lookup, or anything where one collision in 2^^32 is +acceptable. Do NOT use for cryptographic purposes. +-------------------------------------------------------------------- +*/ +unsigned jenkins_hash ( unsigned char *k, unsigned length, unsigned initval ) +{ + unsigned a, b; + unsigned c = initval; + unsigned len = length; + + a = b = 0x9e3779b9; + + while ( len >= 12 ) { + a += ( k[0] + ( (unsigned)k[1] << 8 ) + + ( (unsigned)k[2] << 16 ) + + ( (unsigned)k[3] << 24 ) ); + b += ( k[4] + ( (unsigned)k[5] << 8 ) + + ( (unsigned)k[6] << 16 ) + + ( (unsigned)k[7] << 24 ) ); + c += ( k[8] + ( (unsigned)k[9] << 8 ) + + ( (unsigned)k[10] << 16 ) + + ( (unsigned)k[11] << 24 ) ); + + mix ( a, b, c ); + + k += 12; + len -= 12; + } + + c += length; + + switch ( len ) { + case 11: c += ( (unsigned)k[10] << 24 ); + case 10: c += ( (unsigned)k[9] << 16 ); + case 9 : c += ( (unsigned)k[8] << 8 ); + /* First byte of c reserved for length */ + case 8 : b += ( (unsigned)k[7] << 24 ); + case 7 : b += ( (unsigned)k[6] << 16 ); + case 6 : b += ( (unsigned)k[5] << 8 ); + case 5 : b += k[4]; + case 4 : a += ( (unsigned)k[3] << 24 ); + case 3 : a += ( (unsigned)k[2] << 16 ); + case 2 : a += ( (unsigned)k[1] << 8 ); + case 1 : a += k[0]; + } + + mix ( a, b, c ); + + return c; +} + +#endif diff --git a/Service/GearSvc/CMakeLists.txt b/Service/GearSvc/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..f78d581ad47f95e7a74ffb61883c152cceffca7e --- /dev/null +++ b/Service/GearSvc/CMakeLists.txt @@ -0,0 +1,14 @@ +gaudi_subdir(GearSvc v0r0) + +find_package(GEAR REQUIRED) + +set(GearSvc_srcs + src/*.cpp +) + +gaudi_install_headers(GearSvc) + +gaudi_add_module(GearSvc ${GearSvc_srcs} + INCLUDE_DIRS GaudiKernel gear + LINK_LIBRARIES GaudiKernel $ENV{GEAR}/lib/libgear.so $ENV{GEAR}/lib/libgearxml.so +) diff --git a/Service/GearSvc/GearSvc/IGearSvc.h b/Service/GearSvc/GearSvc/IGearSvc.h new file mode 100644 index 0000000000000000000000000000000000000000..a6f4dff77430c960237b7cfdd62a4e16430d2029 --- /dev/null +++ b/Service/GearSvc/GearSvc/IGearSvc.h @@ -0,0 +1,21 @@ +#ifndef I_GEAR_SVC_H +#define I_GEAR_SVC_H + +#include "GaudiKernel/IService.h" +#include "gear/GearMgr.h" + +// IGearSvc is the interface between Gaudi and GEAR. + +class IGearSvc: virtual public IService { +public: + DeclareInterfaceID(IGearSvc, 0, 1); // major/minor version + + virtual ~IGearSvc() = default; + + // Get the GEAR Manager + virtual gear::GearMgr* getGearMgr() = 0; + +}; + + +#endif diff --git a/Service/GearSvc/src/GearSvc.cpp b/Service/GearSvc/src/GearSvc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7aafb84e56cc3c121826cf752582d77453a31d9c --- /dev/null +++ b/Service/GearSvc/src/GearSvc.cpp @@ -0,0 +1,44 @@ +#include "GearSvc.h" +#include "gearxml/GearXML.h" +#include "gearimpl/GearMgrImpl.h" + +DECLARE_COMPONENT(GearSvc) + +GearSvc::GearSvc(const std::string& name, ISvcLocator* svc) + : base_class(name, svc), + m_gearMgr(nullptr) +{ +} + +GearSvc::~GearSvc() +{ +} + +gear::GearMgr* GearSvc::getGearMgr() +{ + return m_gearMgr; +} + +StatusCode GearSvc::initialize() +{ + if ( m_gearFile.size() > 0 ) { + info() << "instantiated GEAR from file " << m_gearFile << endmsg; + m_gearMgr = gear::GearXML(m_gearFile).createGearMgr(); + } + else { + warning() << "no GEAR XML file given ..." << endmsg; + m_gearMgr = new gear::GearMgrImpl; + } + + return StatusCode::SUCCESS; +} + +StatusCode GearSvc::finalize() +{ + if ( m_gearMgr ) { + delete m_gearMgr; + m_gearMgr = nullptr; + } + + return StatusCode::SUCCESS; +} diff --git a/Service/GearSvc/src/GearSvc.h b/Service/GearSvc/src/GearSvc.h new file mode 100644 index 0000000000000000000000000000000000000000..00a8045834baba425c732aab2bf754313d01f663 --- /dev/null +++ b/Service/GearSvc/src/GearSvc.h @@ -0,0 +1,25 @@ +#ifndef GEAR_SVC_H +#define GEAR_SVC_H + +#include "GearSvc/IGearSvc.h" +#include <GaudiKernel/Service.h> + +class GearSvc : public extends<Service, IGearSvc> +{ + public: + GearSvc(const std::string& name, ISvcLocator* svc); + ~GearSvc(); + + gear::GearMgr* getGearMgr() override; + + StatusCode initialize() override; + StatusCode finalize() override; + + private: + + Gaudi::Property<std::string> m_gearFile{this, "GearXMLFile", ""}; + + gear::GearMgr* m_gearMgr; +}; + +#endif