Repository navigation
Expand file tree
/
Copy pathnp.random.cs
More file actions
237 lines (207 loc) · 7.99 KB
/
Copy pathnp.random.cs
File metadata and controls
237 lines (207 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
using System;
namespace NumSharp
{
/// <summary>
/// A class that serves as numpy.random.RandomState in python.
/// Uses MT19937 (Mersenne Twister) for NumPy-compatible random number generation.
/// </summary>
/// <remarks>https://numpy.org/doc/stable/reference/random/index.html</remarks>
[ModuleName("np.random")]
public partial class NumPyRandom
{
/// <summary>
/// The MT19937 bit generator (NumPy-compatible).
/// </summary>
protected internal MT19937 randomizer;
/// <summary>
/// Cached Gaussian value from Box-Muller transform.
/// NumPy caches the second value to maintain state reproducibility.
/// </summary>
private bool _hasGauss;
private double _gaussCache;
public int Seed { get; set; }
#region Constructors
protected internal NumPyRandom(MT19937 bitGenerator)
{
this.randomizer = bitGenerator;
}
protected internal NumPyRandom(NativeRandomState nativeRandomState)
{
set_state(nativeRandomState);
}
protected internal NumPyRandom(int seed)
{
Seed = seed;
randomizer = new MT19937(seed);
}
protected internal NumPyRandom()
{
randomizer = new MT19937();
}
#endregion
#region Gaussian
/// <summary>
/// Returns a random sample from the standard normal distribution (mean=0, std=1).
/// Uses the polar method (Marsaglia) matching NumPy's legacy RandomState exactly.
/// </summary>
/// <remarks>
/// NumPy's legacy RandomState uses the polar method (not Box-Muller) with caching.
/// The polar method generates two uniform values in [-1,1], rejects if outside unit circle,
/// then transforms to standard normal. The second value is cached.
///
/// This is critical for matching NumPy's randn() output exactly.
/// </remarks>
protected internal double NextGaussian()
{
// Return cached value if available (NumPy behavior)
if (_hasGauss)
{
_hasGauss = false;
return _gaussCache;
}
// Polar method (Marsaglia) - matches NumPy's random_standard_normal
double x, y, r2;
do
{
// Generate x, y uniform in [-1, 1]
x = 2.0 * randomizer.NextDouble() - 1.0;
y = 2.0 * randomizer.NextDouble() - 1.0;
r2 = x * x + y * y;
} while (r2 >= 1.0 || r2 == 0.0);
// Polar transform
double d = Math.Sqrt(-2.0 * Math.Log(r2) / r2);
// NumPy caches x*d and returns y*d first
_gaussCache = x * d;
_hasGauss = true;
// Return y*d (NumPy convention)
return y * d;
}
#endregion
#region RandomState
/// <summary>
/// Returns a new instance of <see cref="NumPyRandom"/>.
/// </summary>
public NumPyRandom RandomState()
{
return new NumPyRandom();
}
/// <summary>
/// Returns a new instance of <see cref="NumPyRandom"/>.
/// </summary>
public NumPyRandom RandomState(int seed)
{
return new NumPyRandom(seed);
}
/// <summary>
/// Returns a new instance of <see cref="NumPyRandom"/>.
/// </summary>
public NumPyRandom RandomState(NativeRandomState state)
{
return new NumPyRandom(state);
}
#endregion
/// <summary>
/// Seeds the generator with a uint value (full NumPy range).
/// It can be called again to re-seed the generator.
/// </summary>
/// <param name="seed">Seed value in range [0, 2^32-1].</param>
/// <remarks>
/// This uses the MT19937 algorithm matching NumPy exactly.
/// Same seed produces identical sequences to NumPy.
/// </remarks>
public void seed(uint seed)
{
Seed = (int)seed;
randomizer = new MT19937(seed);
// Clear Gaussian cache on reseed (NumPy behavior)
_hasGauss = false;
_gaussCache = 0.0;
}
/// <summary>
/// Seeds the generator with an int value.
/// Validates that seed is non-negative (NumPy behavior).
/// </summary>
/// <param name="seed">Seed value in range [0, 2^31-1].</param>
/// <exception cref="ValueError">If seed is negative.</exception>
/// <remarks>
/// NumPy accepts 0 to 2^32-1. Negative values throw:
/// "Seed must be between 0 and 2**32 - 1"
/// </remarks>
public void seed(int seed)
{
if (seed < 0)
throw new ValueError("Seed must be between 0 and 2**32 - 1");
this.seed((uint)seed);
}
/// <summary>
/// Seeds the generator with a long value.
/// Validates that seed is in range [0, 2^32-1] (NumPy behavior).
/// </summary>
/// <param name="seed">Seed value in range [0, 2^32-1].</param>
/// <exception cref="ValueError">If seed is out of range.</exception>
public void seed(long seed)
{
if (seed < 0 || seed > uint.MaxValue)
throw new ValueError("Seed must be between 0 and 2**32 - 1");
this.seed((uint)seed);
}
/// <summary>
/// Seeds the generator with a ulong value.
/// Validates that seed is in range [0, 2^32-1] (NumPy behavior).
/// </summary>
/// <param name="seed">Seed value in range [0, 2^32-1].</param>
/// <exception cref="ValueError">If seed is out of range.</exception>
public void seed(ulong seed)
{
if (seed > uint.MaxValue)
throw new ValueError("Seed must be between 0 and 2**32 - 1");
this.seed((uint)seed);
}
/// <summary>
/// Seeds the generator with an array of uint values.
/// Matches NumPy's init_by_array seeding.
/// </summary>
/// <param name="seed">Array of seed values.</param>
public void seed(uint[] seed)
{
if (seed == null || seed.Length == 0)
{
this.seed(0u);
return;
}
Seed = (int)seed[0];
randomizer = new MT19937();
randomizer.SeedByArray(seed);
_hasGauss = false;
_gaussCache = 0.0;
}
/// <summary>
/// Set the internal state of the generator from a <see cref="NativeRandomState"/>.
/// For use if one has reason to manually (re-)set the internal state of the pseudo-random number generating algorithm.
/// </summary>
/// <param name="state">The state to restore onto this <see cref="NumPyRandom"/></param>
public void set_state(NativeRandomState state)
{
if (state.Key == null || state.Key.Length != 624)
throw new ArgumentException("Invalid state: key array must be length 624");
if (randomizer == null)
randomizer = new MT19937();
randomizer.SetState(state.Key, state.Pos);
_hasGauss = state.HasGauss != 0;
_gaussCache = state.CachedGaussian;
}
/// <summary>
/// Return a <see cref="NativeRandomState"/> representing the internal state of the generator.
/// </summary>
/// <returns>The current state, including Gaussian cache.</returns>
public NativeRandomState get_state()
{
return new NativeRandomState(
key: (uint[])randomizer.Key.Clone(),
pos: randomizer.Pos,
hasGauss: _hasGauss ? 1 : 0,
cachedGaussian: _gaussCache
);
}
}
}