MRPT  2.0.2
RandomGenerators.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | https://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2020, Individual contributors, see AUTHORS file |
6  | See: https://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See: https://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #pragma once
10 
12 #include <algorithm>
13 #include <cstddef>
14 #include <limits>
15 #include <limits> // numeric_limits
16 #include <random>
17 #include <stdexcept>
18 #include <type_traits> // remove_reference
19 #include <vector>
20 
21 // Frwd decl:
22 namespace Eigen
23 {
24 template <typename _MatrixType>
26 }
27 
28 namespace mrpt
29 {
30 /** A namespace of pseudo-random numbers generators of diferent distributions.
31  * The central class in this namespace is mrpt::random::CRandomGenerator
32  * \ingroup mrpt_random_grp
33  */
34 namespace random
35 {
36 /** Portable MT19937 random generator, C++11 UniformRandomBitGenerator
37  * compliant.
38  *
39  * It is ensured to generate the same numbers on any compiler and system.
40  * \ingroup mrpt_random_grp
41  */
43 {
44  public:
45  using result_type = uint32_t;
46  static constexpr result_type min()
47  {
48  return std::numeric_limits<result_type>::min();
49  }
50  static constexpr result_type max()
51  {
52  return std::numeric_limits<result_type>::max();
53  }
54 
56 
57  void seed(const uint32_t seed);
58 
59  private:
60  uint32_t m_MT[624];
61  uint32_t m_index{0};
62  bool m_seed_initialized{false};
63 
64  void generateNumbers();
65 };
66 
67 /** A thred-safe pseudo random number generator, based on an internal MT19937
68  * randomness generator.
69  * The base algorithm for randomness is platform-independent. See
70  * http://en.wikipedia.org/wiki/Mersenne_twister
71  *
72  * For real thread-safety, each thread must create and use its own instance of
73  * this class.
74  *
75  * Single-thread programs can use the static object
76  * mrpt::random::randomGenerator
77  * \ingroup mrpt_random_grp
78  */
80 {
81  protected:
82  /** Data used internally by the MT19937 PRNG algorithm. */
84 
85  std::normal_distribution<double> m_normdistribution;
86  std::uniform_int_distribution<uint32_t> m_uint32;
87  std::uniform_int_distribution<uint64_t> m_uint64;
88 
89  public:
90  /** @name Initialization
91  @{ */
92 
93  /** Default constructor: initialize random seed based on current time */
95  /** Constructor for providing a custom random seed to initialize the PRNG */
96  CRandomGenerator(const uint32_t seed) { randomize(seed); }
97  /** Initialize the PRNG from the given random seed */
98  void randomize(const uint32_t seed);
99  /** Randomize the generators, based on std::random_device */
100  void randomize();
101 
102  /** @} */
103 
104  /** @name Uniform pdf
105  @{ */
106 
107  /** Generate a uniformly distributed pseudo-random number using the MT19937
108  * algorithm, in the whole range of 32-bit integers.
109  * See: http://en.wikipedia.org/wiki/Mersenne_twister */
110  uint32_t drawUniform32bit();
111 
112  /** Returns a uniformly distributed pseudo-random number by joining two
113  * 32bit numbers from \a drawUniform32bit() */
114  uint64_t drawUniform64bit();
115 
116  /** You can call this overloaded method with either 32 or 64bit unsigned
117  * ints for the sake of general coding. */
118  void drawUniformUnsignedInt(uint32_t& ret_number)
119  {
120  ret_number = m_uint32(m_MT19937);
121  }
122  void drawUniformUnsignedInt(uint64_t& ret_number)
123  {
124  ret_number = m_uint64(m_MT19937);
125  }
126 
127  /** Return a uniform unsigned integer in the range [min_val,max_val] (both
128  * inclusive) */
129  template <typename T, typename U, typename V>
131  T& ret_number, const U min_val, const V max_val)
132  {
133  const T range = max_val - min_val + 1;
134  T rnd;
136  ret_number = min_val + (rnd % range);
137  }
138 
139  /** Generate a uniformly distributed pseudo-random number using the MT19937
140  * algorithm, scaled to the selected range. */
141  template <typename return_t = double>
142  return_t drawUniform(const double Min, const double Max)
143  {
144  double k = 2.3283064370807973754314699618685e-10; // 0xFFFFFFFF ^ -1
145  return static_cast<return_t>(
146  Min + (Max - Min) * drawUniform32bit() * k);
147  }
148 
149  /** Fills the given matrix with independent, uniformly distributed samples.
150  * Matrix classes can be mrpt::math::CMatrixDynamic or
151  * mrpt::math::CMatrixFixed
152  * \sa drawUniform
153  */
154  template <class MAT>
156  MAT& matrix, const double unif_min = 0, const double unif_max = 1)
157  {
158  for (size_t r = 0; r < matrix.rows(); r++)
159  for (size_t c = 0; c < matrix.cols(); c++)
160  matrix(r, c) = static_cast<typename MAT::Scalar>(
161  drawUniform(unif_min, unif_max));
162  }
163 
164  /** Fills the given vector with independent, uniformly distributed samples.
165  * \sa drawUniform
166  */
167  template <class VEC>
169  VEC& v, const double unif_min = 0, const double unif_max = 1)
170  {
171  const size_t N = v.size();
172  for (size_t c = 0; c < N; c++)
173  v[c] = static_cast<typename std::decay<decltype(v[c])>::type>(
174  drawUniform(unif_min, unif_max));
175  }
176 
177  /** @} */
178 
179  /** @name Normal/Gaussian pdf
180  @{ */
181 
182  /** Generate a normalized (mean=0, std=1) normally distributed sample.
183  * \param likelihood If desired, pass a pointer to a double which will
184  * receive the likelihood of the given sample to have been obtained, that
185  * is, the value of the normal pdf at the sample value.
186  */
187  double drawGaussian1D_normalized();
188 
189  /** Generate a normally distributed pseudo-random number.
190  * \param mean The mean value of desired normal distribution
191  * \param std The standard deviation value of desired normal distribution
192  */
193  template <typename return_t = double>
194  return_t drawGaussian1D(const double mean, const double std)
195  {
196  return static_cast<return_t>(mean + std * drawGaussian1D_normalized());
197  }
198 
199  /** Fills the given matrix with independent, 1D-normally distributed
200  * samples.
201  * Matrix classes can be mrpt::math::CMatrixDynamic or
202  * mrpt::math::CMatrixFixed
203  * \sa drawGaussian1D
204  */
205  template <class MAT>
207  MAT& matrix, const double mean = 0, const double std = 1)
208  {
209  for (decltype(matrix.rows()) r = 0; r < matrix.rows(); r++)
210  for (decltype(matrix.cols()) c = 0; c < matrix.cols(); c++)
211  matrix(r, c) = static_cast<typename MAT::Scalar>(
213  }
214 
215  /** Generates a random definite-positive matrix of the given size, using the
216  * formula C = v*v^t + epsilon*I, with "v" being a vector of gaussian random
217  * samples.
218  */
219  template <class MATRIX, class AUXVECTOR_T = MATRIX>
221  const size_t dim, const double std_scale = 1.0,
222  const double diagonal_epsilon = 1e-8)
223  {
224  AUXVECTOR_T r(dim, 1);
225  drawGaussian1DMatrix(r, 0, std_scale);
226  MATRIX cov;
227  cov.resize(dim, dim);
228  cov.matProductOf_AAt(r); // random semi-definite positive matrix:
229  for (size_t i = 0; i < dim; i++)
230  cov(i, i) += diagonal_epsilon; // make sure it's definite-positive
231  return cov;
232  }
233 
234  /** Fills the given vector with independent, 1D-normally distributed
235  * samples.
236  * \sa drawGaussian1D
237  */
238  template <class VEC>
240  VEC& v, const double mean = 0, const double std = 1)
241  {
242  const size_t N = v.size();
243  for (size_t c = 0; c < N; c++)
244  v[c] = static_cast<std::remove_reference_t<decltype(v[c])>>(
246  }
247 
248  /** Generate multidimensional random samples according to a given covariance
249  * matrix.
250  * Mean is assumed to be zero if mean==nullptr.
251  * \exception std::exception On invalid covariance matrix
252  * \sa drawGaussianMultivariateMany
253  */
254  template <typename T, typename MATRIX>
256  std::vector<T>& out_result, const MATRIX& cov,
257  const std::vector<T>* mean = nullptr)
258  {
259  const size_t dim = cov.cols();
260  if (cov.rows() != cov.cols())
261  throw std::runtime_error(
262  "drawGaussianMultivariate(): cov is not square.");
263  if (mean && mean->size() != dim)
264  throw std::runtime_error(
265  "drawGaussianMultivariate(): mean and cov sizes ");
266  MATRIX Z, D;
267  // Set size of output vector:
268  out_result.clear();
269  out_result.resize(dim, 0);
270  /** Computes the eigenvalues/eigenvector decomposition of this matrix,
271  * so that: M = Z * D * Z<sup>T</sup>, where columns in Z are the
272  * eigenvectors and the diagonal matrix D contains the eigenvalues
273  * as diagonal elements, sorted in <i>ascending</i> order.
274  */
275  cov.eigenVectors(Z, D);
276  // Scale eigenvectors with eigenvalues:
277  D = D.array().sqrt().matrix();
278  Z.matProductOf_AB(Z, D);
279  for (size_t i = 0; i < dim; i++)
280  {
281  T rnd = this->drawGaussian1D_normalized();
282  for (size_t d = 0; d < dim; d++) out_result[d] += (Z(d, i) * rnd);
283  }
284  if (mean)
285  for (size_t d = 0; d < dim; d++) out_result[d] += (*mean)[d];
286  }
287 
288  /** Generate multidimensional random samples according to a given covariance
289  * matrix.
290  * Mean is assumed to be zero if mean==nullptr.
291  * \exception std::exception On invalid covariance matrix
292  * \sa drawGaussianMultivariateMany
293  */
294  template <class VECTORLIKE, class COVMATRIX>
296  VECTORLIKE& out_result, const COVMATRIX& cov,
297  const VECTORLIKE* mean = nullptr)
298  {
299  const size_t N = cov.rows();
300  if (cov.rows() != cov.cols())
301  throw std::runtime_error(
302  "drawGaussianMultivariate(): cov is not square.");
303  if (mean && size_t(mean->size()) != N)
304  throw std::runtime_error(
305  "drawGaussianMultivariate(): mean and cov sizes ");
306 
307  // Compute eigenvalues/eigenvectors of cov:
308  COVMATRIX eigVecs;
309  std::vector<typename COVMATRIX::Scalar> eigVals;
310  cov.eig_symmetric(eigVecs, eigVals, false /*sorted*/);
311 
312  // Scale eigenvectors with eigenvalues:
313  // D.Sqrt(); Z = Z * D; (for each column)
314  for (typename COVMATRIX::Index c = 0; c < eigVecs.cols(); c++)
315  {
316  const auto s = std::sqrt(eigVals[c]);
317  for (typename COVMATRIX::Index r = 0; r < eigVecs.rows(); r++)
318  eigVecs(c, r) *= s;
319  }
320 
321  // Set size of output vector:
322  out_result.assign(N, 0);
323 
324  for (size_t i = 0; i < N; i++)
325  {
327  for (size_t d = 0; d < N; d++)
328  out_result[d] += eigVecs.coeff(d, i) * rnd;
329  }
330  if (mean)
331  for (size_t d = 0; d < N; d++) out_result[d] += (*mean)[d];
332  }
333 
334  /** Generate a given number of multidimensional random samples according to
335  * a given covariance matrix.
336  * \param cov The covariance matrix where to draw the samples from.
337  * \param desiredSamples The number of samples to generate.
338  * \param ret The output list of samples
339  * \param mean The mean, or zeros if mean==nullptr.
340  */
341  template <typename VECTOR_OF_VECTORS, typename COVMATRIX>
343  VECTOR_OF_VECTORS& ret, size_t desiredSamples, const COVMATRIX& cov,
344  const typename VECTOR_OF_VECTORS::value_type* mean = nullptr)
345  {
346  const size_t N = cov.rows();
347  if (cov.rows() != cov.cols())
348  throw std::runtime_error(
349  "drawGaussianMultivariateMany(): cov is not square.");
350  if (mean && size_t(mean->size()) != N)
351  throw std::runtime_error(
352  "drawGaussianMultivariateMany(): mean and cov sizes ");
353 
354  // Compute eigenvalues/eigenvectors of cov:
355  COVMATRIX eigVecs;
356  std::vector<typename COVMATRIX::Scalar> eigVals;
357  cov.eig_symmetric(eigVecs, eigVals, false /*sorted*/);
358 
359  // Scale eigenvectors with eigenvalues:
360  // D.Sqrt(); Z = Z * D; (for each column)
361  for (typename COVMATRIX::Index c = 0; c < eigVecs.cols(); c++)
362  {
363  const auto s = std::sqrt(eigVals[c]);
364  for (typename COVMATRIX::Index r = 0; r < eigVecs.rows(); r++)
365  eigVecs(c, r) *= s;
366  }
367 
368  // Set size of output vector:
369  ret.resize(desiredSamples);
370  for (size_t k = 0; k < desiredSamples; k++)
371  {
372  ret[k].assign(N, 0);
373  for (size_t i = 0; i < N; i++)
374  {
376  for (size_t d = 0; d < N; d++)
377  ret[k][d] += eigVecs.coeff(d, i) * rnd;
378  }
379  if (mean)
380  for (size_t d = 0; d < N; d++) ret[k][d] += (*mean)[d];
381  }
382  }
383 
384  /** @} */
385 
386  /** @name Miscellaneous
387  @{ */
388 
389  /** Returns a random permutation of a vector: all the elements of the input
390  * vector are in the output but at random positions.
391  */
392  template <class VEC>
393  void permuteVector(const VEC& in_vector, VEC& out_result)
394  {
395  out_result = in_vector;
396  const size_t N = out_result.size();
397  if (N > 1) mrpt::random::shuffle(&out_result[0], &out_result[N - 1]);
398  }
399 
400  /** @} */
401 
402 }; // end of CRandomGenerator
403 // --------------------------------------------------------------
404 
405 /** A static instance of a CRandomGenerator class, for use in single-thread
406  * applications */
407 CRandomGenerator& getRandomGenerator();
408 
409 /** A random number generator for usage in STL algorithms expecting a function
410  * like this (eg, random_shuffle):
411  */
412 inline ptrdiff_t random_generator_for_STL(ptrdiff_t i)
413 {
414  return getRandomGenerator().drawUniform32bit() % i;
415 }
416 
417 /** Fills the given matrix with independent, uniformly distributed samples.
418  * Matrix classes can be mrpt::math::CMatrixDynamic or
419  * mrpt::math::CMatrixFixed
420  * \sa matrixRandomNormal
421  */
422 template <class MAT>
424  MAT& matrix, const double unif_min = 0, const double unif_max = 1)
425 {
426  for (typename MAT::Index r = 0; r < matrix.rows(); r++)
427  for (typename MAT::Index c = 0; c < matrix.cols(); c++)
428  matrix(r, c) = static_cast<typename MAT::Scalar>(
429  getRandomGenerator().drawUniform(unif_min, unif_max));
430 }
431 
432 /** Fills the given matrix with independent, uniformly distributed samples.
433  * \sa vectorRandomNormal
434  */
435 template <class T>
437  std::vector<T>& v_out, const T& unif_min = 0, const T& unif_max = 1)
438 {
439  size_t n = v_out.size();
440  for (size_t r = 0; r < n; r++)
441  v_out[r] = getRandomGenerator().drawUniform(unif_min, unif_max);
442 }
443 
444 /** Fills the given matrix with independent, normally distributed samples.
445  * Matrix classes can be mrpt::math::CMatrixDynamic or
446  * mrpt::math::CMatrixFixed
447  * \sa matrixRandomUni
448  */
449 template <class MAT>
451  MAT& matrix, const double mean = 0, const double std = 1)
452 {
453  for (typename MAT::Index r = 0; r < matrix.rows(); r++)
454  for (typename MAT::Index c = 0; c < matrix.cols(); c++)
455  matrix(r, c) = static_cast<typename MAT::Scalar>(
457 }
458 
459 /** Generates a random vector with independent, normally distributed samples.
460  * \sa matrixRandomUni
461  */
462 template <class T>
464  std::vector<T>& v_out, const T& mean = 0, const T& std = 1)
465 {
466  size_t n = v_out.size();
467  for (size_t r = 0; r < n; r++)
468  v_out[r] =
470 }
471 
472 /** Randomize the generators.
473  * A seed can be providen, or a current-time based seed can be used (default)
474  */
475 inline void Randomize(const uint32_t seed)
476 {
478 }
479 inline void Randomize() { getRandomGenerator().randomize(); }
480 /** Returns a random permutation of a vector: all the elements of the input
481  * vector are in the output but at random positions.
482  */
483 template <class T>
485  const std::vector<T>& in_vector, std::vector<T>& out_result)
486 {
487  getRandomGenerator().permuteVector(in_vector, out_result);
488 }
489 
490 /** Generate a given number of multidimensional random samples according to a
491  * given covariance matrix.
492  * \param cov The covariance matrix where to draw the samples from.
493  * \param desiredSamples The number of samples to generate.
494  * \param samplesLikelihoods If desired, set to a valid pointer to a vector,
495  * where it will be stored the likelihoods of having obtained each sample: the
496  * product of the gaussian-pdf for each independent variable.
497  * \param ret The output list of samples
498  *
499  * \exception std::exception On invalid covariance matrix
500  *
501  * \sa randomNormalMultiDimensional
502  */
503 template <typename T, typename MATRIX>
505  const MATRIX& cov, size_t desiredSamples, std::vector<std::vector<T>>& ret,
506  std::vector<T>* samplesLikelihoods = nullptr)
507 {
509  ret, desiredSamples, cov, static_cast<const std::vector<T>*>(nullptr),
510  samplesLikelihoods);
511 }
512 
513 /** Generate multidimensional random samples according to a given covariance
514  * matrix.
515  * \exception std::exception On invalid covariance matrix
516  * \sa randomNormalMultiDimensional
517  */
518 template <typename T, typename MATRIXLIKE>
520  const MATRIXLIKE& cov, size_t desiredSamples,
521  std::vector<std::vector<T>>& ret)
522 {
523  getRandomGenerator().drawGaussianMultivariateMany(ret, desiredSamples, cov);
524 }
525 
526 /** Generate multidimensional random samples according to a given covariance
527  * matrix.
528  * \exception std::exception On invalid covariance matrix
529  * \sa randomNormalMultiDimensionalMany
530  */
531 template <typename T, typename MATRIX>
532 void randomNormalMultiDimensional(const MATRIX& cov, std::vector<T>& out_result)
533 {
535 }
536 
537 } // namespace random
538 } // namespace mrpt
uint32_t drawUniform32bit()
Generate a uniformly distributed pseudo-random number using the MT19937 algorithm, in the whole range of 32-bit integers.
void permuteVector(const VEC &in_vector, VEC &out_result)
Returns a random permutation of a vector: all the elements of the input vector are in the output but ...
MATRIX drawDefinitePositiveMatrix(const size_t dim, const double std_scale=1.0, const double diagonal_epsilon=1e-8)
Generates a random definite-positive matrix of the given size, using the formula C = v*v^t + epsilon*...
void drawUniformUnsignedInt(uint32_t &ret_number)
You can call this overloaded method with either 32 or 64bit unsigned ints for the sake of general cod...
double Scalar
Definition: KmUtils.h:43
void resize(size_t row, size_t col)
void shuffle(RandomIt first, RandomIt last, URBG &&g)
Uniform shuffle a sequence.
void drawGaussian1DVector(VEC &v, const double mean=0, const double std=1)
Fills the given vector with independent, 1D-normally distributed samples.
void randomize(const uint32_t seed)
Initialize the PRNG from the given random seed.
void drawGaussian1DMatrix(MAT &matrix, const double mean=0, const double std=1)
Fills the given matrix with independent, 1D-normally distributed samples.
std::uniform_int_distribution< uint64_t > m_uint64
STL namespace.
std::normal_distribution< double > m_normdistribution
A thred-safe pseudo random number generator, based on an internal MT19937 randomness generator...
void randomPermutation(const std::vector< T > &in_vector, std::vector< T > &out_result)
Returns a random permutation of a vector: all the elements of the input vector are in the output but ...
void drawUniformUnsignedIntRange(T &ret_number, const U min_val, const V max_val)
Return a uniform unsigned integer in the range [min_val,max_val] (both inclusive) ...
bool eig_symmetric(Derived &eVecs, std::vector< Scalar > &eVals, bool sorted=true) const
Read: eig()
void matProductOf_AAt(const MAT_A &A)
this = A * AT
Definition: MatrixBase.h:276
void drawUniformMatrix(MAT &matrix, const double unif_min=0, const double unif_max=1)
Fills the given matrix with independent, uniformly distributed samples.
return_t drawUniform(const double Min, const double Max)
Generate a uniformly distributed pseudo-random number using the MT19937 algorithm, scaled to the selected range.
void drawGaussianMultivariate(VECTORLIKE &out_result, const COVMATRIX &cov, const VECTORLIKE *mean=nullptr)
Generate multidimensional random samples according to a given covariance matrix.
Portable MT19937 random generator, C++11 UniformRandomBitGenerator compliant.
static constexpr result_type min()
void drawGaussianMultivariateMany(VECTOR_OF_VECTORS &ret, size_t desiredSamples, const COVMATRIX &cov, const typename VECTOR_OF_VECTORS::value_type *mean=nullptr)
Generate a given number of multidimensional random samples according to a given covariance matrix...
static constexpr result_type max()
void Randomize(const uint32_t seed)
Randomize the generators.
return_t drawGaussian1D(const double mean, const double std)
Generate a normally distributed pseudo-random number.
CMatrixDouble cov(const MATRIX &v)
Computes the covariance matrix from a list of samples in an NxM matrix, where each row is a sample...
Definition: ops_matrices.h:149
void vectorRandomNormal(std::vector< T > &v_out, const T &mean=0, const T &std=1)
Generates a random vector with independent, normally distributed samples.
CRandomGenerator(const uint32_t seed)
Constructor for providing a custom random seed to initialize the PRNG.
size_type rows() const
Number of rows in the matrix.
size_type cols() const
Number of columns in the matrix.
ptrdiff_t random_generator_for_STL(ptrdiff_t i)
A random number generator for usage in STL algorithms expecting a function like this (eg...
uint64_t drawUniform64bit()
Returns a uniformly distributed pseudo-random number by joining two 32bit numbers from drawUniform32b...
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
void drawGaussianMultivariate(std::vector< T > &out_result, const MATRIX &cov, const std::vector< T > *mean=nullptr)
Generate multidimensional random samples according to a given covariance matrix.
void matrixRandomNormal(MAT &matrix, const double mean=0, const double std=1)
Fills the given matrix with independent, normally distributed samples.
void matrixRandomUni(MAT &matrix, const double unif_min=0, const double unif_max=1)
Fills the given matrix with independent, uniformly distributed samples.
void randomize()
Randomize the generators, based on std::random_device.
double mean(const CONTAINER &v)
Computes the mean value of a vector.
std::uniform_int_distribution< uint32_t > m_uint32
CRandomGenerator()
Default constructor: initialize random seed based on current time.
void vectorRandomUni(std::vector< T > &v_out, const T &unif_min=0, const T &unif_max=1)
Fills the given matrix with independent, uniformly distributed samples.
Generator_MT19937 m_MT19937
Data used internally by the MT19937 PRNG algorithm.
void randomNormalMultiDimensionalMany(const MATRIX &cov, size_t desiredSamples, std::vector< std::vector< T >> &ret, std::vector< T > *samplesLikelihoods=nullptr)
Generate a given number of multidimensional random samples according to a given covariance matrix...
void randomNormalMultiDimensional(const MATRIX &cov, std::vector< T > &out_result)
Generate multidimensional random samples according to a given covariance matrix.
void seed(const uint32_t seed)
CRandomGenerator & getRandomGenerator()
A static instance of a CRandomGenerator class, for use in single-thread applications.
void drawUniformVector(VEC &v, const double unif_min=0, const double unif_max=1)
Fills the given vector with independent, uniformly distributed samples.
void drawUniformUnsignedInt(uint64_t &ret_number)
double drawGaussian1D_normalized()
Generate a normalized (mean=0, std=1) normally distributed sample.



Page generated by Doxygen 1.8.14 for MRPT 2.0.2 Git: 9efc2a654 Mon Apr 6 11:24:47 2020 +0200 at lun abr 6 11:30:12 CEST 2020