From 3b7fe5686ac2f81bc9b22b8c39ef563c6ee41f4b Mon Sep 17 00:00:00 2001 From: Nathan Sashihara <21227491+n8sh@users.noreply.github.com> Date: Sun, 21 Mar 2021 09:43:26 -0700 Subject: [PATCH] Add .save property to XoshiroEngine, Xoroshiro128Plus, PermutedCongruentialEngine when stream type is oneseq or none, and applicable instances of PhobosRandom (#130) --- source/mir/random/engine/pcg.d | 35 ++++++++++++++---- source/mir/random/engine/xorshift.d | 18 ---------- source/mir/random/engine/xoshiro.d | 21 +++++++++++ source/mir/random/package.d | 55 ++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 26 deletions(-) diff --git a/source/mir/random/engine/pcg.d b/source/mir/random/engine/pcg.d index ca04725d..2b8c013f 100644 --- a/source/mir/random/engine/pcg.d +++ b/source/mir/random/engine/pcg.d @@ -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) { @@ -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); @@ -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. @@ -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; + } } } @@ -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)) @@ -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)) diff --git a/source/mir/random/engine/xorshift.d b/source/mir/random/engine/xorshift.d index 0c3f58e1..ed0c1f1f 100644 --- a/source/mir/random/engine/xorshift.d +++ b/source/mir/random/engine/xorshift.d @@ -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 { diff --git a/source/mir/random/engine/xoshiro.d b/source/mir/random/engine/xoshiro.d index aa153697..a5b32711 100644 --- a/source/mir/random/engine/xoshiro.d +++ b/source/mir/random/engine/xoshiro.d @@ -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 @@ -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; + } } /++ @@ -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; + } } /// diff --git a/source/mir/random/package.d b/source/mir/random/package.d index 8b59652b..803a9acb 100644 --- a/source/mir/random/package.d +++ b/source/mir/random/package.d @@ -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. @@ -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*) ©)[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; @@ -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)); @@ -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()); +}