GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
oct-rand.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2003-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <cassert>
31 #include <cstdint>
32 
33 #include <limits>
34 
35 #include "lo-error.h"
36 #include "lo-ieee.h"
37 #include "lo-mappers.h"
38 #include "lo-ranlib-proto.h"
39 #include "mach-info.h"
40 #include "oct-locbuf.h"
41 #include "oct-rand.h"
42 #include "oct-time.h"
43 #include "quit.h"
44 #include "randgamma.h"
45 #include "randmtzig.h"
46 #include "randpoisson.h"
47 #include "singleton-cleanup.h"
48 
50 
51 rand *rand::s_instance = nullptr;
52 
54  : m_current_distribution (uniform_dist), m_use_old_generators (false),
55  m_rand_states ()
56 {
57  initialize_ranlib_generators ();
58 
59  initialize_mersenne_twister ();
60 }
61 
62 bool
64 {
65  bool retval = true;
66 
67  if (! s_instance)
68  {
69  s_instance = new rand ();
70  singleton_cleanup_list::add (cleanup_instance);
71  }
72 
73  return retval;
74 }
75 
76 double
77 rand::do_seed ()
78 {
79  union d2i { double d; int32_t i[2]; };
80  union d2i u;
81 
83 
84  switch (ff)
85  {
87  F77_FUNC (getsd, GETSD) (u.i[1], u.i[0]);
88  break;
89 
90  default:
91  F77_FUNC (getsd, GETSD) (u.i[0], u.i[1]);
92  break;
93  }
94 
95  return u.d;
96 }
97 
98 static int32_t
99 force_to_fit_range (int32_t i, int32_t lo, int32_t hi)
100 {
101  assert (hi > lo && lo >= 0);
102 
103  i = (i > 0 ? i : -i);
104 
105  if (i < lo)
106  i = lo;
107  else if (i > hi)
108  i = i % hi;
109 
110  return i;
111 }
112 
113 void
114 rand::do_seed (double s)
115 {
116  m_use_old_generators = true;
117 
118  int i0, i1;
119  union d2i { double d; int32_t i[2]; };
120  union d2i u;
121  u.d = s;
122 
124 
125  switch (ff)
126  {
128  i1 = force_to_fit_range (u.i[0], 1, 2147483563);
129  i0 = force_to_fit_range (u.i[1], 1, 2147483399);
130  break;
131 
132  default:
133  i0 = force_to_fit_range (u.i[0], 1, 2147483563);
134  i1 = force_to_fit_range (u.i[1], 1, 2147483399);
135  break;
136  }
137 
138  F77_FUNC (setsd, SETSD) (i0, i1);
139 }
140 
141 void
142 rand::do_reset ()
143 {
144  m_use_old_generators = true;
145  initialize_ranlib_generators ();
146 }
147 
149 rand::do_state (const std::string& d)
150 {
151  return m_rand_states[d.empty () ? m_current_distribution : get_dist_id (d)];
152 }
153 
154 void
155 rand::do_state (const uint32NDArray& s, const std::string& d)
156 {
157  m_use_old_generators = false;
158 
159  int old_dist = m_current_distribution;
160 
161  int new_dist = (d.empty () ? m_current_distribution : get_dist_id (d));
162 
163  uint32NDArray saved_state;
164 
165  if (old_dist != new_dist)
166  saved_state = get_internal_state ();
167 
168  set_internal_state (s);
169 
170  m_rand_states[new_dist] = get_internal_state ();
171 
172  if (old_dist != new_dist)
173  m_rand_states[old_dist] = saved_state;
174 }
175 
176 void
177 rand::do_reset (const std::string& d)
178 {
179  m_use_old_generators = false;
180 
181  int old_dist = m_current_distribution;
182 
183  int new_dist = (d.empty () ? m_current_distribution : get_dist_id (d));
184 
185  uint32NDArray saved_state;
186 
187  if (old_dist != new_dist)
188  saved_state = get_internal_state ();
189 
191  m_rand_states[new_dist] = get_internal_state ();
192 
193  if (old_dist != new_dist)
194  m_rand_states[old_dist] = saved_state;
195 }
196 
197 std::string
198 rand::do_distribution ()
199 {
200  std::string retval;
201 
202  switch (m_current_distribution)
203  {
204  case uniform_dist:
205  retval = "uniform";
206  break;
207 
208  case normal_dist:
209  retval = "normal";
210  break;
211 
212  case expon_dist:
213  retval = "exponential";
214  break;
215 
216  case poisson_dist:
217  retval = "poisson";
218  break;
219 
220  case gamma_dist:
221  retval = "gamma";
222  break;
223 
224  default:
225  (*current_liboctave_error_handler)
226  ("rand: invalid distribution ID = %d", m_current_distribution);
227  break;
228  }
229 
230  return retval;
231 }
232 
233 void
234 rand::do_distribution (const std::string& d)
235 {
236  int id = get_dist_id (d);
237 
238  switch (id)
239  {
240  case uniform_dist:
242  break;
243 
244  case normal_dist:
246  break;
247 
248  case expon_dist:
250  break;
251 
252  case poisson_dist:
254  break;
255 
256  case gamma_dist:
258  break;
259 
260  default:
261  (*current_liboctave_error_handler)
262  ("rand: invalid distribution ID = %d", id);
263  break;
264  }
265 }
266 
267 void
268 rand::do_uniform_distribution ()
269 {
270  switch_to_generator (uniform_dist);
271 
272  F77_FUNC (setcgn, SETCGN) (uniform_dist);
273 }
274 
275 void
276 rand::do_normal_distribution ()
277 {
278  switch_to_generator (normal_dist);
279 
280  F77_FUNC (setcgn, SETCGN) (normal_dist);
281 }
282 
283 void
284 rand::do_exponential_distribution ()
285 {
286  switch_to_generator (expon_dist);
287 
288  F77_FUNC (setcgn, SETCGN) (expon_dist);
289 }
290 
291 void
292 rand::do_poisson_distribution ()
293 {
294  switch_to_generator (poisson_dist);
295 
296  F77_FUNC (setcgn, SETCGN) (poisson_dist);
297 }
298 
299 void
300 rand::do_gamma_distribution ()
301 {
302  switch_to_generator (gamma_dist);
303 
304  F77_FUNC (setcgn, SETCGN) (gamma_dist);
305 }
306 
307 template <>
308 OCTAVE_API double
309 rand::uniform<double> ()
310 {
311  double retval;
312 
313  if (m_use_old_generators)
314  F77_FUNC (dgenunf, DGENUNF) (0.0, 1.0, retval);
315  else
316  retval = rand_uniform<double> ();
317 
318  return retval;
319 }
320 
321 template <>
322 OCTAVE_API double
323 rand::normal<double> ()
324 {
325  double retval;
326 
327  if (m_use_old_generators)
328  F77_FUNC (dgennor, DGENNOR) (0.0, 1.0, retval);
329  else
330  retval = rand_normal<double> ();
331 
332  return retval;
333 }
334 
335 template <>
336 OCTAVE_API double
337 rand::exponential<double> ()
338 {
339  double retval;
340 
341  if (m_use_old_generators)
342  F77_FUNC (dgenexp, DGENEXP) (1.0, retval);
343  else
344  retval = rand_exponential<double> ();
345 
346  return retval;
347 }
348 
349 template <>
350 OCTAVE_API double
351 rand::poisson<double> (double a)
352 {
353  double retval;
354 
355  if (m_use_old_generators)
356  {
357  if (a < 0.0 || ! math::isfinite (a))
358  retval = numeric_limits<double>::NaN ();
359  else
360  {
361  // workaround bug in ignpoi, by calling with different Mu
362  F77_FUNC (dignpoi, DIGNPOI) (a + 1, retval);
363  F77_FUNC (dignpoi, DIGNPOI) (a, retval);
364  }
365  }
366  else
367  retval = rand_poisson<double> (a);
368 
369  return retval;
370 }
371 
372 template <>
373 OCTAVE_API double
374 rand::gamma<double> (double a)
375 {
376  double retval;
377 
378  if (m_use_old_generators)
379  {
380  if (a <= 0.0 || ! math::isfinite (a))
381  retval = numeric_limits<double>::NaN ();
382  else
383  F77_FUNC (dgengam, DGENGAM) (1.0, a, retval);
384  }
385  else
386  retval = rand_gamma<double> (a);
387 
388  return retval;
389 }
390 
391 template <>
392 OCTAVE_API float rand::uniform<float> ()
393 {
394  float retval;
395 
396  if (m_use_old_generators)
397  F77_FUNC (fgenunf, FGENUNF) (0.0f, 1.0f, retval);
398  else
399  retval = rand_uniform<float> ();
400 
401  return retval;
402 }
403 
404 template <>
405 OCTAVE_API float rand::normal<float> ()
406 {
407  float retval;
408 
409  if (m_use_old_generators)
410  F77_FUNC (fgennor, FGENNOR) (0.0f, 1.0f, retval);
411  else
412  retval = rand_normal<float> ();
413 
414  return retval;
415 }
416 
417 template <>
418 OCTAVE_API float rand::exponential<float> ()
419 {
420  float retval;
421 
422  if (m_use_old_generators)
423  F77_FUNC (fgenexp, FGENEXP) (1.0f, retval);
424  else
425  retval = rand_exponential<float> ();
426 
427  return retval;
428 }
429 
430 template <>
431 OCTAVE_API float rand::poisson<float> (float a)
432 {
433  float retval;
434 
435  if (m_use_old_generators)
436  {
437  if (a < 0.0f || ! math::isfinite (a))
438  retval = numeric_limits<float>::NaN ();
439  else
440  {
441  // workaround bug in ignpoi, by calling with different Mu
442  F77_FUNC (fignpoi, FIGNPOI) (a + 1, retval);
443  F77_FUNC (fignpoi, FIGNPOI) (a, retval);
444  }
445  }
446  else
447  {
448  // Keep poisson distribution in double precision for accuracy
449  retval = rand_poisson<double> (a);
450  }
451 
452  return retval;
453 }
454 
455 template <>
456 OCTAVE_API float rand::gamma<float> (float a)
457 {
458  float retval;
459 
460  if (m_use_old_generators)
461  {
462  if (a <= 0.0f || ! math::isfinite (a))
463  retval = numeric_limits<float>::NaN ();
464  else
465  F77_FUNC (fgengam, FGENGAM) (1.0f, a, retval);
466  }
467  else
468  retval = rand_gamma<float> (a);
469 
470  return retval;
471 }
472 
473 template <typename T>
474 T
475 rand::do_scalar (T a)
476 {
477  T retval = 0;
478 
479  switch (m_current_distribution)
480  {
481  case uniform_dist:
482  retval = uniform<T> ();
483  break;
484 
485  case normal_dist:
486  retval = normal<T> ();
487  break;
488 
489  case expon_dist:
490  retval = exponential<T> ();
491  break;
492 
493  case poisson_dist:
494  retval = poisson<T> (a);
495  break;
496 
497  case gamma_dist:
498  retval = gamma<T> (a);
499  break;
500 
501  default:
502  (*current_liboctave_error_handler)
503  ("rand: invalid distribution ID = %d", m_current_distribution);
504  break;
505  }
506 
507  if (! m_use_old_generators)
508  save_state ();
509 
510  return retval;
511 }
512 
513 template OCTAVE_API double rand::do_scalar<double> (double);
514 template OCTAVE_API float rand::do_scalar<float> (float);
515 
516 template <typename T>
517 Array<T>
518 rand::do_vector (octave_idx_type n, T a)
519 {
520  Array<T> retval;
521 
522  if (n > 0)
523  {
524  retval.clear (n, 1);
525 
526  fill (retval.numel (), retval.fortran_vec (), a);
527  }
528  else if (n < 0)
529  (*current_liboctave_error_handler) ("rand: invalid negative argument");
530 
531  return retval;
532 }
533 
534 template OCTAVE_API Array<double>
535 rand::do_vector<double> (octave_idx_type, double);
536 template OCTAVE_API Array<float>
537 rand::do_vector<float> (octave_idx_type, float);
538 
539 NDArray
540 rand::do_nd_array (const dim_vector& dims, double a)
541 {
542  NDArray retval;
543 
544  if (! dims.all_zero ())
545  {
546  retval.clear (dims);
547 
548  fill (retval.numel (), retval.fortran_vec (), a);
549  }
550 
551  return retval;
552 }
553 
555 rand::do_float_nd_array (const dim_vector& dims, float a)
556 {
557  FloatNDArray retval;
558 
559  if (! dims.all_zero ())
560  {
561  retval.clear (dims);
562 
563  fill (retval.numel (), retval.fortran_vec (), a);
564  }
565 
566  return retval;
567 }
568 
569 // Make the random number generator give us a different sequence every
570 // time we start octave unless we specifically set the seed. The
571 // technique used below will cycle monthly, but it does seem to
572 // work ok to give fairly different seeds each time Octave starts.
573 
574 void
575 rand::initialize_ranlib_generators ()
576 {
577  sys::localtime tm;
578  int stored_distribution = m_current_distribution;
579  F77_FUNC (setcgn, SETCGN) (uniform_dist);
580 
581  int hour = tm.hour () + 1;
582  int minute = tm.min () + 1;
583  int second = tm.sec () + 1;
584 
585  int32_t s0 = tm.mday () * hour * minute * second;
586  int32_t s1 = hour * minute * second;
587 
588  s0 = force_to_fit_range (s0, 1, 2147483563);
589  s1 = force_to_fit_range (s1, 1, 2147483399);
590 
591  F77_FUNC (setall, SETALL) (s0, s1);
592  F77_FUNC (setcgn, SETCGN) (stored_distribution);
593 }
594 
595 void
596 rand::initialize_mersenne_twister ()
597 {
598  uint32NDArray s;
599 
601  s = get_internal_state ();
602  m_rand_states[uniform_dist] = s;
603 
605  s = get_internal_state ();
606  m_rand_states[normal_dist] = s;
607 
609  s = get_internal_state ();
610  m_rand_states[expon_dist] = s;
611 
613  s = get_internal_state ();
614  m_rand_states[poisson_dist] = s;
615 
617  s = get_internal_state ();
618  m_rand_states[gamma_dist] = s;
619 
620  // All of the initializations above have messed with the internal state.
621  // Restore the state of the currently selected distribution.
622  set_internal_state (m_rand_states[m_current_distribution]);
623 }
624 
626 rand::get_internal_state ()
627 {
628  uint32NDArray s (dim_vector (MT_N + 1, 1));
629 
630  get_mersenne_twister_state (reinterpret_cast<uint32_t *> (s.fortran_vec ()));
631 
632  return s;
633 }
634 
635 void
636 rand::save_state ()
637 {
638  m_rand_states[m_current_distribution] = get_internal_state ();
639 }
640 
641 int
642 rand::get_dist_id (const std::string& d)
643 {
644  int retval = unknown_dist;
645 
646  if (d == "uniform" || d == "rand")
647  retval = uniform_dist;
648  else if (d == "normal" || d == "randn")
649  retval = normal_dist;
650  else if (d == "exponential" || d == "rande")
651  retval = expon_dist;
652  else if (d == "poisson" || d == "randp")
653  retval = poisson_dist;
654  else if (d == "gamma" || d == "randg")
655  retval = gamma_dist;
656  else
658  ("rand: invalid distribution '%s'", d.c_str ());
659 
660  return retval;
661 }
662 
663 void
664 rand::set_internal_state (const uint32NDArray& s)
665 {
666  octave_idx_type len = s.numel ();
667 
668  const uint32_t *sdata = reinterpret_cast <const uint32_t *> (s.data ());
669 
670  if (len == MT_N + 1 && sdata[MT_N] <= MT_N && sdata[MT_N] > 0)
672  else
673  init_mersenne_twister (sdata, len);
674 }
675 
676 void
677 rand::switch_to_generator (int dist)
678 {
679  if (dist != m_current_distribution)
680  {
681  m_current_distribution = dist;
682 
683  set_internal_state (m_rand_states[dist]);
684  }
685 }
686 
687 void
688 rand::fill (octave_idx_type len, double *v, double a)
689 {
690  if (len < 1)
691  return;
692 
693  switch (m_current_distribution)
694  {
695  case uniform_dist:
696  if (m_use_old_generators)
697  std::generate_n (v, len, []() { double x; F77_FUNC (dgenunf, DGENUNF) (0.0, 1.0, x); return x; });
698  else
700  break;
701 
702  case normal_dist:
703  if (m_use_old_generators)
704  std::generate_n (v, len, []() { double x; F77_FUNC (dgennor, DGENNOR) (0.0, 1.0, x); return x; });
705  else
707  break;
708 
709  case expon_dist:
710  if (m_use_old_generators)
711  std::generate_n (v, len, []() { double x; F77_FUNC (dgenexp, DGENEXP) (1.0, x); return x; });
712  else
714  break;
715 
716  case poisson_dist:
717  if (m_use_old_generators)
718  {
719  if (a < 0.0 || ! math::isfinite (a))
720  std::fill_n (v, len, numeric_limits<double>::NaN ());
721  else
722  {
723  // workaround bug in ignpoi, by calling with different Mu
724  double tmp;
725  F77_FUNC (dignpoi, DIGNPOI) (a + 1, tmp);
726  std::generate_n (v, len, [a]() { double x; F77_FUNC (dignpoi, DIGNPOI) (a, x); return x; });
727  }
728  }
729  else
730  rand_poisson<double> (a, len, v);
731  break;
732 
733  case gamma_dist:
734  if (m_use_old_generators)
735  {
736  if (a <= 0.0 || ! math::isfinite (a))
737  std::fill_n (v, len, numeric_limits<double>::NaN ());
738  else
739  std::generate_n (v, len, [a]() { double x; F77_FUNC (dgengam, DGENGAM) (1.0, a, x); return x; });
740  }
741  else
742  rand_gamma<double> (a, len, v);
743  break;
744 
745  default:
746  (*current_liboctave_error_handler)
747  ("rand: invalid distribution ID = %d", m_current_distribution);
748  break;
749  }
750 
751  save_state ();
752 
753  return;
754 }
755 
756 void
757 rand::fill (octave_idx_type len, float *v, float a)
758 {
759  if (len < 1)
760  return;
761 
762  switch (m_current_distribution)
763  {
764  case uniform_dist:
765  if (m_use_old_generators)
766  std::generate_n (v, len, []() { float x; F77_FUNC (fgenunf, FGENUNF) (0.0f, 1.0f, x); return x; });
767  else
769  break;
770 
771  case normal_dist:
772  if (m_use_old_generators)
773  std::generate_n (v, len, []() { float x; F77_FUNC (fgennor, FGENNOR) (0.0f, 1.0f, x); return x; });
774  else
775  rand_normal<float> (len, v);
776  break;
777 
778  case expon_dist:
779  if (m_use_old_generators)
780  std::generate_n (v, len, []() { float x; F77_FUNC (fgenexp, FGENEXP) (1.0f, x); return x; });
781  else
783  break;
784 
785  case poisson_dist:
786  if (m_use_old_generators)
787  {
788  if (a < 0.0f || ! math::isfinite (a))
789  std::fill_n (v, len, numeric_limits<float>::NaN ());
790  else
791  {
792  // workaround bug in ignpoi, by calling with different Mu
793  float tmp;
794  F77_FUNC (fignpoi, FIGNPOI) (a + 1, tmp);
795  std::generate_n (v, len, [a]() { float x; F77_FUNC (fignpoi, FIGNPOI) (a, x); return x; });
796  }
797  }
798  else
799  rand_poisson<float> (a, len, v);
800  break;
801 
802  case gamma_dist:
803  if (m_use_old_generators)
804  {
805  if (a <= 0.0f || ! math::isfinite (a))
806  std::fill_n (v, len, numeric_limits<float>::NaN ());
807  else
808  std::generate_n (v, len, [a]() { float x; F77_FUNC (fgengam, FGENGAM) (1.0f, a, x); return x; });
809  }
810  else
811  rand_gamma<float> (a, len, v);
812  break;
813 
814  default:
815  (*current_liboctave_error_handler)
816  ("rand: invalid distribution ID = %d", m_current_distribution);
817  break;
818  }
819 
820  save_state ();
821 
822  return;
823 }
824 
825 OCTAVE_END_NAMESPACE(octave)
#define NaN
Definition: Faddeeva.cc:261
T * fortran_vec()
Size of the specified dimension.
Definition: Array-base.cc:1764
void clear()
Definition: Array-base.cc:109
const T * data() const
Size of the specified dimension.
Definition: Array.h:663
octave_idx_type numel() const
Number of elements in the array.
Definition: Array.h:414
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
bool all_zero() const
Definition: dim-vector.h:300
Definition: oct-rand.h:45
static void uniform_distribution()
Definition: oct-rand.h:114
static void exponential_distribution()
Definition: oct-rand.h:126
static bool instance_ok()
Definition: oct-rand.cc:63
rand()
Definition: oct-rand.cc:53
static void gamma_distribution()
Definition: oct-rand.h:138
static void normal_distribution()
Definition: oct-rand.h:120
static void poisson_distribution()
Definition: oct-rand.h:132
static void add(fptr f)
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
subroutine getsd(iseed1, iseed2)
Definition: getsd.f:2
OCTAVE_NORETURN liboctave_error_handler current_liboctave_error_handler
Definition: lo-error.c:41
bool isfinite(double x)
Definition: lo-mappers.h:192
F77_RET_T const F77_DBLE const F77_DBLE F77_DBLE * d
F77_RET_T const F77_DBLE * x
float_format native_float_format()
Definition: mach-info.cc:67
float_format
Definition: mach-info.h:38
@ flt_fmt_ieee_big_endian
Definition: mach-info.h:44
#define OCTAVE_API
Definition: main.cc:55
octave_idx_type n
Definition: mx-inlines.cc:761
void get_mersenne_twister_state(uint32_t *save)
Definition: randmtzig.cc:323
double rand_uniform< double >()
Definition: randmtzig.cc:442
void set_mersenne_twister_state(const uint32_t *save)
Definition: randmtzig.cc:315
double rand_normal< double >()
Definition: randmtzig.cc:599
void init_mersenne_twister(const uint32_t s)
Definition: randmtzig.cc:201
float rand_normal< float >()
Definition: randmtzig.cc:806
float rand_exponential< float >()
Definition: randmtzig.cc:848
double rand_exponential< double >()
Definition: randmtzig.cc:665
float rand_uniform< float >()
Definition: randmtzig.cc:450
#define MT_N
Definition: randmtzig.h:72
template void rand_poisson< double >(double, octave_idx_type, double *)
template void rand_poisson< float >(float, octave_idx_type, float *)
subroutine setall(iseed1, iseed2)
Definition: setall.f:2
subroutine setsd(iseed1, iseed2)
Definition: setsd.f:2
subroutine fignpoi(mu, result)
Definition: wrap.f:65
subroutine fgennor(av, sd, result)
Definition: wrap.f:37
subroutine dgenunf(low, high, result)
Definition: wrap.f:9
subroutine fgenexp(av, result)
Definition: wrap.f:51
subroutine dgengam(a, r, result)
Definition: wrap.f:23
subroutine dgenexp(av, result)
Definition: wrap.f:16
subroutine fgengam(a, r, result)
Definition: wrap.f:58
subroutine fgenunf(low, high, result)
Definition: wrap.f:44
subroutine dgennor(av, sd, result)
Definition: wrap.f:2
subroutine dignpoi(mu, result)
Definition: wrap.f:30
F77_RET_T F77_FUNC(xerbla, XERBLA)(F77_CONST_CHAR_ARG_DEF(s_arg
F77_RET_T len
Definition: xerbla.cc:61