Skip to content

[math] PRBS generator #8798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions documentation/users-guide/MathLibraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ which are provided in the `ROOT::Math` namespace are:
for evaluating one-dimensional (`ROOT::Math::IBaseFunctiononeDim`) and multi-dimensional functions
(`ROOT::Math::IBaseFunctionMultiDim`) and parametric function interfaces for evaluating functions with parameters in one
(`ROOT::Math::IParametricFunctionOneDim`) or multi dimensions (`ROOT::Math::IParametricFunctionMultiDim`).
A set of user convenient wrapper classes, such as `ROOT::Math::Functor` is provided for wrapping user-classes in the needed interface,
required to use the algorithms of the `ROOT` Mathematical libraries.
A set of user convenient wrapper classes, such as `ROOT::Math::Functor` is provided for wrapping user-classes in the needed interface,
required to use the algorithms of the `ROOT` Mathematical libraries.

- Numerical algorithms interfaces and in same cases default implementations for:
- numerical integration;
- numerical differentiation;
- one dimensional root-finding;
- one-dimensional minimization;
- multi-dimensional minimization (only the `ROOT::Math::Minimizer` interface)
- one dimensional root-finding;
- one-dimensional minimization;
- multi-dimensional minimization (only the `ROOT::Math::Minimizer` interface)

- Fitting classes: set of classes for fitting generic data sets. These classes are provided in the namespace `ROOT::Fit`.
They are describing separately in the Fitting chapter.
Expand Down Expand Up @@ -268,6 +268,7 @@ classes. 4 different types exist: **`TRandom`**, **`TRandom1`**,
of random generators. **`TRandom`** is the base class used by others. It
implements methods for generating random numbers according to
pre-defined distributions, such as Gaussian or Poisson.
For random bit sequence generators, see **`TRandomBinary`**.

### TRandom

Expand Down Expand Up @@ -592,6 +593,11 @@ Here are the CPU times obtained using the four random classes on an
| `UNURAN` | | | | |
+--------------------+---------------+----------------+----------------+----------------+

### TRandomBinary

This class generates pseudo-random binary sequences, to match those
often implemented in electronic chips, based on linear feedback shift
registers. A usage example can be found in $ROOTSYS/tutorials/PRBS.C.

## Mathematical Functions

Expand Down Expand Up @@ -1469,10 +1475,10 @@ iteration the subinterval with the largest estimated error is bisected. It is po
* `Integration::kGAUSS41` : 41 points Gauss-Konrod rule (value = 4)
* `Integration::kGAUSS51` : 51 points Gauss-Konrod rule (value = 5)
* `Integration::kGAUSS61` : 61 points Gauss-Konrod rule (value = 6)
The higher-order rules give better accuracy for smooth functions, while lower-order rules save time when the function contains local difficulties, such as discontinuities. If no integration rule
is passed, the 31 points rule is used as default.
The higher-order rules give better accuracy for smooth functions, while lower-order rules save time when the function contains local difficulties, such as discontinuities. If no integration rule
is passed, the 31 points rule is used as default.

* `ROOT::Math::Integration::kADAPTIVESINGULAR`: based on `gsl_integration_qags`. It is an integration type which can be used in the case of the presence of singularities.It uses the
* `ROOT::Math::Integration::kADAPTIVESINGULAR`: based on `gsl_integration_qags`. It is an integration type which can be used in the case of the presence of singularities.It uses the
Gauss-Kronrod 21-point integration rule. This is the default algorithm

Note that when using the common `ROOT::Math::IntegratorOneDIm` class the enumeration type defining the algorithm must be defined in the namespace `ROOT::Math::IntegrationOneDim` (to distinguish from
Expand Down
2 changes: 2 additions & 0 deletions math/mathcore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ set(HEADERS
TRandom1.h
TRandom2.h
TRandom3.h
TRandomBinary.h
TRandomGen.h
TStatistic.h
VectorizedTMath.h
Expand Down Expand Up @@ -186,6 +187,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(MathCore
src/TRandom1.cxx
src/TRandom2.cxx
src/TRandom3.cxx
src/TRandomBinary.cxx
src/TRandomGen.cxx
src/TStatistic.cxx
src/UnBinData.cxx
Expand Down
1 change: 1 addition & 0 deletions math/mathcore/inc/LinkDef2.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#pragma link C++ class TRandom1+;
#pragma link C++ class TRandom2+;
#pragma link C++ class TRandom3-;
#pragma link C++ class TRandomBinary+;

#pragma link C++ class ROOT::Math::TRandomEngine+;
#pragma link C++ class ROOT::Math::LCGEngine+;
Expand Down
150 changes: 150 additions & 0 deletions math/mathcore/inc/TRandomBinary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// @(#)root/mathcore:$Id$
// Author: Fernando Hueso-González 04/08/2021

#ifndef ROOT_TRandomBinary
#define ROOT_TRandomBinary

//////////////////////////////////////////////////////////////////////////
// //
// TRandomBinary //
// //
// Pseudo Random Binary Sequence generator class (periodicity = 2**n-1) //
// //
//////////////////////////////////////////////////////////////////////////

#include <array>
#include <bitset>
#include <vector>
#include <set>
#include <cmath>
#include "Rtypes.h"
#include "TError.h"

class TRandomBinary {
public:
/**
* @brief Generate the next pseudo-random bit using the current state of a linear feedback shift register (LFSR) and update it
* @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
* @tparam nTaps the number of taps
* @param lfsr the current value of the LFSR. Passed by reference, it will be updated with the next value
* @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial. Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
* @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit position 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
* @return the new random bit
* @throw an exception is thrown if taps are out of the range [1, k]
* @see https://en.wikipedia.org/wiki/Monic_polynomial
* @see https://en.wikipedia.org/wiki/Linear-feedback_shift_register
* @see https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
*/
template <size_t k, size_t nTaps>
static bool
NextLFSR(std::bitset<k>& lfsr, const std::array<uint16_t, nTaps> taps, const bool left = true)
{
static_assert(k <= 32, "For the moment, only supported until k == 32.");
static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
static_assert(nTaps > 0, "At least one tap is needed for the LFSR.");
static_assert(nTaps <= k, "Cannot use more taps than polynomial order");

// First, calculate the XOR (^) of all selected bits (marked by the taps)
bool newBit = lfsr[taps.at(0) - 1]; // the exponent E of the polynomial correspond to index E - 1 in the bitset
for(uint16_t j = 1; j < nTaps ; ++j)
{
newBit ^= lfsr[taps.at(j) - 1];
}

//Apply the shift to the register in the right direction, and overwrite the empty one with newBit
if(left)
{
lfsr <<= 1;
lfsr[0] = newBit;
}
else
{
lfsr >>= 1;
lfsr[k-1] = newBit;
}

return newBit;
}

/**
* @brief Generation of a sequence of pseudo-random bits using a linear feedback shift register (LFSR), until a register value is repeated (or maxPeriod is reached)
* @tparam k the length of the LFSR, usually also the order of the monic polynomial PRBS-k (last exponent)
* @tparam nTaps the number of taps
* @param start the start value (seed) of the LFSR
* @param taps the taps that will be XOR-ed to calculate the new bit. They are the exponents of the monic polynomial. Ordering is unimportant. Note that an exponent E in the polynom maps to bit index E-1 in the LFSR.
* @param left if true, the direction of the register shift is to the left <<, the newBit is set on lfsr at bit position 0 (right). If false, shift is to the right and the newBit is stored at bit position (k-1)
* @param wrapping if true, allow repetition of values in the LFSRhistory, until maxPeriod is reached or the repeated value == start. Enabling this option saves memory as no history is kept
* @param oppositeBit if true, use the high/low bit of the LFSR to store output (for left=true/false, respectively) instead of the newBit returned by ::NextLFSR
* @return the array of pseudo random bits, or an empty array if input was incorrect
* @see https://en.wikipedia.org/wiki/Monic_polynomial
* @see https://en.wikipedia.org/wiki/Linear-feedback_shift_register
* @see https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence
*/
template <size_t k, size_t nTaps>
static std::vector<bool>
GenerateSequence(const std::bitset<k> start, const std::array<uint16_t, nTaps> taps, const bool left = true, const bool wrapping = false, const bool oppositeBit = false)
{
std::vector<bool> result; // Store result here

//Sanity-checks
static_assert(k <= 32, "For the moment, only supported until k == 32.");
static_assert(k > 0, "Non-zero degree is needed for the LFSR.");
static_assert(nTaps >= 2, "At least two taps are needed for a proper sequence");
static_assert(nTaps <= k, "Cannot use more taps than polynomial order");
for(auto tap : taps) {
if(tap > k || tap == 0) {
Error("TRandomBinary", "Tap %u is out of range [1,%lu]", tap, k);
return result;
}
}
if(start.none()) {
Error("TRandomBinary", "A non-zero start value is needed");
return result;
}

// Calculate maximum period and pre-allocate space in result
const uint32_t maxPeriod = pow(2,k) - 1;
result.reserve(maxPeriod);

std::set<uint32_t> lfsrHistory; // a placeholder to store the history of all different values of the LFSR
std::bitset<k> lfsr(start); // a variable storing the current value of the LFSR
uint32_t i = 0; // a loop counter
if(oppositeBit) // if oppositeBit enabled, first value is already started with the seed
result.emplace_back(left ? lfsr[k-1] : lfsr[0]);

//Loop now until maxPeriod or a lfsr value is repeated. If wrapping enabled, allow repeated values if not equal to seed
do {
bool newBit = NextLFSR(lfsr, taps, left);

if(!oppositeBit)
result.emplace_back(newBit);
else
result.emplace_back(left ? lfsr[k-1] : lfsr[0]);

++i;

if(!wrapping) // If wrapping not allowed, break the loop once a repeated value is encountered
{
if(lfsrHistory.count(lfsr.to_ulong()))
break;

lfsrHistory.insert(lfsr.to_ulong()); // Add to the history
}
}
while(lfsr != start && i < maxPeriod);

if(oppositeBit)
result.pop_back();// remove last element, as we already pushed the one from the seed above the while loop

result.shrink_to_fit();//only some special taps will lead to the maxPeriod, others will stop earlier

return result;
}

TRandomBinary() = default;
virtual ~TRandomBinary() = default;

ClassDef(TRandomBinary,0) //Pseudo Random Binary Sequence (periodicity = 2**n - 1)
};

#endif
2 changes: 1 addition & 1 deletion math/mathcore/src/TRandom2.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

\class TRandom2

Random number generator class based on the maximally quidistributed combined
Random number generator class based on the maximally equidistributed combined
Tausworthe generator by L'Ecuyer.

The period of the generator is 2**88 (about 10**26) and it uses only 3 words
Expand Down
37 changes: 37 additions & 0 deletions math/mathcore/src/TRandomBinary.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @(#)root/mathcore:$Id$
// Author: Fernando Hueso-González 04/08/2021

/**

\class TRandomBinary

@ingroup Random

This class contains a generator of Pseudo Random Binary Sequences (<a
href="https://en.wikipedia.org/wiki/Pseudorandom_binary_sequence">PRBS</a>).
It should NOT be used for general-purpose random number generation or any
statistical study, see ::TRandom2 class instead.

The goal is to generate binary bit sequences with the same algorithm as the ones usually implemented
in electronic chips, so that the theoretically expected ones can be compared with the acquired sequences.

The main ingredients of a PRBS generator are a monic polynomial of maximum degree \f$n\f$, with coefficients
either 0 or 1, and a <a href="https://www.nayuki.io/page/galois-linear-feedback-shift-register">Galois</a>
linear-feedback shift register with a non-zero seed. When the monic polynomial exponents are chosen appropriately,
the period of the resulting bit sequence (0s and 1s) yields \f$2^n - 1\f$.

Other implementations can be found here:

- https://gist.github.com/mattbierner/d6d989bf26a7e54e7135
- https://root.cern/doc/master/civetweb_8c_source.html#l06030
- https://cryptography.fandom.com/wiki/Linear_feedback_shift_register
- https://www3.advantest.com/documents/11348/33b24c8a-c8cb-40b8-a2a7-37515ba4abc8
- https://www.reddit.com/r/askscience/comments/63a10q/for_prbs3_with_clock_input_on_each_gate_how_can/
- https://es.mathworks.com/help/serdes/ref/prbs.html
- https://metacpan.org/pod/Math::PRBS

*/

#include "TRandomBinary.h"

ClassImp(TRandomBinary);
46 changes: 46 additions & 0 deletions tutorials/math/PRBS.C
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/// \file
/// \ingroup tutorial_math
/// \notebook -nodraw
/// Tutorial illustrating the use of TRandomBinary::GenerateSequence
/// can be run with:
///
/// ~~~{.cpp}
/// root > .x PRBS.C
/// root > .x PRBS.C+ with ACLIC
/// ~~~
///
/// \macro_output
/// \macro_code
///
/// \author Fernando Hueso-González

#include <TRandomBinary.h>
#include <iostream>

void PRBS ()
{
printf("\nTRandomBinary::GenerateSequence PRBS3, PRBS4 and PRBS5 tests\n");
printf("==========================\n");

//PRBS3
std::array<uint16_t, 2> taps3 = {2, 3}; // Exponents of the monic polynomial
auto prbs3 = TRandomBinary::GenerateSequence(std::bitset<3>().flip(), taps3);// Start value all high

//PRBS4
std::array<uint16_t, 2> taps4 = {3, 4}; // Exponents of the monic polynomial
auto prbs4 = TRandomBinary::GenerateSequence(std::bitset<4>().flip(), taps4);// Start value all high

//PRBS7
std::array<uint16_t, 2> taps5 = {5, 3}; // Exponents of the monic polynomial
auto prbs5 = TRandomBinary::GenerateSequence(std::bitset<5>().flip(), taps5);// Start value all high

for(auto prbs : {prbs3, prbs4, prbs5})
{
std::cout << "PRBS period " << prbs.size() << ":\t";
for(auto p : prbs)
{
std::cout << p << " ";
}
std::cout << std::endl;
}
}
Loading