games_dice 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +0 -6
- data/Rakefile +12 -2
- data/ext/games_dice/extconf.rb +3 -0
- data/ext/games_dice/games_dice.c +12 -0
- data/ext/games_dice/probabilities.c +765 -0
- data/ext/games_dice/probabilities.h +47 -0
- data/games_dice.gemspec +3 -2
- data/lib/games_dice.rb +5 -1
- data/lib/games_dice/bunch.rb +2 -67
- data/lib/games_dice/probabilities.rb +138 -7
- data/lib/games_dice/version.rb +1 -1
- data/spec/helpers.rb +2 -2
- data/spec/probability_spec.rb +220 -144
- metadata +28 -8
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
|
+
require 'rake/extensiontask'
|
3
4
|
require "yard"
|
4
5
|
|
5
|
-
task :default => [:test]
|
6
|
-
|
7
6
|
desc "GamesDice unit tests"
|
8
7
|
RSpec::Core::RakeTask.new(:test) do |t|
|
9
8
|
t.pattern = "spec/*_spec.rb"
|
@@ -13,3 +12,14 @@ end
|
|
13
12
|
YARD::Rake::YardocTask.new do |t|
|
14
13
|
t.files = ['lib/**/*.rb']
|
15
14
|
end
|
15
|
+
|
16
|
+
gemspec = Gem::Specification.load('games_dice.gemspec')
|
17
|
+
Rake::ExtensionTask.new do |ext|
|
18
|
+
ext.name = 'games_dice'
|
19
|
+
ext.source_pattern = "*.{c,h}"
|
20
|
+
ext.ext_dir = 'ext/games_dice'
|
21
|
+
ext.lib_dir = 'lib/games_dice'
|
22
|
+
ext.gem_spec = gemspec
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => [:compile, :test]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
// ext/games_dice/games_dice.c
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
#include "probabilities.h"
|
5
|
+
|
6
|
+
// To hold the module object
|
7
|
+
VALUE GamesDice = Qnil;
|
8
|
+
|
9
|
+
void Init_games_dice() {
|
10
|
+
GamesDice = rb_define_module("GamesDice");
|
11
|
+
init_probabilities_class( GamesDice );
|
12
|
+
}
|
@@ -0,0 +1,765 @@
|
|
1
|
+
// ext/games_dice/probabilities.c
|
2
|
+
|
3
|
+
#include "probabilities.h"
|
4
|
+
|
5
|
+
// Ruby 1.8.7 compatibility patch
|
6
|
+
#ifndef DBL2NUM
|
7
|
+
#define DBL2NUM( dbl_val ) rb_float_new( dbl_val )
|
8
|
+
#endif
|
9
|
+
|
10
|
+
VALUE Probabilities = Qnil;
|
11
|
+
|
12
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
13
|
+
//
|
14
|
+
// General utils
|
15
|
+
//
|
16
|
+
|
17
|
+
static inline int max( int *a, int n ) {
|
18
|
+
int m = -1000000000;
|
19
|
+
int i;
|
20
|
+
for ( i=0; i < n; i++ ) {
|
21
|
+
m = a[i] > m ? a[i] : m;
|
22
|
+
}
|
23
|
+
return m;
|
24
|
+
}
|
25
|
+
|
26
|
+
static inline int min( int *a, int n ) {
|
27
|
+
int m = 1000000000;
|
28
|
+
int i;
|
29
|
+
for ( i=0; i < n; i++ ) {
|
30
|
+
m = a[i] < m ? a[i] : m;
|
31
|
+
}
|
32
|
+
return m;
|
33
|
+
}
|
34
|
+
|
35
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
36
|
+
//
|
37
|
+
// Quick factorials, that fit into unsigned longs . . . the size of this structure sets the
|
38
|
+
// maximum possible n in repeat_n_sum_k calculations
|
39
|
+
//
|
40
|
+
|
41
|
+
// There is no point calculating these, a cache of them is just fine.
|
42
|
+
static double nfact[171] = {
|
43
|
+
1.0, 1.0, 2.0, 6.0,
|
44
|
+
24.0, 120.0, 720.0, 5040.0,
|
45
|
+
40320.0, 362880.0, 3628800.0, 39916800.0,
|
46
|
+
479001600.0, 6227020800.0, 87178291200.0, 1307674368000.0,
|
47
|
+
20922789888000.0, 355687428096000.0, 6402373705728000.0, 121645100408832000.0,
|
48
|
+
2432902008176640000.0, 51090942171709440000.0, 1124000727777607700000.0, 25852016738884980000000.0,
|
49
|
+
620448401733239400000000.0, 15511210043330986000000000.0, 403291461126605650000000000.0, 10888869450418352000000000000.0,
|
50
|
+
304888344611713870000000000000.0, 8841761993739702000000000000000.0, 2.6525285981219107e+32, 8.222838654177922e+33,
|
51
|
+
2.631308369336935e+35, 8.683317618811886e+36, 2.9523279903960416e+38, 1.0333147966386145e+40,
|
52
|
+
3.7199332678990125e+41, 1.3763753091226346e+43, 5.230226174666011e+44, 2.0397882081197444e+46,
|
53
|
+
8.159152832478977e+47, 3.345252661316381e+49, 1.40500611775288e+51, 6.041526306337383e+52,
|
54
|
+
2.658271574788449e+54, 1.1962222086548019e+56, 5.502622159812089e+57, 2.5862324151116818e+59,
|
55
|
+
1.2413915592536073e+61, 6.082818640342675e+62, 3.0414093201713376e+64, 1.5511187532873822e+66,
|
56
|
+
8.065817517094388e+67, 4.2748832840600255e+69, 2.308436973392414e+71, 1.2696403353658276e+73,
|
57
|
+
7.109985878048635e+74, 4.0526919504877214e+76, 2.3505613312828785e+78, 1.3868311854568984e+80,
|
58
|
+
8.32098711274139e+81, 5.075802138772248e+83, 3.146997326038794e+85, 1.98260831540444e+87,
|
59
|
+
1.2688693218588417e+89, 8.247650592082472e+90, 5.443449390774431e+92, 3.647111091818868e+94,
|
60
|
+
2.4800355424368305e+96, 1.711224524281413e+98, 1.1978571669969892e+100, 8.504785885678623e+101,
|
61
|
+
6.1234458376886085e+103, 4.4701154615126844e+105, 3.307885441519386e+107, 2.48091408113954e+109,
|
62
|
+
1.8854947016660504e+111, 1.4518309202828587e+113, 1.1324281178206297e+115, 8.946182130782976e+116,
|
63
|
+
7.156945704626381e+118, 5.797126020747368e+120, 4.753643337012842e+122, 3.945523969720659e+124,
|
64
|
+
3.314240134565353e+126, 2.81710411438055e+128, 2.4227095383672734e+130, 2.107757298379528e+132,
|
65
|
+
1.8548264225739844e+134, 1.650795516090846e+136, 1.4857159644817615e+138, 1.352001527678403e+140,
|
66
|
+
1.2438414054641308e+142, 1.1567725070816416e+144, 1.087366156656743e+146, 1.032997848823906e+148,
|
67
|
+
9.916779348709496e+149, 9.619275968248212e+151, 9.426890448883248e+153, 9.332621544394415e+155,
|
68
|
+
9.332621544394415e+157, 9.42594775983836e+159, 9.614466715035127e+161, 9.90290071648618e+163,
|
69
|
+
1.0299016745145628e+166, 1.081396758240291e+168, 1.1462805637347084e+170, 1.226520203196138e+172,
|
70
|
+
1.324641819451829e+174, 1.4438595832024937e+176, 1.588245541522743e+178, 1.7629525510902446e+180,
|
71
|
+
1.974506857221074e+182, 2.2311927486598138e+184, 2.5435597334721877e+186, 2.925093693493016e+188,
|
72
|
+
3.393108684451898e+190, 3.969937160808721e+192, 4.684525849754291e+194, 5.574585761207606e+196,
|
73
|
+
6.689502913449127e+198, 8.094298525273444e+200, 9.875044200833601e+202, 1.214630436702533e+205,
|
74
|
+
1.506141741511141e+207, 1.882677176888926e+209, 2.372173242880047e+211, 3.0126600184576594e+213,
|
75
|
+
3.856204823625804e+215, 4.974504222477287e+217, 6.466855489220474e+219, 8.47158069087882e+221,
|
76
|
+
1.1182486511960043e+224, 1.4872707060906857e+226, 1.9929427461615188e+228, 2.6904727073180504e+230,
|
77
|
+
3.659042881952549e+232, 5.012888748274992e+234, 6.917786472619489e+236, 9.615723196941089e+238,
|
78
|
+
1.3462012475717526e+241, 1.898143759076171e+243, 2.695364137888163e+245, 3.854370717180073e+247,
|
79
|
+
5.5502938327393044e+249, 8.047926057471992e+251, 1.1749972043909107e+254, 1.727245890454639e+256,
|
80
|
+
2.5563239178728654e+258, 3.80892263763057e+260, 5.713383956445855e+262, 8.62720977423324e+264,
|
81
|
+
1.3113358856834524e+267, 2.0063439050956823e+269, 3.0897696138473508e+271, 4.789142901463394e+273,
|
82
|
+
7.471062926282894e+275, 1.1729568794264145e+278, 1.853271869493735e+280, 2.9467022724950384e+282,
|
83
|
+
4.7147236359920616e+284, 7.590705053947219e+286, 1.2296942187394494e+289, 2.0044015765453026e+291,
|
84
|
+
3.287218585534296e+293, 5.423910666131589e+295, 9.003691705778438e+297, 1.503616514864999e+300,
|
85
|
+
2.5260757449731984e+302, 4.269068009004705e+304, 7.257415615307999e+306 };
|
86
|
+
|
87
|
+
static double num_arrangements( int *args, int nargs ) {
|
88
|
+
int sum = 0;
|
89
|
+
double div_by = 1.0;
|
90
|
+
int i;
|
91
|
+
for ( i = 0; i < nargs; i++ ) {
|
92
|
+
sum += args[i];
|
93
|
+
if ( sum > 170 ) {
|
94
|
+
rb_raise( rb_eRuntimeError, "Too many dice to calculate numbers of arrangements" );
|
95
|
+
}
|
96
|
+
div_by *= nfact[ args[i] ];
|
97
|
+
}
|
98
|
+
return nfact[ sum ] / div_by;
|
99
|
+
}
|
100
|
+
|
101
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
102
|
+
//
|
103
|
+
// Probability List basics - create, delete, copy
|
104
|
+
//
|
105
|
+
|
106
|
+
static ProbabilityList *create_probability_list() {
|
107
|
+
ProbabilityList *pl;
|
108
|
+
pl = malloc (sizeof(ProbabilityList));
|
109
|
+
if ( pl == NULL ) {
|
110
|
+
rb_raise(rb_eRuntimeError, "Could not allocate memory for Probabilities");
|
111
|
+
}
|
112
|
+
pl->probs = NULL;
|
113
|
+
pl->cumulative = NULL;
|
114
|
+
pl->slots = 0;
|
115
|
+
pl->offset = 0;
|
116
|
+
return pl;
|
117
|
+
}
|
118
|
+
|
119
|
+
static void destroy_probability_list( ProbabilityList *pl ) {
|
120
|
+
xfree( pl->cumulative );
|
121
|
+
xfree( pl->probs );
|
122
|
+
xfree( pl );
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
|
126
|
+
static double *alloc_probs( ProbabilityList *pl, int slots ) {
|
127
|
+
if ( slots < 1 || slots > 1000000 ) {
|
128
|
+
rb_raise(rb_eArgError, "Bad number of probability slots");
|
129
|
+
}
|
130
|
+
pl->slots = slots;
|
131
|
+
|
132
|
+
double *pr = ALLOC_N( double, slots );
|
133
|
+
pl->probs = pr;
|
134
|
+
|
135
|
+
pl->cumulative = ALLOC_N( double, slots );
|
136
|
+
|
137
|
+
return pr;
|
138
|
+
}
|
139
|
+
|
140
|
+
static double calc_cumulative( ProbabilityList *pl ) {
|
141
|
+
double *c = pl->cumulative;
|
142
|
+
double *pr = pl->probs;
|
143
|
+
int i;
|
144
|
+
double t = 0.0;
|
145
|
+
for(i=0; i < pl->slots; i++) {
|
146
|
+
t += pr[i];
|
147
|
+
c[i] = t;
|
148
|
+
}
|
149
|
+
return t;
|
150
|
+
}
|
151
|
+
|
152
|
+
static double *alloc_probs_iv( ProbabilityList *pl, int slots, double iv ) {
|
153
|
+
if ( iv < 0.0 || iv > 1.0 ) {
|
154
|
+
rb_raise(rb_eArgError, "Bad single probability value");
|
155
|
+
}
|
156
|
+
double *pr = alloc_probs( pl, slots );
|
157
|
+
int i;
|
158
|
+
for(i=0; i<slots; i++) {
|
159
|
+
pr[i] = iv;
|
160
|
+
}
|
161
|
+
calc_cumulative( pl );
|
162
|
+
return pr;
|
163
|
+
}
|
164
|
+
|
165
|
+
static ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
|
166
|
+
ProbabilityList *pl = create_probability_list();
|
167
|
+
double *pr = alloc_probs( pl, orig->slots );
|
168
|
+
pl->offset = orig->offset;
|
169
|
+
memcpy( pr, orig->probs, orig->slots * sizeof(double) );
|
170
|
+
memcpy( pl->cumulative, orig->cumulative, orig->slots * sizeof(double) );
|
171
|
+
return pl;
|
172
|
+
}
|
173
|
+
|
174
|
+
static inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
|
175
|
+
ProbabilityList *pl = create_probability_list();
|
176
|
+
alloc_probs_iv( pl, nslots, iv );
|
177
|
+
pl->offset = o;
|
178
|
+
return pl;
|
179
|
+
}
|
180
|
+
|
181
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
182
|
+
//
|
183
|
+
// Probability List core "native" methods
|
184
|
+
//
|
185
|
+
|
186
|
+
static inline int pl_min( ProbabilityList *pl ) {
|
187
|
+
return pl->offset;
|
188
|
+
}
|
189
|
+
|
190
|
+
static inline int pl_max( ProbabilityList *pl ) {
|
191
|
+
return pl->offset + pl->slots - 1;
|
192
|
+
}
|
193
|
+
|
194
|
+
static ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *pl_b ) {
|
195
|
+
int s = pl_a->slots + pl_b->slots - 1;
|
196
|
+
int o = pl_a->offset + pl_b->offset;
|
197
|
+
int i,j;
|
198
|
+
|
199
|
+
ProbabilityList *pl = create_probability_list();
|
200
|
+
pl->offset = o;
|
201
|
+
double *pr = alloc_probs_iv( pl, s, 0.0 );
|
202
|
+
for ( i=0; i < pl_a->slots; i++ ) { for ( j=0; j < pl_b->slots; j++ ) {
|
203
|
+
pr[ i + j ] += (pl_a->probs)[i] * (pl_b->probs)[j];
|
204
|
+
} }
|
205
|
+
calc_cumulative( pl );
|
206
|
+
return pl;
|
207
|
+
}
|
208
|
+
|
209
|
+
static ProbabilityList *pl_add_distributions_mult( int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b ) {
|
210
|
+
int pts[4] = {
|
211
|
+
mul_a * pl_min( pl_a ) + mul_b * pl_min( pl_b ),
|
212
|
+
mul_a * pl_max( pl_a ) + mul_b * pl_min( pl_b ),
|
213
|
+
mul_a * pl_min( pl_a ) + mul_b * pl_max( pl_b ),
|
214
|
+
mul_a * pl_max( pl_a ) + mul_b * pl_max( pl_b ) };
|
215
|
+
|
216
|
+
int combined_min = min( pts, 4 );
|
217
|
+
int combined_max = max( pts, 4 );
|
218
|
+
int s = 1 + combined_max - combined_min;
|
219
|
+
|
220
|
+
ProbabilityList *pl = create_probability_list();
|
221
|
+
pl->offset = combined_min;
|
222
|
+
double *pr = alloc_probs_iv( pl, s, 0.0 );
|
223
|
+
int i,j;
|
224
|
+
for ( i=0; i < pl_a->slots; i++ ) { for ( j=0; j < pl_b->slots; j++ ) {
|
225
|
+
int k = mul_a * (i + pl_a->offset) + mul_b * (j + pl_b->offset) - combined_min;
|
226
|
+
pr[ i + j ] += (pl_a->probs)[i] * (pl_b->probs)[j];
|
227
|
+
} }
|
228
|
+
calc_cumulative( pl );
|
229
|
+
return pl;
|
230
|
+
}
|
231
|
+
|
232
|
+
static inline double pl_p_eql( ProbabilityList *pl, int target ) {
|
233
|
+
int idx = target - pl->offset;
|
234
|
+
if ( idx < 0 || idx >= pl->slots ) {
|
235
|
+
return 0.0;
|
236
|
+
}
|
237
|
+
return (pl->probs)[idx];
|
238
|
+
}
|
239
|
+
|
240
|
+
static inline double pl_p_gt( ProbabilityList *pl, int target ) {
|
241
|
+
return 1.0 - pl_p_le( pl, target );
|
242
|
+
}
|
243
|
+
|
244
|
+
static inline double pl_p_lt( ProbabilityList *pl, int target ) {
|
245
|
+
return pl_p_le( pl, target - 1 );
|
246
|
+
}
|
247
|
+
|
248
|
+
static inline double pl_p_le( ProbabilityList *pl, int target ) {
|
249
|
+
int idx = target - pl->offset;
|
250
|
+
if ( idx < 0 ) {
|
251
|
+
return 0.0;
|
252
|
+
}
|
253
|
+
if ( idx >= pl->slots - 1 ) {
|
254
|
+
return 1.0;
|
255
|
+
}
|
256
|
+
return (pl->cumulative)[idx];
|
257
|
+
}
|
258
|
+
|
259
|
+
static inline double pl_p_ge( ProbabilityList *pl, int target ) {
|
260
|
+
return 1.0 - pl_p_le( pl, target - 1 );
|
261
|
+
}
|
262
|
+
|
263
|
+
static inline double pl_expected( ProbabilityList *pl ) {
|
264
|
+
double t = 0.0;
|
265
|
+
int o = pl->offset;
|
266
|
+
int s = pl->slots;
|
267
|
+
double *pr = pl->probs;
|
268
|
+
int i;
|
269
|
+
for ( i = 0; i < s ; i++ ) {
|
270
|
+
t += ( i + o ) * pr[i];
|
271
|
+
}
|
272
|
+
return t;
|
273
|
+
}
|
274
|
+
|
275
|
+
static ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
|
276
|
+
int m = pl_min( pl );
|
277
|
+
if ( m > target ) {
|
278
|
+
target = m;
|
279
|
+
}
|
280
|
+
double p = pl_p_ge( pl, target );
|
281
|
+
if ( p <= 0.0 ) {
|
282
|
+
rb_raise( rb_eRuntimeError, "Cannot calculate given probabilities, divide by zero" );
|
283
|
+
}
|
284
|
+
double mult = 1.0/p;
|
285
|
+
int s = pl->slots + pl->offset - target;
|
286
|
+
double *pr = pl->probs;
|
287
|
+
|
288
|
+
ProbabilityList *new_pl = create_probability_list();
|
289
|
+
new_pl->offset = target;
|
290
|
+
double *new_pr = alloc_probs( new_pl, s );
|
291
|
+
int o = target - pl->offset;
|
292
|
+
int i;
|
293
|
+
for ( i = 0; i < s; i++ ) {
|
294
|
+
new_pr[i] = pr[o + i] * mult;
|
295
|
+
}
|
296
|
+
calc_cumulative( new_pl );
|
297
|
+
return new_pl;
|
298
|
+
}
|
299
|
+
|
300
|
+
static ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
|
301
|
+
int m = pl_max( pl );
|
302
|
+
if ( m < target ) {
|
303
|
+
target = m;
|
304
|
+
}
|
305
|
+
double p = pl_p_le( pl, target );
|
306
|
+
if ( p <= 0.0 ) {
|
307
|
+
rb_raise( rb_eRuntimeError, "Cannot calculate given probabilities, divide by zero" );
|
308
|
+
}
|
309
|
+
double mult = 1.0/p;
|
310
|
+
int s = target - pl->offset + 1;
|
311
|
+
double *pr = pl->probs;
|
312
|
+
|
313
|
+
ProbabilityList *new_pl = create_probability_list();
|
314
|
+
new_pl->offset = pl->offset;
|
315
|
+
double *new_pr = alloc_probs( new_pl, s );
|
316
|
+
int i;
|
317
|
+
for ( i = 0; i < s; i++ ) {
|
318
|
+
new_pr[i] = pr[i] * mult;
|
319
|
+
}
|
320
|
+
calc_cumulative( new_pl );
|
321
|
+
return new_pl;
|
322
|
+
}
|
323
|
+
|
324
|
+
static ProbabilityList *pl_repeat_sum( ProbabilityList *pl, int n ) {
|
325
|
+
if ( n < 1 ) {
|
326
|
+
rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum when n < 1" );
|
327
|
+
}
|
328
|
+
if ( n * pl->slots - n > 1000000 ) {
|
329
|
+
rb_raise( rb_eRuntimeError, "Too many probability slots" );
|
330
|
+
}
|
331
|
+
|
332
|
+
ProbabilityList *pd_power = copy_probability_list( pl );
|
333
|
+
ProbabilityList *pd_result = NULL;
|
334
|
+
ProbabilityList *pd_next = NULL;
|
335
|
+
int power = 1;
|
336
|
+
while ( 1 ) {
|
337
|
+
if ( power & n ) {
|
338
|
+
if ( pd_result ) {
|
339
|
+
pd_next = pl_add_distributions( pd_result, pd_power );
|
340
|
+
destroy_probability_list( pd_result );
|
341
|
+
pd_result = pd_next;
|
342
|
+
} else {
|
343
|
+
pd_result = copy_probability_list( pd_power );
|
344
|
+
}
|
345
|
+
}
|
346
|
+
power = power << 1;
|
347
|
+
if ( power > n ) break;
|
348
|
+
pd_next = pl_add_distributions( pd_power, pd_power );
|
349
|
+
destroy_probability_list( pd_power );
|
350
|
+
pd_power = pd_next;
|
351
|
+
}
|
352
|
+
destroy_probability_list( pd_power );
|
353
|
+
|
354
|
+
return pd_result;
|
355
|
+
}
|
356
|
+
|
357
|
+
// Assigns { p_rejected, p_maybe, p_kept } to buffer
|
358
|
+
static void calc_p_table( ProbabilityList *pl, int q, int kbest, double *buffer ) {
|
359
|
+
if ( kbest ) {
|
360
|
+
buffer[2] = pl_p_gt( pl, q );
|
361
|
+
buffer[1] = pl_p_eql( pl, q );
|
362
|
+
buffer[0] = pl_p_lt( pl, q );
|
363
|
+
} else {
|
364
|
+
buffer[2] = pl_p_lt( pl, q );
|
365
|
+
buffer[1] = pl_p_eql( pl, q );
|
366
|
+
buffer[0] = pl_p_gt( pl, q );
|
367
|
+
}
|
368
|
+
return;
|
369
|
+
}
|
370
|
+
|
371
|
+
// Assigns a list of pl variants to a buffer
|
372
|
+
static void calc_keep_distributions( ProbabilityList *pl, int k, int q, int kbest, ProbabilityList **pl_array ) {
|
373
|
+
// Init array
|
374
|
+
int n;
|
375
|
+
for ( n=0; n<k; n++) { pl_array[n] = NULL; }
|
376
|
+
pl_array[0] = new_basic_pl( 1, 1.0, q * k );
|
377
|
+
ProbabilityList *pl_kd;
|
378
|
+
|
379
|
+
if ( kbest ) {
|
380
|
+
if ( pl_p_gt( pl, q ) > 0.0 && k > 1 ) {
|
381
|
+
pl_kd = pl_given_ge( pl, q + 1 );
|
382
|
+
for ( n = 1; n < k; n++ ) {
|
383
|
+
pl_array[n] = pl_repeat_sum( pl_kd, n );
|
384
|
+
(pl_array[n])->offset += q * ( k - n );
|
385
|
+
}
|
386
|
+
}
|
387
|
+
} else {
|
388
|
+
if ( pl_p_lt( pl, q ) > 0.0 && k > 1 ) {
|
389
|
+
pl_kd = pl_given_le( pl, q - 1 );
|
390
|
+
for ( n = 1; n < k; n++ ) {
|
391
|
+
pl_array[n] = pl_repeat_sum( pl_kd, n );
|
392
|
+
(pl_array[n])->offset += q * ( k - n );
|
393
|
+
}
|
394
|
+
}
|
395
|
+
}
|
396
|
+
|
397
|
+
return;
|
398
|
+
}
|
399
|
+
|
400
|
+
static inline void clear_pl_array( int k, ProbabilityList **pl_array ) {
|
401
|
+
int n;
|
402
|
+
for ( n=0; n<k; n++) {
|
403
|
+
if ( pl_array[n] != NULL ) {
|
404
|
+
destroy_probability_list( pl_array[n] );
|
405
|
+
}
|
406
|
+
}
|
407
|
+
return;
|
408
|
+
}
|
409
|
+
|
410
|
+
static ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest ) {
|
411
|
+
if ( n < 1 ) {
|
412
|
+
rb_raise( rb_eRuntimeError, "Cannot calculate repeat_n_sum_k when n < 1" );
|
413
|
+
}
|
414
|
+
if ( k < 1 ) {
|
415
|
+
rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum_k when k < 1" );
|
416
|
+
}
|
417
|
+
if ( k >= n ) {
|
418
|
+
return pl_repeat_sum( pl, n );
|
419
|
+
}
|
420
|
+
if ( k * pl->slots - k >= 1000000 ) {
|
421
|
+
rb_raise( rb_eRuntimeError, "Too many probability slots" );
|
422
|
+
}
|
423
|
+
if ( n > 170 ) {
|
424
|
+
rb_raise( rb_eRuntimeError, "Too many dice to calculate combinations" );
|
425
|
+
}
|
426
|
+
|
427
|
+
// Init target
|
428
|
+
ProbabilityList *pl_result = create_probability_list();
|
429
|
+
double *pr = alloc_probs_iv( pl_result, 1 + k * (pl->slots - 1), 0.0 );
|
430
|
+
pl_result->offset = pl->offset * k;
|
431
|
+
|
432
|
+
// Table of probabilities ( reject, maybe, keep ) for each "pivot point"
|
433
|
+
double p_table[3];
|
434
|
+
int keep_combos[3];
|
435
|
+
// Table of distributions for each count of > pivot point (vs == pivot point)
|
436
|
+
ProbabilityList *keep_distributions[171];
|
437
|
+
ProbabilityList *kd;
|
438
|
+
|
439
|
+
int d = n - k;
|
440
|
+
int i, j, q, dn, kn, mn, kdq;
|
441
|
+
double p_sequence;
|
442
|
+
|
443
|
+
for ( i = 0; i < pl->slots; i++ ) {
|
444
|
+
if ( ! pl->probs[i] > 0.0 ) continue;
|
445
|
+
|
446
|
+
q = i + pl->offset;
|
447
|
+
calc_keep_distributions( pl, k, q, kbest, keep_distributions );
|
448
|
+
calc_p_table( pl, q, kbest, p_table );
|
449
|
+
|
450
|
+
for ( kn = 0; kn < k; kn++ ) {
|
451
|
+
// Construct keepers. maybes, discards (just counts of these) . . .
|
452
|
+
if ( kn > 0 && ! ( p_table[2] > 0.0 ) ) continue;
|
453
|
+
|
454
|
+
for ( dn = 0; dn <= d; dn++ ) {
|
455
|
+
mn = (k - kn) + ( d - dn );
|
456
|
+
if ( dn > 0 && ! ( p_table[0] > 0.0 ) ) continue;
|
457
|
+
p_sequence = 1.0;
|
458
|
+
for ( j = 0; j < dn; j++ ) { p_sequence *= p_table[0]; }
|
459
|
+
for ( j = 0; j < mn; j++ ) { p_sequence *= p_table[1]; }
|
460
|
+
for ( j = 0; j < kn; j++ ) { p_sequence *= p_table[2]; }
|
461
|
+
keep_combos[0] = dn;
|
462
|
+
keep_combos[1] = mn;
|
463
|
+
keep_combos[2] = kn;
|
464
|
+
p_sequence *= num_arrangements( keep_combos, 3 );
|
465
|
+
kd = keep_distributions[ kn ];
|
466
|
+
|
467
|
+
for ( j = 0; j < kd->slots; j++ ) {
|
468
|
+
kdq = j + kd->offset;
|
469
|
+
pr[ kdq - pl_result->offset ] += p_sequence * kd->probs[ j ];
|
470
|
+
}
|
471
|
+
}
|
472
|
+
}
|
473
|
+
clear_pl_array( k, keep_distributions );
|
474
|
+
}
|
475
|
+
|
476
|
+
calc_cumulative( pl_result );
|
477
|
+
return pl_result;
|
478
|
+
}
|
479
|
+
|
480
|
+
|
481
|
+
|
482
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
483
|
+
//
|
484
|
+
// Ruby integration
|
485
|
+
//
|
486
|
+
|
487
|
+
static inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
|
488
|
+
return Data_Wrap_Struct( klass, 0, destroy_probability_list, pl );
|
489
|
+
}
|
490
|
+
|
491
|
+
static VALUE pl_alloc(VALUE klass) {
|
492
|
+
return pl_as_ruby_class( create_probability_list(), klass );
|
493
|
+
}
|
494
|
+
|
495
|
+
inline static ProbabilityList *get_probability_list( VALUE obj ) {
|
496
|
+
ProbabilityList *pl;
|
497
|
+
Data_Get_Struct( obj, ProbabilityList, pl );
|
498
|
+
return pl;
|
499
|
+
}
|
500
|
+
|
501
|
+
static void assert_value_wraps_pl( VALUE obj ) {
|
502
|
+
if ( TYPE(obj) != T_DATA ||
|
503
|
+
RDATA(obj)->dfree != (RUBY_DATA_FUNC)destroy_probability_list) {
|
504
|
+
rb_raise( rb_eTypeError, "Expected a Probabilities object, but got something else" );
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
// Validate key/value from hash, and adjust object properties as required
|
509
|
+
int validate_key_value( VALUE key, VALUE val, VALUE obj ) {
|
510
|
+
int k = NUM2INT( key );
|
511
|
+
double v = NUM2DBL( val );
|
512
|
+
ProbabilityList *pl = get_probability_list( obj );
|
513
|
+
if ( k < pl->offset ) {
|
514
|
+
if ( pl->slots < 1 ) {
|
515
|
+
pl->slots = 1;
|
516
|
+
} else {
|
517
|
+
pl->slots = pl->slots - k + pl->offset;
|
518
|
+
}
|
519
|
+
pl->offset = k;
|
520
|
+
} else if ( k - pl->offset >= pl->slots ) {
|
521
|
+
pl->slots = 1 + k - pl->offset;
|
522
|
+
}
|
523
|
+
return ST_CONTINUE;
|
524
|
+
}
|
525
|
+
|
526
|
+
// Copy key/value from hash
|
527
|
+
int copy_key_value( VALUE key, VALUE val, VALUE obj ) {
|
528
|
+
int k = NUM2INT( key );
|
529
|
+
double v = NUM2DBL( val );
|
530
|
+
ProbabilityList *pl = get_probability_list( obj );
|
531
|
+
pl->probs[ k - pl->offset ] = v;
|
532
|
+
return ST_CONTINUE;
|
533
|
+
}
|
534
|
+
|
535
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
536
|
+
//
|
537
|
+
// Ruby class and instance methods for Probabilities
|
538
|
+
//
|
539
|
+
|
540
|
+
static VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
|
541
|
+
int o = NUM2INT(offset);
|
542
|
+
Check_Type( arr, T_ARRAY );
|
543
|
+
int s = FIX2INT( rb_funcall( arr, rb_intern("count"), 0 ) );
|
544
|
+
ProbabilityList *pl = get_probability_list( self );
|
545
|
+
pl->offset = o;
|
546
|
+
int i;
|
547
|
+
double *pr = alloc_probs( pl, s );
|
548
|
+
for(i=0; i<s; i++) {
|
549
|
+
double p_item = NUM2DBL( rb_ary_entry( arr, i ) );
|
550
|
+
if ( p_item < 0.0 ) {
|
551
|
+
rb_raise( rb_eArgError, "Negative probability not allowed" );
|
552
|
+
} else if ( p_item > 1.0 ) {
|
553
|
+
rb_raise( rb_eArgError, "Probability must be in range 0.0..1.0" );
|
554
|
+
}
|
555
|
+
pr[i] = p_item;
|
556
|
+
}
|
557
|
+
double error = calc_cumulative( pl ) - 1.0;
|
558
|
+
if ( error < -1.0e-8 ) {
|
559
|
+
rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
|
560
|
+
} else if ( error > 1.0e-8 ) {
|
561
|
+
rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
|
562
|
+
}
|
563
|
+
return self;
|
564
|
+
}
|
565
|
+
|
566
|
+
|
567
|
+
static VALUE probabilities_initialize_copy( VALUE copy, VALUE orig ) {
|
568
|
+
if (copy == orig) return copy;
|
569
|
+
ProbabilityList *pl_copy = get_probability_list( copy );
|
570
|
+
ProbabilityList *pl_orig = get_probability_list( orig );
|
571
|
+
|
572
|
+
double *pr = alloc_probs( pl_copy, pl_orig->slots );
|
573
|
+
pl_copy->offset = pl_orig->offset;
|
574
|
+
memcpy( pr, pl_orig->probs, pl_orig->slots * sizeof(double) );
|
575
|
+
memcpy( pl_copy->cumulative, pl_orig->cumulative, pl_orig->slots * sizeof(double) );;
|
576
|
+
|
577
|
+
return copy;
|
578
|
+
}
|
579
|
+
|
580
|
+
VALUE probabilities_to_h( VALUE self ) {
|
581
|
+
ProbabilityList *pl = get_probability_list( self );
|
582
|
+
VALUE h = rb_hash_new();
|
583
|
+
double *pr = pl->probs;
|
584
|
+
int s = pl->slots;
|
585
|
+
int o = pl->offset;
|
586
|
+
int i;
|
587
|
+
for(i=0; i<s; i++) {
|
588
|
+
if ( pr[i] > 0.0 ) {
|
589
|
+
rb_hash_aset( h, INT2FIX( o + i ), DBL2NUM( pr[i] ) );
|
590
|
+
}
|
591
|
+
}
|
592
|
+
return h;
|
593
|
+
}
|
594
|
+
|
595
|
+
VALUE probabilities_min( VALUE self ) {
|
596
|
+
return INT2NUM( pl_min( get_probability_list( self ) ) );
|
597
|
+
}
|
598
|
+
|
599
|
+
VALUE probabilities_max( VALUE self ) {
|
600
|
+
return INT2NUM( pl_max( get_probability_list( self ) ) );
|
601
|
+
}
|
602
|
+
|
603
|
+
VALUE probabilites_p_eql( VALUE self, VALUE target ) {
|
604
|
+
return DBL2NUM( pl_p_eql( get_probability_list( self ), NUM2INT(target) ) );
|
605
|
+
}
|
606
|
+
|
607
|
+
VALUE probabilites_p_gt( VALUE self, VALUE target ) {
|
608
|
+
return DBL2NUM( pl_p_gt( get_probability_list( self ), NUM2INT(target) ) );
|
609
|
+
}
|
610
|
+
|
611
|
+
VALUE probabilites_p_ge( VALUE self, VALUE target ) {
|
612
|
+
return DBL2NUM( pl_p_ge( get_probability_list( self ), NUM2INT(target) ) );
|
613
|
+
}
|
614
|
+
|
615
|
+
VALUE probabilites_p_le( VALUE self, VALUE target ) {
|
616
|
+
return DBL2NUM( pl_p_le( get_probability_list( self ), NUM2INT(target) ) );
|
617
|
+
}
|
618
|
+
|
619
|
+
VALUE probabilites_p_lt( VALUE self, VALUE target ) {
|
620
|
+
return DBL2NUM( pl_p_lt( get_probability_list( self ), NUM2INT(target) ) );
|
621
|
+
}
|
622
|
+
|
623
|
+
VALUE probabilites_expected( VALUE self ) {
|
624
|
+
return DBL2NUM( pl_expected( get_probability_list( self ) ) );
|
625
|
+
}
|
626
|
+
|
627
|
+
VALUE probabilities_given_ge( VALUE self, VALUE target ) {
|
628
|
+
int t = NUM2INT(target);
|
629
|
+
ProbabilityList *pl = get_probability_list( self );
|
630
|
+
return pl_as_ruby_class( pl_given_ge( pl, t ), Probabilities );
|
631
|
+
}
|
632
|
+
|
633
|
+
VALUE probabilities_given_le( VALUE self, VALUE target ) {
|
634
|
+
int t = NUM2INT(target);
|
635
|
+
ProbabilityList *pl = get_probability_list( self );
|
636
|
+
return pl_as_ruby_class( pl_given_le( pl, t ), Probabilities );
|
637
|
+
}
|
638
|
+
|
639
|
+
VALUE probabilities_repeat_sum( VALUE self, VALUE nsum ) {
|
640
|
+
int n = NUM2INT(nsum);
|
641
|
+
ProbabilityList *pl = get_probability_list( self );
|
642
|
+
return pl_as_ruby_class( pl_repeat_sum( pl, n ), Probabilities );
|
643
|
+
}
|
644
|
+
|
645
|
+
static VALUE probabilities_repeat_n_sum_k( int argc, VALUE* argv, VALUE self ) {
|
646
|
+
VALUE nsum, nkeepers, kmode;
|
647
|
+
rb_scan_args( argc, argv, "21", &nsum, &nkeepers, &kmode );
|
648
|
+
int keep_best = 1;
|
649
|
+
if (NIL_P(kmode)) {
|
650
|
+
keep_best = 1;
|
651
|
+
} else if ( rb_intern("keep_worst") == SYM2ID(kmode) ) {
|
652
|
+
keep_best = 0;
|
653
|
+
} else if ( rb_intern("keep_best") != SYM2ID(kmode) ) {
|
654
|
+
rb_raise( rb_eArgError, "Keep mode not recognised" );
|
655
|
+
}
|
656
|
+
|
657
|
+
int n = NUM2INT(nsum);
|
658
|
+
int k = NUM2INT(nkeepers);
|
659
|
+
ProbabilityList *pl = get_probability_list( self );
|
660
|
+
return pl_as_ruby_class( pl_repeat_n_sum_k( pl, n, k, keep_best ), Probabilities );
|
661
|
+
}
|
662
|
+
|
663
|
+
VALUE probabilities_each( VALUE self ) {
|
664
|
+
ProbabilityList *pl = get_probability_list( self );
|
665
|
+
int i;
|
666
|
+
double *pr = pl->probs;
|
667
|
+
int o = pl->offset;
|
668
|
+
for ( i = 0; i < pl->slots; i++ ) {
|
669
|
+
if ( pr[i] > 0.0 ) {
|
670
|
+
VALUE a = rb_ary_new2( 2 );
|
671
|
+
rb_ary_store( a, 0, INT2NUM( i + o ));
|
672
|
+
rb_ary_store( a, 1, DBL2NUM( pr[i] ));
|
673
|
+
rb_yield( a );
|
674
|
+
}
|
675
|
+
}
|
676
|
+
return self;
|
677
|
+
}
|
678
|
+
|
679
|
+
VALUE probabilities_for_fair_die( VALUE self, VALUE sides ) {
|
680
|
+
int s = NUM2INT( sides );
|
681
|
+
if ( s < 1 ) {
|
682
|
+
rb_raise( rb_eArgError, "Number of sides should be 1 or more" );
|
683
|
+
}
|
684
|
+
if ( s > 100000 ) {
|
685
|
+
rb_raise( rb_eArgError, "Number of sides should be less than 100001" );
|
686
|
+
}
|
687
|
+
VALUE obj = pl_alloc( Probabilities );
|
688
|
+
ProbabilityList *pl = get_probability_list( obj );
|
689
|
+
pl->offset = 1;
|
690
|
+
alloc_probs_iv( pl, s, 1.0/s );
|
691
|
+
return obj;
|
692
|
+
}
|
693
|
+
|
694
|
+
VALUE probabilities_from_h( VALUE self, VALUE hash ) {
|
695
|
+
rb_check_hash_type( hash );
|
696
|
+
|
697
|
+
VALUE obj = pl_alloc( Probabilities );
|
698
|
+
ProbabilityList *pl = get_probability_list( obj );
|
699
|
+
// Set these up so that they get adjusted during hash iteration
|
700
|
+
pl->offset = 2000000000;
|
701
|
+
pl->slots = 0;
|
702
|
+
// First iteration establish min/max and validate all key/values
|
703
|
+
rb_hash_foreach( hash, validate_key_value, obj );
|
704
|
+
|
705
|
+
double *pr = alloc_probs_iv( pl, pl->slots, 0.0 );
|
706
|
+
// Second iteration copy key/value pairs into structure
|
707
|
+
rb_hash_foreach( hash, copy_key_value, obj );
|
708
|
+
|
709
|
+
double error = calc_cumulative( pl ) - 1.0;
|
710
|
+
if ( error < -1.0e-8 ) {
|
711
|
+
rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
|
712
|
+
} else if ( error > 1.0e-8 ) {
|
713
|
+
rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
|
714
|
+
}
|
715
|
+
return obj;
|
716
|
+
}
|
717
|
+
|
718
|
+
VALUE probabilities_add_distributions( VALUE self, VALUE gdpa, VALUE gdpb ) {
|
719
|
+
assert_value_wraps_pl( gdpa );
|
720
|
+
assert_value_wraps_pl( gdpb );
|
721
|
+
ProbabilityList *pl_a = get_probability_list( gdpa );
|
722
|
+
ProbabilityList *pl_b = get_probability_list( gdpb );
|
723
|
+
return pl_as_ruby_class( pl_add_distributions( pl_a, pl_b ), Probabilities );
|
724
|
+
}
|
725
|
+
|
726
|
+
VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, VALUE m_b, VALUE gdpb ) {
|
727
|
+
assert_value_wraps_pl( gdpa );
|
728
|
+
assert_value_wraps_pl( gdpb );
|
729
|
+
int mul_a = NUM2INT( m_a );
|
730
|
+
ProbabilityList *pl_a = get_probability_list( gdpa );
|
731
|
+
int mul_b = NUM2INT( m_b );
|
732
|
+
ProbabilityList *pl_b = get_probability_list( gdpb );
|
733
|
+
return pl_as_ruby_class( pl_add_distributions_mult( mul_a, pl_a, mul_b, pl_b ), Probabilities );
|
734
|
+
}
|
735
|
+
|
736
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
737
|
+
//
|
738
|
+
// Setup Probabilities class for Ruby interpretter
|
739
|
+
//
|
740
|
+
|
741
|
+
void init_probabilities_class( VALUE ParentModule ) {
|
742
|
+
Probabilities = rb_define_class_under( ParentModule, "Probabilities", rb_cObject );
|
743
|
+
rb_define_alloc_func( Probabilities, pl_alloc );
|
744
|
+
rb_define_method( Probabilities, "initialize", probabilities_initialize, 2 );
|
745
|
+
rb_define_method( Probabilities, "initialize_copy", probabilities_initialize_copy, 1 );
|
746
|
+
rb_define_method( Probabilities, "to_h", probabilities_to_h, 0 );
|
747
|
+
rb_define_method( Probabilities, "min", probabilities_min, 0 );
|
748
|
+
rb_define_method( Probabilities, "max", probabilities_max, 0 );
|
749
|
+
rb_define_method( Probabilities, "p_eql", probabilites_p_eql, 1 );
|
750
|
+
rb_define_method( Probabilities, "p_gt", probabilites_p_gt, 1 );
|
751
|
+
rb_define_method( Probabilities, "p_ge", probabilites_p_ge, 1 );
|
752
|
+
rb_define_method( Probabilities, "p_le", probabilites_p_le, 1 );
|
753
|
+
rb_define_method( Probabilities, "p_lt", probabilites_p_lt, 1 );
|
754
|
+
rb_define_method( Probabilities, "expected", probabilites_expected, 0 );
|
755
|
+
rb_define_method( Probabilities, "each", probabilities_each, 0 );
|
756
|
+
rb_define_method( Probabilities, "given_ge", probabilities_given_ge, 1 );
|
757
|
+
rb_define_method( Probabilities, "given_le", probabilities_given_le, 1 );
|
758
|
+
rb_define_method( Probabilities, "repeat_sum", probabilities_repeat_sum, 1 );
|
759
|
+
rb_define_method( Probabilities, "repeat_n_sum_k", probabilities_repeat_n_sum_k, -1 );
|
760
|
+
rb_define_singleton_method( Probabilities, "for_fair_die", probabilities_for_fair_die, 1 );
|
761
|
+
rb_define_singleton_method( Probabilities, "add_distributions", probabilities_add_distributions, 2 );
|
762
|
+
rb_define_singleton_method( Probabilities, "add_distributions_mult", probabilities_add_distributions_mult, 4 );
|
763
|
+
rb_define_singleton_method( Probabilities, "from_h", probabilities_from_h, 1 );
|
764
|
+
return;
|
765
|
+
}
|