[Python] A python translation of `XorShift128Plus`

Discussão em 'Python' iniciado por Stack, Março 5, 2025.

  1. Stack

    Stack Membro Participativo

    I need to translate ILGPU's XorShift128Plus class into Python.

    First, I have written a unit test for the C# version:

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using ILGPU.Algorithms.Random;
    using System;

    namespace MyTests
    {
    [TestClass]
    public class XorShift128PlusTests
    {
    [TestMethod]
    public void TestInitialization()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    Assert.AreEqual(123456789UL, rng.State0);
    Assert.AreEqual(987654321UL, rng.State1);
    }

    [TestMethod]
    public void TestNextULong()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var actual = rng.NextULong();
    Assert.AreEqual(1035635210406904UL, actual);
    }

    [TestMethod]
    public void TestNextUInt()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var actual = rng.NextUInt();
    Assert.AreEqual(2631329311, actual);
    }

    [TestMethod]
    public void TestNext()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var actual = rng.Next();
    Assert.AreEqual(483845663, actual);
    }

    [TestMethod]
    public void TestNextLong()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var actual = rng.NextLong();
    Assert.AreEqual(1035635210406904L, actual);
    }

    [TestMethod]
    public void TestNextFloat()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var value = rng.NextFloat();
    var actual = value.Trim(6);
    var expected = 0.0001122839f.Trim(6);

    float tolerance = 1e-7f; // Small allowable error due to float limitations
    Assert.IsTrue(Math.Abs(expected - actual) < tolerance, $"Expected {expected}, but got {actual}");
    }



    [TestMethod]
    public void TestNextDouble()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var actual = rng.NextDouble();
    Assert.AreEqual(0.000112283794502565.Trim(15), actual.Trim(15));
    }

    [TestMethod]
    public void TestShiftPeriod()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var oldState0 = rng.State0;
    var oldState1 = rng.State1;
    rng.ShiftPeriod(10);
    Assert.IsTrue(rng.State0 != oldState0 || rng.State1 != oldState1);
    }

    [TestMethod]
    public void TestNextProvider()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var newRng = rng.NextProvider();
    Assert.IsInstanceOfType(newRng, typeof(XorShift128Plus));
    Assert.IsTrue(newRng.State0 != rng.State0 || newRng.State1 != rng.State1);
    }

    [TestMethod]
    public void TestCreateProvider()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    var newRng = rng.CreateProvider(new Random(42));
    Assert.IsInstanceOfType(newRng, typeof(XorShift128Plus));
    Assert.IsTrue(newRng.State0 > 0UL && newRng.State1 > 0UL);
    }

    [TestMethod]
    public void TestEquality()
    {
    var rng1 = new XorShift128Plus(123456789UL, 987654321UL);
    var rng2 = new XorShift128Plus(123456789UL, 987654321UL);
    var rng3 = new XorShift128Plus(111111111UL, 222222222UL);
    Assert.AreEqual(rng1, rng2);
    Assert.AreNotEqual(rng1, rng3);
    }

    [TestMethod]
    public void TestHash()
    {
    var rng1 = new XorShift128Plus(123456789UL, 987654321UL);
    var rng2 = new XorShift128Plus(123456789UL, 987654321UL);
    Assert.AreEqual(rng1.GetHashCode(), rng2.GetHashCode());
    }

    [TestMethod]
    public void TestToString()
    {
    var rng = new XorShift128Plus(123456789UL, 987654321UL);
    Assert.AreEqual("[123456789, 987654321]", rng.ToString());
    }
    }
    }

    public static class DoubleTrimmer
    {
    public static double Trim(this double value, int decimal_places)
    {
    return Math.Round(value, decimal_places);
    }

    public static double Clamp(this double value, double min, double max)
    {
    if (value < min) return min;
    if (value > max) return max;
    return value;
    }

    public static float Trim(this float value, int decimal_places)
    {
    float factor = (float)Math.Pow(10f, decimal_places);
    return (float)Math.Round(value * factor) / factor;
    }


    public static float Clamp(this float value, float min, float max)
    {
    if (value < min) return min;
    if (value > max) return max;
    return value;
    }

    public static decimal Trim(this decimal value, int decimal_places)
    {
    return Math.Round(value, decimal_places);
    }

    public static decimal Clamp(this decimal value, decimal min, decimal max)
    {
    if (value < min) return min;
    if (value > max) return max;
    return value;
    }
    }


    Then I have translated the C# class into a Python class:

    import random
    import struct

    class XorShift128Plus:
    """
    Implements a simple and fast xor-shift RNG.
    """

    def __init__(self, state0: int, state1: int):
    if state0 == 0 and state1 == 0:
    raise ValueError("State must not be zero")
    self.state0 = state0
    self.state1 = state1

    @staticmethod
    def create(seed_random: random.Random = None):
    if seed_random is None:
    seed_random = random.Random()

    state0 = (seed_random.randint(1, 0x7FFFFFFF) << 32) | seed_random.randint(0, 0xFFFFFFFF)
    state1 = (seed_random.randint(0, 0xFFFFFFFF) << 32) | seed_random.randint(0, 0xFFFFFFFF)
    return XorShift128Plus(state0, state1)

    def next_ulong(self) -> int:
    x = self.state0
    y = self.state1
    self.state0 = y
    x ^= (x << 23) & 0xFFFFFFFFFFFFFFFF
    self.state1 = x ^ y ^ ((x >> 17) & 0xFFFFFFFFFFFFFFFF) ^ ((y >> 26) & 0xFFFFFFFFFFFFFFFF)
    return (self.state1 + y) & 0xFFFFFFFFFFFFFFFF

    def next_uint(self) -> int:
    return self.next_ulong() & 0xFFFFFFFF

    def next(self) -> int:
    return self.next_uint() & 0x7FFFFFFF

    def next_long(self) -> int:
    return struct.unpack("q", struct.pack("Q", self.next_ulong()))[0]

    def next_float(self) -> float:
    return self.next_long() * (1.0 / (1 << 63))

    def next_double(self) -> float:
    return self.next_long() * (1.0 / (1 << 63))

    def shift_period(self, shift: int):
    local_rng = XorShift128Plus.create()
    for _ in range(shift):
    local_rng.next_ulong()
    self.state0 = local_rng.next_ulong()
    self.state1 = local_rng.next_ulong()

    def next_provider(self):
    return XorShift128Plus(self.next_ulong(), self.next_ulong())

    def create_provider(self, other_random):
    return XorShift128Plus(abs(other_random.next_long()) + 1, abs(other_random.next_long()) + 1)

    def __eq__(self, other):
    if not isinstance(other, XorShift128Plus):
    return False
    return self.state0 == other.state0 and self.state1 == other.state1

    def __hash__(self):
    return hash((self.state0, self.state1))

    def __repr__(self):
    return f"XorShift128Plus({self.state0}, {self.state1})"



    Then, I have translated the C# version of the unit-test into a PyTest module.

    import unittest
    from XorShift128Plus import XorShift128Plus


    class TestXorShift128Plus(unittest.TestCase):

    def test_initialization(self):
    rng = XorShift128Plus(123456789, 987654321)
    self.assertEqual(rng.state0, 123456789)
    self.assertEqual(rng.state1, 987654321)

    def test_next_ulong(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next_ulong()
    self.assertEqual(actual, 1035635210406904)

    def test_next_uint(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next_uint()
    self.assertEqual(actual, 2631329311)

    def test_next(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next()
    self.assertEqual(actual, 483845663)

    def test_next_long(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next_long()
    self.assertEqual(actual, 1035635210406904)

    def test_next_float(self):
    rng = XorShift128Plus(123456789, 987654321)
    value = rng.next_float()
    actual = round(value, 6)
    expected = round(0.0001122839, 6)

    print(f"Original: {value}")
    print(f"Rounded : {actual}")
    print(f"Expected: {expected}")

    tolerance = 1e-7 # Small allowable error due to float limitations
    self.assertTrue(abs(expected - actual) < tolerance, f"Expected {expected}, but got {actual}")

    def test_next_double(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next_double()
    self.assertEqual(round(actual, 15), round(0.000112283794502565, 15))

    def test_shift_period(self):
    rng = XorShift128Plus(123456789, 987654321)
    old_state0, old_state1 = rng.state0, rng.state1
    rng.shift_period(10)
    self.assertTrue(rng.state0 != old_state0 or rng.state1 != old_state1)

    def test_next_provider(self):
    rng = XorShift128Plus(123456789, 987654321)
    new_rng = rng.next_provider()
    self.assertIsInstance(new_rng, XorShift128Plus)
    self.assertTrue(new_rng.state0 != rng.state0 or new_rng.state1 != rng.state1)

    def test_create_provider(self):
    rng = XorShift128Plus(123456789, 987654321)
    new_rng = rng.create_provider(rng)
    self.assertIsInstance(new_rng, XorShift128Plus)
    self.assertTrue(new_rng.state0 > 0 and new_rng.state1 > 0)

    def test_equality(self):
    rng1 = XorShift128Plus(123456789, 987654321)
    rng2 = XorShift128Plus(123456789, 987654321)
    rng3 = XorShift128Plus(111111111, 222222222)
    self.assertEqual(rng1, rng2)
    self.assertNotEqual(rng1, rng3)

    def test_hash(self):
    rng1 = XorShift128Plus(123456789, 987654321)
    rng2 = XorShift128Plus(123456789, 987654321)
    self.assertEqual(hash(rng1), hash(rng2))

    def test_repr(self):
    rng = XorShift128Plus(123456789, 987654321)
    self.assertEqual(repr(rng), "XorShift128Plus(123456789, 987654321)")


    if __name__ == "__main__":
    unittest.main()



    Now, the issue is, Python's test_next() and test_next_uint() are failing:

    ....F

    483845663 != 483740664

    Expected :483740664
    Actual :483845663
    <Click to see difference>

    self = <RefPythonCode.unit_test_for_XorShift128Plus.TestXorShift128Plus testMethod=test_next>
    first = 483740664, second = 483845663, msg = None

    def _patched_equals(self, first, second, msg=None):
    try:
    > old(self, first, second, msg)
    E AssertionError: 483740664 != 483845663

    C:\Program Files\JetBrains\PyCharm 2018.3.7\helpers\pycharm\teamcity\diff_tools.py:32: AssertionError

    During handling of the above exception, another exception occurred:

    self = <RefPythonCode.unit_test_for_XorShift128Plus.TestXorShift128Plus testMethod=test_next>

    def test_next(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next()
    > self.assertEqual(actual, 483845663)

    unit_test_for_XorShift128Plus.py:25:

    ..Original: 0.00011228379450256478
    Rounded : 0.000112
    Expected: 0.000112
    ..F

    2631329311 != 2631224312

    Expected :2631224312
    Actual :2631329311
    <Click to see difference>

    self = <RefPythonCode.unit_test_for_XorShift128Plus.TestXorShift128Plus testMethod=test_next_uint>
    first = 2631224312, second = 2631329311, msg = None

    def _patched_equals(self, first, second, msg=None):
    try:
    > old(self, first, second, msg)
    E AssertionError: 2631224312 != 2631329311

    C:\Program Files\JetBrains\PyCharm 2018.3.7\helpers\pycharm\teamcity\diff_tools.py:32: AssertionError

    During handling of the above exception, another exception occurred:

    self = <RefPythonCode.unit_test_for_XorShift128Plus.TestXorShift128Plus testMethod=test_next_uint>

    def test_next_uint(self):
    rng = XorShift128Plus(123456789, 987654321)
    actual = rng.next_uint()
    > self.assertEqual(actual, 2631329311)

    unit_test_for_XorShift128Plus.py:20:

    ...


    How can I make the Python version match the output with the C# version?

    Continue reading...

Compartilhe esta Página