Skip to content

Commit e30ab4b

Browse files
authored
RNG-188: Add Philox4x32 and Philox4x64 random number generators (#191)
Add Philox4x32 and Philox4x64 random number generators.
1 parent 0d2faaa commit e30ab4b

13 files changed

Lines changed: 1114 additions & 6 deletions

File tree

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.rng.core.source32;
19+
20+
import org.apache.commons.rng.JumpableUniformRandomProvider;
21+
import org.apache.commons.rng.LongJumpableUniformRandomProvider;
22+
import org.apache.commons.rng.UniformRandomProvider;
23+
import org.apache.commons.rng.core.util.NumberFactory;
24+
25+
import java.util.Arrays;
26+
27+
/**
28+
* This class implements the Philox4x32 128-bit counter-based generator with 10 rounds.
29+
* Jumping in the sequence is essentially instantaneous. This generator provides subsequences for easy parallelization.
30+
*
31+
* @see <a href="https://www.thesalmons.org/john/random123/papers/random123sc11.pdf">Parallel Random Numbers: As Easy as 1,2,3</a>
32+
* @since 1.7
33+
*/
34+
public final class Philox4x32 extends IntProvider implements LongJumpableUniformRandomProvider {
35+
/**
36+
* Philox 32-bit mixing constant for counter 0.
37+
*/
38+
private static final int K_PHILOX_10_A = 0x9E3779B9;
39+
/**
40+
* Philox 32-bit mixing constant for counter 1.
41+
*/
42+
private static final int K_PHILOX_10_B = 0xBB67AE85;
43+
/**
44+
* Philox 32-bit constant for key 0.
45+
*/
46+
private static final int K_PHILOX_SA = 0xD2511F53;
47+
/**
48+
* Philox 32-bit constant for key 1.
49+
*/
50+
private static final int K_PHILOX_SB = 0xCD9E8D57;
51+
/**
52+
* Internal buffer size.
53+
*/
54+
private static final int PHILOX_BUFFER_SIZE = 4;
55+
/**
56+
* number of int variables.
57+
*/
58+
private static final int STATE_SIZE = 7;
59+
60+
/**
61+
* Counter 0.
62+
*/
63+
private int counter0;
64+
/**
65+
* Counter 1.
66+
*/
67+
private int counter1;
68+
/**
69+
* Counter 2.
70+
*/
71+
private int counter2;
72+
/**
73+
* Counter 3.
74+
*/
75+
private int counter3;
76+
/**
77+
* Output point.
78+
*/
79+
private int[] buffer = new int[PHILOX_BUFFER_SIZE]; // UINT4
80+
/**
81+
* Key low bits.
82+
*/
83+
private int key0;
84+
/**
85+
* Key high bits.
86+
*/
87+
private int key1;
88+
/**
89+
* State index: which output word is next (0..3).
90+
*/
91+
private int bufferPosition;
92+
93+
94+
/**
95+
* Copy constructor.
96+
*
97+
* @param source Source to copy.
98+
*/
99+
private Philox4x32(Philox4x32 source) {
100+
super(source);
101+
counter0 = source.counter0;
102+
counter1 = source.counter1;
103+
counter2 = source.counter2;
104+
counter3 = source.counter3;
105+
key0 = source.key0;
106+
key1 = source.key1;
107+
bufferPosition = source.bufferPosition;
108+
buffer = source.buffer.clone();
109+
}
110+
111+
/**
112+
* Creates a new instance with default seed. Subsequence and offset (or equivalently, the internal counter)
113+
* are set to zero.
114+
*/
115+
public Philox4x32() {
116+
this(67280421310721L);
117+
}
118+
119+
/**
120+
* Creates a new instance with a given seed. Subsequence and offset (or equivalently, the internal counter)
121+
* are set to zero.
122+
*
123+
* @param key the low 32 bits constitute the first int key of Philox,
124+
* and the high 32 bits constitute the second int key of Philox
125+
*/
126+
private Philox4x32(long key) {
127+
this(new int[]{(int) key, (int) (key >>> 32)});
128+
}
129+
130+
/**
131+
* Creates a new instance based on an array of int containing, key (first two ints) and
132+
* the counter (next 4 ints, low bits = first int). The counter is not scrambled and may
133+
* be used to create contiguous blocks with size a multiple of 4 ints. For example,
134+
* setting seed[2] = 1 is equivalent to start with seed[2]=0 and calling {@link #next()} 4 times.
135+
*
136+
* @param seed an array of size 6 defining key0,key1,counter0,counter1,counter2,counter3.
137+
* If the size is smaller, zero values are assumed.
138+
*/
139+
public Philox4x32(int[] seed) {
140+
final int[] input = seed.length < 6 ? Arrays.copyOf(seed, 6) : seed;
141+
key0 = input[0];
142+
key1 = input[1];
143+
counter0 = input[2];
144+
counter1 = input[3];
145+
counter2 = input[4];
146+
counter3 = input[5];
147+
bufferPosition = PHILOX_BUFFER_SIZE;
148+
}
149+
150+
/**
151+
* Fetch next integer from the buffer, or regenerate the buffer using 10 rounds.
152+
*
153+
* @return random integer
154+
*/
155+
@Override
156+
public int next() {
157+
final int p = bufferPosition;
158+
if (p < PHILOX_BUFFER_SIZE) {
159+
bufferPosition = p + 1;
160+
return buffer[p];
161+
}
162+
incrementCounter();
163+
rand10();
164+
bufferPosition = 1;
165+
return buffer[0];
166+
}
167+
168+
/**
169+
* Increment by one.
170+
*/
171+
private void incrementCounter() {
172+
counter0++;
173+
if (counter0 != 0) {
174+
return;
175+
}
176+
177+
counter1++;
178+
if (counter1 != 0) {
179+
return;
180+
}
181+
182+
counter2++;
183+
if (counter2 != 0) {
184+
return;
185+
}
186+
187+
counter3++;
188+
}
189+
190+
/**
191+
* Performs a single round of philox.
192+
*
193+
* @param ctr local counter, which will be updated after each call.
194+
* @param key0 key low bits
195+
* @param key1 key high bits
196+
*/
197+
private static void singleRound(int[] ctr, int key0, int key1) {
198+
long product = (K_PHILOX_SA & 0xFFFFFFFFL) * (ctr[0] & 0xFFFFFFFFL);
199+
final int hi0 = (int) (product >>> 32);
200+
final int lo0 = (int) product;
201+
product = (K_PHILOX_SB & 0xFFFFFFFFL) * (ctr[2] & 0xFFFFFFFFL);
202+
final int hi1 = (int) (product >>> 32);
203+
final int lo1 = (int) product;
204+
205+
ctr[0] = hi1 ^ ctr[1] ^ key0;
206+
ctr[1] = lo1;
207+
ctr[2] = hi0 ^ ctr[3] ^ key1;
208+
ctr[3] = lo0;
209+
}
210+
211+
/**
212+
* Perform 10 rounds, using counter0, counter1, counter2, counter3 as starting point.
213+
* It updates the buffer member variable, but no others.
214+
*/
215+
private void rand10() {
216+
buffer[0] = counter0;
217+
buffer[1] = counter1;
218+
buffer[2] = counter2;
219+
buffer[3] = counter3;
220+
221+
int k0 = key0;
222+
int k1 = key1;
223+
224+
//unrolled loop for performance
225+
singleRound(buffer, k0, k1);
226+
k0 += K_PHILOX_10_A;
227+
k1 += K_PHILOX_10_B;
228+
singleRound(buffer, k0, k1);
229+
k0 += K_PHILOX_10_A;
230+
k1 += K_PHILOX_10_B;
231+
singleRound(buffer, k0, k1);
232+
k0 += K_PHILOX_10_A;
233+
k1 += K_PHILOX_10_B;
234+
singleRound(buffer, k0, k1);
235+
k0 += K_PHILOX_10_A;
236+
k1 += K_PHILOX_10_B;
237+
singleRound(buffer, k0, k1);
238+
k0 += K_PHILOX_10_A;
239+
k1 += K_PHILOX_10_B;
240+
singleRound(buffer, k0, k1);
241+
k0 += K_PHILOX_10_A;
242+
k1 += K_PHILOX_10_B;
243+
singleRound(buffer, k0, k1);
244+
k0 += K_PHILOX_10_A;
245+
k1 += K_PHILOX_10_B;
246+
singleRound(buffer, k0, k1);
247+
k0 += K_PHILOX_10_A;
248+
k1 += K_PHILOX_10_B;
249+
singleRound(buffer, k0, k1);
250+
k0 += K_PHILOX_10_A;
251+
k1 += K_PHILOX_10_B;
252+
singleRound(buffer, k0, k1);
253+
}
254+
255+
256+
/**
257+
* {@inheritDoc}
258+
*
259+
* <p>Increments the subsequence by 1.</p>
260+
* <p>The jump size is the equivalent of 4*2<sup>96</sup> calls to
261+
* {@link UniformRandomProvider#nextInt() nextInt()}.
262+
*/
263+
@Override
264+
public JumpableUniformRandomProvider longJump() {
265+
final Philox4x32 copy = copy();
266+
counter3++;
267+
rand10();
268+
resetCachedState();
269+
return copy;
270+
}
271+
272+
/**
273+
* {@inheritDoc}
274+
*
275+
* <p>The jump size is the equivalent of 4*2<sup>64</sup>
276+
* calls to {@link UniformRandomProvider#nextInt() nextInt()}.
277+
*/
278+
@Override
279+
public UniformRandomProvider jump() {
280+
final Philox4x32 copy = copy();
281+
if (++counter2 == 0) {
282+
counter3++;
283+
}
284+
rand10();
285+
resetCachedState();
286+
return copy;
287+
}
288+
289+
/**
290+
* {@inheritDoc}
291+
*/
292+
@Override
293+
protected byte[] getStateInternal() {
294+
return composeStateInternal(NumberFactory.makeByteArray(
295+
new int[]{key0, key1, counter0, counter1, counter2, counter3, bufferPosition}),
296+
super.getStateInternal());
297+
}
298+
299+
/**
300+
* {@inheritDoc}
301+
*/
302+
@Override
303+
protected void setStateInternal(byte[] s) {
304+
final byte[][] c = splitStateInternal(s, STATE_SIZE * 4);
305+
final int[] state = NumberFactory.makeIntArray(c[0]);
306+
key0 = state[0];
307+
key1 = state[1];
308+
counter0 = state[2];
309+
counter1 = state[3];
310+
counter2 = state[4];
311+
counter3 = state[5];
312+
bufferPosition = state[6];
313+
super.setStateInternal(c[1]);
314+
rand10(); //to regenerate the internal buffer
315+
}
316+
317+
/**
318+
* Create a copy.
319+
*
320+
* @return the copy
321+
*/
322+
private Philox4x32 copy() {
323+
return new Philox4x32(this);
324+
}
325+
}

0 commit comments

Comments
 (0)