Skip to content

Commit

Permalink
random generators compatibility with parallel processing (#513)
Browse files Browse the repository at this point in the history
* new random generator

* merged from master

* send process ident

* Fortran ordering in r_in

* sync with patpass

* restore test_patpass

* python common and thread-specific RNGs

* C common and thread-specific RNGs

* merged from ;aster

* Initializers in atrandom.c

* removed the id field

* taken from master

* "inc" initializer must be odd
  • Loading branch information
lfarv authored Nov 29, 2022
1 parent cc12e09 commit b5614c0
Show file tree
Hide file tree
Showing 13 changed files with 521 additions and 237 deletions.
2 changes: 0 additions & 2 deletions atintegrators/VariableThinMPolePass.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ void VariableThinMPolePass(double *r, struct elem *Elem, double t0, int turn, in
struct elemab *ElemA = Elem->ElemA;
struct elemab *ElemB = Elem->ElemB;
double *ramps = Elem->Ramps;

init_seed(seed);

for(i=0;i<maxorder+1;i++){
pola[i]=get_pol(ElemA, ramps, mode, t, turn, seed, i, periodic);
Expand Down
179 changes: 136 additions & 43 deletions atintegrators/atrandom.c
Original file line number Diff line number Diff line change
@@ -1,24 +1,111 @@
/*
* PCG Random Number Generation for C.
*
* Copyright 2014 Melissa O'Neill <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* For additional information about the PCG random number generation scheme,
* including its license and other licensing options, visit
*
* http://www.pcg-random.org
*/
#include <math.h>
#include <stdint.h>

void init_seed(int seed){
static int initseed=1;
if(initseed)
{
srand(seed);
initseed=0;
}
#ifndef TWOPI
#define TWOPI 6.28318530717959
#endif /*TWOPI*/

/* initial RNG definitions */
#define AT_RNG_STATE 0x853c49e6748fea9bULL
#define AT_RNG_INC 0xda3e39cb94b95bdbULL

#define COMMON_PCG32_INITIALIZER { AT_RNG_STATE, AT_RNG_INC }
#define THREAD_PCG32_INITIALIZER { AT_RNG_STATE, 1ULL }

struct pcg_state_setseq_64 { // Internals are *Private*.
uint64_t state; // RNG state. All values are possible.
uint64_t inc; // Controls which RNG sequence (stream) is
// selected. Must *always* be odd.
};
typedef struct pcg_state_setseq_64 pcg32_random_t;

// If you *must* statically initialize it, here's one.

#define PCG32_INITIALIZER { AT_RNG_STATE, AT_RNG_INC }

static pcg32_random_t pcg32_global = PCG32_INITIALIZER;

// pcg32_random()
// pcg32_random_r(rng)
// Generate a uniformly distributed 32-bit random number

static uint32_t pcg32_random_r(pcg32_random_t* rng)
{
if (!rng) rng = &pcg32_global;
uint64_t oldstate;
#pragma omp atomic read
oldstate = rng->state;
#pragma omp atomic write
rng->state = oldstate * 6364136223846793005ULL + rng->inc;
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}

double atdrand(void) /* uniform distribution, (0..1] */
static uint32_t pcg32_random()
{
return pcg32_random_r(&pcg32_global);
}

// pcg32_srandom(initstate, initseq)
// pcg32_srandom_r(rng, initstate, initseq):
// Seed the rng. Specified in two parts, state initializer and a
// sequence selection constant (a.k.a. stream id)

static void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq)
{
return (rand()+1.0)/(RAND_MAX+1.0);
if (!rng) rng = &pcg32_global;
rng->state = 0U;
rng->inc = (initseq << 1u) | 1u;
pcg32_random_r(rng);
rng->state += initstate;
pcg32_random_r(rng);
}

double atrandn() /* gaussian distribution, sigma=1, mean=0 */
static void pcg32_srandom(uint64_t seed, uint64_t seq)
{
pcg32_srandom_r(&pcg32_global, seed, seq);
}

/* Functions for uniform, normal and Poisson distributions with
external state variable */

static double atrandd_r(pcg32_random_t* rng)
/* Uniform [0, 1] distribution */
{
return ldexp(pcg32_random_r(rng), -32);
}

static double atrandn_r(pcg32_random_t* rng)
/* gaussian distribution, sigma=1, mean=0 */
{
/* Marsaglia polar method: https://en.wikipedia.org/wiki/Marsaglia_polar_method */

static bool hasSpare = false;
static double spare;
static double u, v, s;
double u, v, s;

if (hasSpare) {
hasSpare = false;
Expand All @@ -27,46 +114,52 @@ double atrandn() /* gaussian distribution, sigma=1, mean=0 */

hasSpare = true;
do {
u = (rand() / (double) RAND_MAX) * 2.0 - 1.0;
v = (rand() / (double) RAND_MAX) * 2.0 - 1.0;
u = 2.0 * atrandd_r(rng) - 1.0;
v = 2.0 * atrandd_r(rng) - 1.0;
s = u * u + v * v;
}
while( (s >= 1.0) || (s == 0.0) );
while ((s >= 1.0) || (s == 0.0));
s = sqrt(-2.0 * log(s) / s);
spare = v * s;
return u * s;
}

int atrandp(double lamb) /* poisson distribution */
static int atrandp_r(pcg32_random_t* rng, double lamb)
/* poisson distribution */
{

int pk,k;
double l,p,u,u1,u2,twopi;

l = -lamb;
k = 0;
p = 0.0;
twopi = 4.0*asin(1.0);
pk=0;

if(lamb<11){

do{
k = k + 1;
u = atdrand();
p = p + log(u);
}while(p>l);

int pk;

if (lamb<11) {
double l = -lamb;
int k = 0;
double p = 0;
do {
k += 1;
p += log(atrandd_r(rng));
} while (p>l);
pk = k-1;

}else{
u1 = atdrand();
u2 = atdrand();
if(u1==0.0){
u1 = 1e-18;
};
pk = (int)floor((sqrt(-2.0*log(u1))*cos(twopi*u2))*sqrt(lamb)+lamb);
}

return pk;
else { /* Gaussian approximation */
pk = (int)floor(atrandn_r(rng)*sqrt(lamb)+lamb);
}

return pk;
}

/* Functions for uniform, normal and Poisson distributions with
internal state variable */

static inline double atrandd(void)
{
return atrandd_r(&pcg32_global);
}

static inline double atrandn(void)
{
return atrandn_r(&pcg32_global);
}

static inline int atrandp(double lamb)
{
return atrandp_r(&pcg32_global, lamb);
}
2 changes: 2 additions & 0 deletions atintegrators/attypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ struct parameters
int nbunch;
double *bunch_spos;
double *bunch_currents;
struct pcg_state_setseq_64 *common_rng;
struct pcg_state_setseq_64 *thread_rng;
};

#endif /*ATTYPES_H*/
7 changes: 7 additions & 0 deletions atmat/attrack/atpass.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#endif /*_OPENMP*/
#include "attypes.h"
#include "elempass.h"
#include "atrandom.c"

/* Get ready for R2018a C matrix API */
#ifndef mxGetDoubles
Expand Down Expand Up @@ -67,6 +68,10 @@ static track_function *integrator_list = NULL;
static pass_function *pass_list = NULL;
static int **field_numbers_ptr = NULL;

/* state buffers for RNGs */
static pcg32_random_t common_state = COMMON_PCG32_INITIALIZER;
static pcg32_random_t thread_state = THREAD_PCG32_INITIALIZER;

static struct LibraryListElement {
const char *MethodName;
LIBRARYHANDLETYPE LibraryHandle;
Expand Down Expand Up @@ -327,6 +332,8 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
}
#endif /*_OPENMP*/

param.common_rng = &common_state;
param.thread_rng = &thread_state;
param.energy = 0.0;
param.rest_energy = 0.0;
param.charge = -1.0;
Expand Down
7 changes: 4 additions & 3 deletions docs/p/api/at.tracking.atpass.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ at.tracking.atpass

.. autosummary::

isopenmp
ismpi
atpass
elempass
elempass
reset_rng
common_rng
thread_rng
70 changes: 45 additions & 25 deletions pyat/at.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <stdbool.h>
#include <math.h>
#include <float.h>
#include <atrandom.c>

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/ndarrayobject.h>
Expand Down Expand Up @@ -64,6 +65,10 @@ static char integrator_path[300];
static PyObject *particle_type;
static PyObject *element_type;

/* state buffers for RNGs */
static pcg32_random_t common_state = COMMON_PCG32_INITIALIZER;
static pcg32_random_t thread_state = THREAD_PCG32_INITIALIZER;

/* Directly copied from atpass.c */
static struct LibraryListElement {
const char *MethodName;
Expand Down Expand Up @@ -395,6 +400,8 @@ static PyObject *at_atpass(PyObject *self, PyObject *args, PyObject *kwargs) {
return PyErr_Format(PyExc_ValueError, "rin is not Fortran-aligned");
}

param.common_rng=&common_state;
param.thread_rng=&thread_state;
param.energy=0.0;
param.rest_energy=0.0;
param.charge=-1.0;
Expand Down Expand Up @@ -683,34 +690,40 @@ static PyObject *at_elempass(PyObject *self, PyObject *args, PyObject *kwargs)
return (PyObject *) rin;
}


static PyObject *isopenmp(PyObject *self)
static PyObject *reset_rng(PyObject *self, PyObject *args, PyObject *kwargs)
{
#ifdef _OPENMP
Py_RETURN_TRUE;
#else
Py_RETURN_FALSE;
#endif /*_OPENMP)*/
}
static char *kwlist[] = {"rank", "seed", NULL};
uint64_t rank = 0;
uint64_t seed = AT_RNG_STATE;

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|K$K", kwlist,
&rank, &seed)) {
return NULL;
}
pcg32_srandom_r(&common_state, seed, AT_RNG_INC);
pcg32_srandom_r(&thread_state, seed, rank);
Py_RETURN_NONE;
}

static PyObject *ismpi(PyObject *self)
static PyObject *common_rng(PyObject *self)
{
#ifdef MPI
Py_RETURN_TRUE;
#else
Py_RETURN_FALSE;
#endif /*MPI)*/
double drand = atrandd_r(&common_state);
return Py_BuildValue("d", drand);
}

static PyObject *thread_rng(PyObject *self)
{
double drand = atrandd_r(&thread_state);
return Py_BuildValue("d", drand);
}

/* Boilerplate to register methods. */
/* Method table */

static PyMethodDef AtMethods[] = {
{"atpass", (PyCFunction)at_atpass, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("atpass(line: Sequence[Element], rin, n_turns: int, refpts: Uint32_refs = [], "
PyDoc_STR("atpass(line: Sequence[Element], r_in, n_turns: int, refpts: Uint32_refs = [], "
"reuse: Optional[bool] = False, omp_num_threads: Optional[int] = 0)\n\n"
"Track input particles rin along line for nturns turns.\n"
"Track input particles r_in along line for nturns turns.\n"
"Record 6D phase space at elements corresponding to refpts for each turn.\n\n"
"Parameters:\n"
" line: list of elements\n"
Expand All @@ -732,8 +745,8 @@ static PyMethodDef AtMethods[] = {
":meta private:"
)},
{"elempass", (PyCFunction)at_elempass, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("elempass(element, rin)\n\n"
"Track input particles rin through a single element.\n\n"
PyDoc_STR("elempass(element, r_in)\n\n"
"Track input particles r_in through a single element.\n\n"
"Parameters:\n"
" element: AT element\n"
" rin: 6 x n_particles Fortran-ordered numpy array.\n"
Expand All @@ -743,13 +756,20 @@ static PyMethodDef AtMethods[] = {
" charge: particle charge [elementary charge]\n\n"
":meta private:"
)},
{"isopenmp", (PyCFunction)isopenmp, METH_NOARGS,
PyDoc_STR("isopenmp()\n\n"
"Return whether OpenMP is active.\n"
{"reset_rng", (PyCFunction)reset_rng, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("reset_rng(rank=0, seed=None)\n\n"
"Reset the *common* and *thread* random generators.\n\n"
"Parameters:\n"
" rank (int): thread identifier (for MPI and python multiprocessing)\n"
" seed (int): single seed for both generators\n"
)},
{"common_rng", (PyCFunction)common_rng, METH_NOARGS,
PyDoc_STR("common_rng()\n\n"
"Return a double from the *common* generator .\n"
)},
{"ismpi", (PyCFunction)ismpi, METH_NOARGS,
PyDoc_STR("ismpi()\n\n"
"Return whether MPI is active.\n"
{"thread_rng", (PyCFunction)thread_rng, METH_NOARGS,
PyDoc_STR("thread_rng()\n\n"
"Return a double from the *thread* generator .\n"
)},
{NULL, NULL, 0, NULL} /* Sentinel */
};
Expand Down
Loading

0 comments on commit b5614c0

Please sign in to comment.