From c907b6b7107a8f0a2756ba3e424b4913df3048d4 Mon Sep 17 00:00:00 2001 From: Jussi Lindgren <openvibe-dev@inria.fr> Date: Wed, 16 Sep 2015 11:41:16 +0200 Subject: [PATCH] Modules: Fixing issues with nonrepeatable experiments due to randomness - Changed System::Math to use its own simple random generator as dependencies can reset rand() seed on some systems - Fixed System::Math algorithm to randomize numbers with ceilings to get a more uniform result - Changed classifier trainer to use System::Math for shuffling - Changed stimulation voter to use System::Math for tie breaking - Added a few (unrelated) safety checks to Pairwise Decision algorithm --- .../platform/designer/src/ovd_main.cpp | 3 +- modules/system/src/ovCMath.cpp | 75 ++++++++++++++++--- .../ovpCAlgorithmPairwiseDecision.cpp | 8 +- .../ovpCBoxAlgorithmClassifierTrainer.cpp | 4 +- .../ovpCBoxAlgorithmStimulationVoter.cpp | 15 ++-- 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/applications/platform/designer/src/ovd_main.cpp b/applications/platform/designer/src/ovd_main.cpp index 70f4c82c23..fb39a2d2af 100644 --- a/applications/platform/designer/src/ovd_main.cpp +++ b/applications/platform/designer/src/ovd_main.cpp @@ -577,7 +577,8 @@ int go(int argc, char ** argv) if(l_oConfiguration.m_oFlag.count(CommandLineFlag_RandomSeed)) { - System::Math::initializeRandomMachine(atol(l_oConfiguration.m_oFlag[CommandLineFlag_RandomSeed].c_str())); + const int32 l_i32Seed = atol(l_oConfiguration.m_oFlag[CommandLineFlag_RandomSeed].c_str()); + System::Math::initializeRandomMachine(static_cast<const uint32>(l_i32Seed)); } else { diff --git a/modules/system/src/ovCMath.cpp b/modules/system/src/ovCMath.cpp index 199ea3a94a..244882d667 100644 --- a/modules/system/src/ovCMath.cpp +++ b/modules/system/src/ovCMath.cpp @@ -1,13 +1,64 @@ +/** + * + * @fixme This class could benefit from a serious overhaul, e.g. using randomness from some established library or C11. + * + * - Here Linear Congruential Generator is re-implemented to avoid third-party dependencies messing the up the rand() state. + * This happened before when we used the global srand() / rand(). It made hard to make repeatable experiments on some + * platforms. The generated randomness from the introduced home-made class is not super but it should be sufficient for + * OpenViBE's present use-cases. + * + * Other notes + * + * - The randomFloat32() and randomFloat64() functions might not be working as expected (verify) + * - Due to generative process, values generated above l_ui32RandMax may not be dense (verify) + * - randomUInteger32WithCeiling() may not be dense either + * + */ #include "ovCMath.h" #include <cstdlib> #include <cstring> using namespace System; +class RandomGenerator +{ +private: + uint32 l_ui32NextValue; + +public: + static const uint32 l_ui32RandMax = 0x7FFFFFFF; // (2^32)/2-1 == 2147483647 + + RandomGenerator(uint32 seed = 1) : l_ui32NextValue( seed ) {} + + int32 rand(void) + { + // Pretty much C99 convention and parameters for a Linear Congruential Generator + l_ui32NextValue = (l_ui32NextValue * 1103515245 + 12345) & l_ui32RandMax; + return static_cast<int32>(l_ui32NextValue); + } + + void setSeed(uint32 seed) + { + l_ui32NextValue = seed; + } + + uint32 getSeed(void) const + { + return l_ui32NextValue; + } +}; + +// Should be only accessed via Math:: calls defined below +static RandomGenerator g_oRandomGenerator; + boolean Math::initializeRandomMachine(const uint64 ui64RandomSeed) { - srand((unsigned int)ui64RandomSeed); + g_oRandomGenerator.setSeed(static_cast<uint32>(ui64RandomSeed)); + + // For safety, we install also the C++ basic random engine (it might be useg by dependencies, old code, etc) + srand(static_cast<unsigned int>(ui64RandomSeed)); + return true; } @@ -28,16 +79,22 @@ uint32 Math::randomUInteger32(void) uint64 Math::randomUInteger64(void) { - uint64 r1=rand(); - uint64 r2=rand(); - uint64 r3=rand(); - uint64 r4=rand(); + const uint64 r1=g_oRandomGenerator.rand(); + const uint64 r2=g_oRandomGenerator.rand(); + const uint64 r3=g_oRandomGenerator.rand(); + const uint64 r4=g_oRandomGenerator.rand(); return (r1<<24)^(r2<<16)^(r3<<8)^(r4); } uint32 Math::randomUInteger32WithCeiling(uint32 ui32upperLimit) { - return static_cast<int32>(randomUInteger64()) % ui32upperLimit; + // float in range [0,1] + const float64 l_f64Temp = g_oRandomGenerator.rand() / static_cast<float64>(g_oRandomGenerator.l_ui32RandMax); + + // static_cast is effectively floor(), so below we get output range [0,upperLimit-1], without explicit subtraction of 1 + const uint32 l_ui32ReturnValue = static_cast<uint32>( ui32upperLimit * l_f64Temp ); + + return l_ui32ReturnValue; } int8 Math::randomSInterger8(void) @@ -62,20 +119,20 @@ int64 Math::randomSInterger64(void) float32 Math::randomFloat32(void) { - uint32 r=randomUInteger32(); + const uint32 r=randomUInteger32(); float32 fr; ::memcpy(&fr, &r, sizeof(fr)); return fr; } float32 Math::randomFloat32BetweenZeroAndOne(void) { - float32 fr = static_cast<float32>(rand()) / static_cast<float32>(RAND_MAX); + const float32 fr = static_cast<float32>(g_oRandomGenerator.rand()) / static_cast<float32>(g_oRandomGenerator.l_ui32RandMax); return fr; } float64 Math::randomFloat64(void) { - uint64 r=randomUInteger64(); + const uint64 r=randomUInteger64(); float64 fr; ::memcpy(&fr, &r, sizeof(fr)); return fr; diff --git a/plugins/processing/classification/src/algorithms/ovpCAlgorithmPairwiseDecision.cpp b/plugins/processing/classification/src/algorithms/ovpCAlgorithmPairwiseDecision.cpp index a96c936ef0..d881b7be2e 100644 --- a/plugins/processing/classification/src/algorithms/ovpCAlgorithmPairwiseDecision.cpp +++ b/plugins/processing/classification/src/algorithms/ovpCAlgorithmPairwiseDecision.cpp @@ -33,11 +33,15 @@ boolean CAlgorithmPairwiseDecision::process() { TParameterHandler < XML::IXMLNode* > op_pConfiguration(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Configuration)); XML::IXMLNode* l_pTempNode = (XML::IXMLNode*)op_pConfiguration; - return this->loadConfiguration(*l_pTempNode); + if(l_pTempNode != NULL) + { + return this->loadConfiguration(*l_pTempNode); + } + return false; } else if(this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parametrize)) { - this->parametrize(); + return this->parametrize(); } return true; } diff --git a/plugins/processing/classification/src/box-algorithms/ovpCBoxAlgorithmClassifierTrainer.cpp b/plugins/processing/classification/src/box-algorithms/ovpCBoxAlgorithmClassifierTrainer.cpp index df241c6fd0..aa3391fd98 100644 --- a/plugins/processing/classification/src/box-algorithms/ovpCBoxAlgorithmClassifierTrainer.cpp +++ b/plugins/processing/classification/src/box-algorithms/ovpCBoxAlgorithmClassifierTrainer.cpp @@ -1,6 +1,7 @@ #include "ovpCBoxAlgorithmClassifierTrainer.h" #include <system/ovCMemory.h> +#include <system/ovCMath.h> #include <fstream> #include <sstream> @@ -326,8 +327,7 @@ boolean CBoxAlgorithmClassifierTrainer::process(void) if (l_bRandomizeVectorOrder) { this->getLogManager() << LogLevel_Info << "Randomizing the feature vector set\n"; - random_shuffle(m_vFeatureVectorIndex.begin(), m_vFeatureVectorIndex.end()); - + random_shuffle(m_vFeatureVectorIndex.begin(), m_vFeatureVectorIndex.end(), System::Math::randomUInteger32WithCeiling); } if(m_ui64PartitionCount>=2) diff --git a/plugins/processing/stimulation/src/box-algorithms/ovpCBoxAlgorithmStimulationVoter.cpp b/plugins/processing/stimulation/src/box-algorithms/ovpCBoxAlgorithmStimulationVoter.cpp index 11e6ebeac9..b0c8afec23 100644 --- a/plugins/processing/stimulation/src/box-algorithms/ovpCBoxAlgorithmStimulationVoter.cpp +++ b/plugins/processing/stimulation/src/box-algorithms/ovpCBoxAlgorithmStimulationVoter.cpp @@ -1,6 +1,7 @@ #include "ovpCBoxAlgorithmStimulationVoter.h" #include <openvibe/ovITimeArithmetics.h> +#include <system/ovCMath.h> #include <vector> #include <string> @@ -40,12 +41,12 @@ boolean CBoxAlgorithmStimulationVoter::initialize(void) ip_pMemoryBuffer.initialize(m_pDecoder->getInputParameter(OVP_GD_Algorithm_StimulationStreamDecoder_InputParameterId_MemoryBufferToDecode)); op_pStimulationSet.initialize(m_pDecoder->getOutputParameter(OVP_GD_Algorithm_StimulationStreamDecoder_OutputParameterId_StimulationSet)); - m_ui64MinimumVotes = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0); - m_f64TimeWindow = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1); - m_oClearVotes = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)); - m_oOutputDateMode = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3)); - m_ui64RejectClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4); - m_oRejectClass_CanWin = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5)); + m_ui64MinimumVotes = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0); + m_f64TimeWindow = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1); + m_oClearVotes = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2)); + m_oOutputDateMode = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3)); + m_ui64RejectClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4); + m_oRejectClass_CanWin = ((uint64)FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5)); this->getLogManager() << LogLevel_Debug << "Vote clear mode " << m_oClearVotes << ", timestamp at " << m_oOutputDateMode << ", reject mode " << m_oRejectClass_CanWin << "\n"; @@ -177,7 +178,7 @@ boolean CBoxAlgorithmStimulationVoter::process(void) } else if( l_ui64StimulusVotes == l_ui64MaxVotes ) { // Break ties arbitrarily - if(rand() > RAND_MAX/2 ) + if(System::Math::randomFloat32BetweenZeroAndOne() > 0.5) { l_ui64ResultClassLabel = l_ui64StimulusType; l_ui64MaxVotes = l_ui64StimulusVotes; -- GitLab