Skip to content

Commit

Permalink
Add .save property to XoshiroEngine, Xoroshiro128Plus, PermutedCongru…
Browse files Browse the repository at this point in the history
…entialEngine when stream type is oneseq or none, and applicable instances of PhobosRandom (#130)
  • Loading branch information
n8sh authored Mar 21, 2021
1 parent 62df39c commit 3b7fe56
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 26 deletions.
35 changes: 28 additions & 7 deletions source/mir/random/engine/pcg.d
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ alias pcg128_oneseq_once_insecure = PermutedCongruentialEngine!(rxs_m_xs_forward
+ Params:
+ output = should be one of the above functions.
+ Controls the output permutation of the state.
+ stream = one of unique, none, oneseq, specific.
+ streamType = one of unique, none, oneseq, specific.
+ Controls the Increment of the LCG portion of the PCG.
+ output_previous = if true then the pre-advance version (increasing instruction-level parallelism)
+ if false then use the post-advance version (reducing register pressure)
+ mult_ = optionally set the multiplier for the LCG.
+/
struct PermutedCongruentialEngine(alias output, // Output function
stream_t stream, // The stream type
stream_t streamType, // The stream type
bool output_previous,
mult_...) if (mult_.length <= 1)
{
Expand All @@ -114,13 +114,13 @@ struct PermutedCongruentialEngine(alias output, // Output function

@disable this(this);
@disable this();
static if (stream == stream_t.none)
static if (streamType == stream_t.none)
mixin no_stream!Uint;
else static if (stream == stream_t.unique)
else static if (streamType == stream_t.unique)
mixin unique_stream!Uint;
else static if (stream == stream_t.specific)
else static if (streamType == stream_t.specific)
mixin specific_stream!Uint;
else static if (stream == stream_t.oneseq)
else static if (streamType == stream_t.oneseq)
mixin oneseq_stream!Uint;
else
static assert(0);
Expand Down Expand Up @@ -226,7 +226,8 @@ public:
/++
Compatibility with $(LINK2 https://dlang.org/phobos/std_random.html#.isUniformRNG,
Phobos library methods). Presents this RNG as an InputRange.
Only available if `output_previous == true`.
Only available if `output_previous == true`. `save` is available when
`streamType` is `stream_t.none` or `stream_t.oneseq`.
The reason that this is enabled when `output_previous == true` is because
`front` can be implemented without additional cost.
Expand All @@ -246,6 +247,15 @@ public:
void popFront()() { state = bump(state); }
/// ditto
void seed()(Uint seed) { this.__ctor(seed); }
/// ditto
static if (streamType == stream_t.none || streamType == stream_t.oneseq)
@property typeof(this) save()() const
{
typeof(return) result = void;
foreach (i, e; this.tupleof)
result.tupleof[i] = e;
return result;
}
}
}

Expand All @@ -255,6 +265,7 @@ public:
//can be used as Phobos-style randoms.
import std.meta: AliasSeq;
import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
import std.range.primitives: isForwardRange;
foreach(RNG; AliasSeq!(pcg32, pcg32_oneseq, pcg32_fast,
pcg32_once_insecure, pcg32_oneseq_once_insecure,
pcg64_once_insecure, pcg64_oneseq_once_insecure))
Expand All @@ -263,12 +274,22 @@ public:
static assert(isSeedable!(RNG, RNG.Uint));
auto gen1 = RNG(1);
auto gen2 = RNG(2);
static if (__traits(hasMember, RNG, "save"))
{
static assert(isForwardRange!RNG);
auto gen3 = gen1.save;
}
gen2.seed(1);
assert(gen1 == gen2);
immutable a = gen1.front;
gen1.popFront();
assert(a == gen2());
assert(gen1.front == gen2());
static if (is(typeof(gen3)))
{
assert(a == gen3());
assert(gen1.front == gen3());
}
}

foreach(RNG; AliasSeq!(pcg32_unique))
Expand Down
18 changes: 0 additions & 18 deletions source/mir/random/engine/xorshift.d
Original file line number Diff line number Diff line change
Expand Up @@ -634,24 +634,6 @@ alias Xorshift64Star32 = XorshiftStarEngine!(ulong,64,12,25,27,26858216577363387
assert(x == 3988833114);
}

version(mir_random_test) version(unittest)
private void testIsPhobosStyleRandom(RNG)()
{
//Test RNG can be used as a Phobos-style random.
alias UIntType = typeof(RNG.init());
import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
static assert(isPhobosUniformRNG!(RNG, UIntType));
static assert(isSeedable!(RNG, UIntType));
auto gen1 = RNG(1);
auto gen2 = RNG(2);
gen2.seed(1);
assert(gen1 == gen2);
immutable a = gen1.front;
gen1.popFront();
assert(a == gen2());
assert(gen1.front == gen2());
}

// Verify that code rewriting has not changed algorithm results.
@nogc nothrow pure @safe version(mir_random_test) unittest
{
Expand Down
21 changes: 21 additions & 0 deletions source/mir/random/engine/xoshiro.d
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,21 @@ private void testIsPhobosStyleRandom(RNG)()
//Test RNG can be used as a Phobos-style random.
alias UIntType = typeof(RNG.init());
import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
import std.range: isForwardRange;
static assert(isPhobosUniformRNG!(RNG, UIntType));
static assert(isSeedable!(RNG, UIntType));
static assert(isForwardRange!RNG);
auto gen1 = RNG(1);
auto gen2 = RNG(2);
auto gen3 = gen1.save;
gen2.seed(1);
assert(gen1 == gen2);
immutable a = gen1.front;
gen1.popFront();
assert(a == gen2());
assert(gen1.front == gen2());
assert(a == gen3());
assert(gen1.front == gen3());
}

@nogc nothrow pure @safe version(mir_random_test) unittest
Expand Down Expand Up @@ -379,6 +384,14 @@ if ((is(UIntType == uint) || is(UIntType == ulong))
{
this.__ctor(x0);
}
/// ditto
@property typeof(this) save()() const
{
typeof(return) copy = void;
foreach (i, ref fld; this.tupleof)
copy.tupleof[i] = fld;
return copy;
}
}

/++
Expand Down Expand Up @@ -517,6 +530,14 @@ struct Xoroshiro128Plus
{
this.__ctor(x0);
}
/// ditto
@property typeof(this) save()() const
{
typeof(return) copy = void;
foreach (i, ref fld; this.tupleof)
copy.tupleof[i] = fld;
return copy;
}
}

///
Expand Down
55 changes: 54 additions & 1 deletion source/mir/random/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,13 @@ struct PhobosRandom(Engine) if (isRandomEngine!Engine && !isPhobosUniformRNG!Eng
_front = _engine.opCall();
}

/// Phobos-style random interface.
/++
Phobos-style random interface.
`save` is only available when the underlying `Engine` has no indirections
and has `pure @safe opCall()` and doesn't have an impure or `@system`
destructor.
+/
enum bool isUniformRandom = true;
/// ditto
enum Uint min = Uint.min;//Always normalized.
Expand All @@ -836,6 +842,38 @@ struct PhobosRandom(Engine) if (isRandomEngine!Engine && !isPhobosUniformRNG!Eng
_engine.__ctor(args);
_front = _engine.opCall();
}
/// ditto
static if (!hasIndirections!Engine && is(typeof(() pure @safe {
Engine e = Engine.init;
return e();
}())))
@property typeof(this) save()() const @trusted
{
import std.meta: allSatisfy;

typeof(return) copy = void;
static if (allSatisfy!(_isPOD, typeof(Engine.tupleof)))
{
// The advantage of fieldwise assignment instead of memcpy is that
// it works during CTFE.
foreach (i, ref e; this.tupleof)
{
static if (__traits(isPOD, typeof(e)))
copy.tupleof[i] = e;
else
foreach (i2, ref e2; e.tupleof)
copy.tupleof[i].tupleof[i2] = e2;
}
}
else
{
enum N = typeof(this).sizeof;
(cast(ubyte*) &copy)[0 .. N] = (cast(const ubyte*) &this)[0 .. N];
}
return copy;
}

private enum _isPOD(T) = __traits(isPOD, T);

/// Retain support for Mir-style random interface.
enum bool isRandomEngine = true;
Expand Down Expand Up @@ -867,12 +905,14 @@ template PhobosRandom(Engine) if (isRandomEngine!Engine && isPhobosUniformRNG!En
{
import mir.random.engine.xorshift: Xorshift1024StarPhi;
import std.random: isSeedable, isPhobosUniformRNG = isUniformRNG;
import std.range.primitives: isForwardRange;

alias RNG = PhobosRandom!Xorshift1024StarPhi;

//Phobos interface
static assert(isPhobosUniformRNG!(RNG, ulong));
static assert(isSeedable!(RNG, ulong));
static assert(isForwardRange!RNG);
//Mir interface
static assert(isSaturatedRandomEngine!RNG);
static assert(is(EngineReturnType!RNG == ulong));
Expand All @@ -889,3 +929,16 @@ template PhobosRandom(Engine) if (isRandomEngine!Engine && isPhobosUniformRNG!En
rng.seed(1);
assert(gen() == rng());
}

@nogc nothrow pure @safe version(mir_random_test) unittest
{
import mir.random.engine.xorshift: Xorshift1024StarPhi;

// Test .save works for PhobosRandom.
auto gen1 = PhobosRandom!Xorshift1024StarPhi(123456789);
auto gen2 = gen1.save;
const a = gen1();
const b = gen1();
assert(a == gen2());
assert(b == gen2());
}

0 comments on commit 3b7fe56

Please sign in to comment.