field_test 0.5.1 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b7676acb9075e7701de0019e3c7ca18a0242db8c87348d024006ff787c42d13
4
- data.tar.gz: 92849469d6ca65422ef3f2a1b77f08c7695d79fa336b463cedf3991770be15cd
3
+ metadata.gz: d72f2ad8e2e3848b2e28bc826a479322e0994c1a65f23217d6bbdd85e1a708e5
4
+ data.tar.gz: 593a9d6292533404aaf5706e8d6efee44d168e659ef39b44f19d2c266259c69a
5
5
  SHA512:
6
- metadata.gz: 9f21fa35a6f44d085bbbfbd15e8174e7dd7f8a0d411e3a584097d59793776a0c0bd783cb4a3d7fee7f07402190a1a61487e1be554492208536f375b3e1981c7f
7
- data.tar.gz: aeb943baab224022931a634fa190bbb3837856b3f0ee956c919f1233c6c6796a5251216a65334cd34878487e5902a251e695722a8982a19ffca6711e1d6113cf
6
+ metadata.gz: 6605c5ba8800d72422bb121a9a27a7c1ac202022ce7fc37308b33ea77c9b0084e963c1b56d1ee54caf3b288abf16d4b91c86896b183bf8beba85ed98bf1e1113
7
+ data.tar.gz: 5b4d3d12e2511cf8369b73ae64bdd5ff49a0d8a1160f6bda31ee180d658ea34ee3efe96fe43cb59625195ce0682a30cef15ac21f7084790e7c46258db7906333
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.5.4 (2022-06-16)
2
+
3
+ - Fixed bug in results with MySQL and multiple goals (again)
4
+
5
+ ## 0.5.3 (2022-05-26)
6
+
7
+ - Fixed bug in results with MySQL and multiple goals
8
+
9
+ ## 0.5.2 (2021-12-16)
10
+
11
+ - Fixed error with `events` association
12
+ - Fixed `exclude` option with custom logic
13
+ - Fixed installation error on macOS 12
14
+
1
15
  ## 0.5.1 (2021-09-22)
2
16
 
3
17
  - Improved performance of Bayesian calculations
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2016-2021 Andrew Kane
1
+ Copyright (c) 2016-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -34,8 +34,6 @@ mount FieldTest::Engine, at: "field_test"
34
34
 
35
35
  Be sure to [secure the dashboard](#dashboard-security) in production.
36
36
 
37
- ![Screenshot](https://ankane.github.io/field_test/screenshot6.png)
38
-
39
37
  ## Getting Started
40
38
 
41
39
  Add an experiment to `config/field_test.yml`.
@@ -427,5 +425,6 @@ To get started with development:
427
425
  git clone https://github.com/ankane/field_test.git
428
426
  cd field_test
429
427
  bundle install
428
+ bundle exec rake compile
430
429
  bundle exec rake test
431
430
  ```
@@ -2,7 +2,7 @@ module FieldTest
2
2
  class Membership < ActiveRecord::Base
3
3
  self.table_name = "field_test_memberships"
4
4
 
5
- has_many :events, class_name: "FieldTest::Event"
5
+ has_many :events, class_name: "FieldTest::Event", foreign_key: "field_test_membership_id"
6
6
 
7
7
  validates :participant, presence: true, if: -> { FieldTest.legacy_participants }
8
8
  validates :participant_id, presence: true, if: -> { !FieldTest.legacy_participants }
@@ -0,0 +1,313 @@
1
+ /*!
2
+ * BayesTest C++ v0.1.0
3
+ * https://github.com/ankane/bayestest-cpp
4
+ * MIT License
5
+ */
6
+
7
+ #pragma once
8
+
9
+ #include <cmath>
10
+ #include <vector>
11
+
12
+ namespace bayestest {
13
+
14
+ double logbeta(double a, double b) {
15
+ return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
16
+ }
17
+
18
+ double prob_b_beats_a(int alpha_a, int beta_a, int alpha_b, int beta_b) {
19
+ double total = 0.0;
20
+ double logbeta_aa_ba = logbeta(alpha_a, beta_a);
21
+ double beta_ba = beta_b + beta_a;
22
+
23
+ for (auto i = 0; i < alpha_b; i++) {
24
+ total += exp(logbeta(alpha_a + i, beta_ba) - log(beta_b + i) - logbeta(1 + i, beta_b) - logbeta_aa_ba);
25
+ }
26
+
27
+ return total;
28
+ }
29
+
30
+ double prob_c_beats_ab(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c) {
31
+ double total = 0.0;
32
+
33
+ double logbeta_ac_bc = logbeta(alpha_c, beta_c);
34
+
35
+ std::vector<double> log_bb_j_logbeta_j_bb;
36
+ log_bb_j_logbeta_j_bb.reserve(alpha_b);
37
+
38
+ for (auto j = 0; j < alpha_b; j++) {
39
+ log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
40
+ }
41
+
42
+ double abc = beta_a + beta_b + beta_c;
43
+ std::vector<double> logbeta_ac_i_j;
44
+ logbeta_ac_i_j.reserve(alpha_a + alpha_b);
45
+
46
+ for (auto i = 0; i < alpha_a + alpha_b; i++) {
47
+ logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
48
+ }
49
+
50
+ for (auto i = 0; i < alpha_a; i++) {
51
+ double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
52
+
53
+ for (auto j = 0; j < alpha_b; j++) {
54
+ total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j_logbeta_j_bb[j]);
55
+ }
56
+ }
57
+
58
+ return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
59
+ prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
60
+ }
61
+
62
+ double prob_d_beats_abc(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c, int alpha_d, int beta_d) {
63
+ double total = 0.0;
64
+
65
+ double logbeta_ad_bd = logbeta(alpha_d, beta_d);
66
+
67
+ std::vector<double> log_bb_j_logbeta_j_bb;
68
+ log_bb_j_logbeta_j_bb.reserve(alpha_b);
69
+
70
+ for (auto j = 0; j < alpha_b; j++) {
71
+ log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
72
+ }
73
+
74
+ std::vector<double> log_bc_k_logbeta_k_bc;
75
+ log_bc_k_logbeta_k_bc.reserve(alpha_c);
76
+
77
+ for (auto k = 0; k < alpha_c; k++) {
78
+ log_bc_k_logbeta_k_bc.push_back(log(beta_c + k) + logbeta(1 + k, beta_c));
79
+ }
80
+
81
+ double abcd = beta_a + beta_b + beta_c + beta_d;
82
+ std::vector<double> logbeta_bd_i_j_k;
83
+ logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
84
+
85
+ for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
86
+ logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
87
+ }
88
+
89
+ for (auto i = 0; i < alpha_a; i++) {
90
+ double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
91
+
92
+ for (auto j = 0; j < alpha_b; j++) {
93
+ double sum_j = sum_i - log_bb_j_logbeta_j_bb[j];
94
+
95
+ for (auto k = 0; k < alpha_c; k++) {
96
+ total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k_logbeta_k_bc[k]);
97
+ }
98
+ }
99
+ }
100
+
101
+ return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
102
+ prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
103
+ prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
104
+ prob_c_beats_ab(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
105
+ prob_c_beats_ab(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
106
+ prob_c_beats_ab(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
107
+ }
108
+
109
+ double prob_1_beats_2(int alpha_1, int beta_1, int alpha_2, int beta_2) {
110
+ double total = 0.0;
111
+ double log_b1 = log(beta_1);
112
+ double a2_log_b2 = alpha_2 * log(beta_2);
113
+ double log_b1_b2 = log(beta_1 + beta_2);
114
+
115
+ for (auto k = 0; k < alpha_1; k++) {
116
+ total += exp(k * log_b1 +
117
+ a2_log_b2 -
118
+ (k + alpha_2) * log_b1_b2 -
119
+ log(k + alpha_2) -
120
+ logbeta(k + 1, alpha_2));
121
+ }
122
+
123
+ return total;
124
+ }
125
+
126
+ double prob_1_beats_23(int alpha_1, int beta_1, int alpha_2, int beta_2, int alpha_3, int beta_3) {
127
+ double total = 0.0;
128
+
129
+ double log_b1_b2_b3 = log(beta_1 + beta_2 + beta_3);
130
+ double a1_log_b1 = alpha_1 * log(beta_1);
131
+ double log_b2 = log(beta_2);
132
+ double log_b3 = log(beta_3);
133
+ double loggamma_a1 = std::lgamma(alpha_1);
134
+
135
+ for (auto k = 0; k < alpha_2; k++) {
136
+ double sum_k = a1_log_b1 + k * log_b2 - std::lgamma(k + 1);
137
+
138
+ for (auto l = 0; l < alpha_3; l++) {
139
+ total += exp(sum_k + l * log_b3
140
+ - (k + l + alpha_1) * log_b1_b2_b3
141
+ + std::lgamma(k + l + alpha_1) - std::lgamma(l + 1) - loggamma_a1);
142
+ }
143
+ }
144
+
145
+ return 1.0 - prob_1_beats_2(alpha_2, beta_2, alpha_1, beta_1)
146
+ - prob_1_beats_2(alpha_3, beta_3, alpha_1, beta_1) + total;
147
+ }
148
+
149
+ class BinaryTest {
150
+ public:
151
+ void add(int participants, int conversions) {
152
+ variants.emplace_back(participants, conversions);
153
+ }
154
+
155
+ std::vector<double> probabilities() {
156
+ std::vector<double> probs;
157
+ probs.reserve(variants.size());
158
+
159
+ switch (variants.size()) {
160
+ case 0: {
161
+ break;
162
+ }
163
+ case 1: {
164
+ probs.push_back(1);
165
+
166
+ break;
167
+ }
168
+ case 2: {
169
+ auto b = variants[0];
170
+ auto a = variants[1];
171
+
172
+ auto prob = prob_b_beats_a(
173
+ 1 + a.conversions,
174
+ 1 + a.participants - a.conversions,
175
+ 1 + b.conversions,
176
+ 1 + b.participants - b.conversions
177
+ );
178
+ probs.push_back(prob);
179
+ probs.push_back(1 - prob);
180
+
181
+ break;
182
+ }
183
+ case 3: {
184
+ auto total = 0.0;
185
+ for (auto i = 0; i < 2; i++) {
186
+ auto c = variants[i];
187
+ auto b = variants[(i + 1) % 3];
188
+ auto a = variants[(i + 2) % 3];
189
+
190
+ auto prob = prob_c_beats_ab(
191
+ 1 + a.conversions,
192
+ 1 + a.participants - a.conversions,
193
+ 1 + b.conversions,
194
+ 1 + b.participants - b.conversions,
195
+ 1 + c.conversions,
196
+ 1 + c.participants - c.conversions
197
+ );
198
+
199
+ probs.push_back(prob);
200
+ total += prob;
201
+ }
202
+ probs.push_back(1 - total);
203
+
204
+ break;
205
+ }
206
+ default: {
207
+ auto total = 0.0;
208
+ for (auto i = 0; i < 3; i++) {
209
+ auto d = variants[i];
210
+ auto c = variants[(i + 1) % 4];
211
+ auto b = variants[(i + 2) % 4];
212
+ auto a = variants[(i + 3) % 4];
213
+
214
+ auto prob = prob_d_beats_abc(
215
+ 1 + a.conversions,
216
+ 1 + a.participants - a.conversions,
217
+ 1 + b.conversions,
218
+ 1 + b.participants - b.conversions,
219
+ 1 + c.conversions,
220
+ 1 + c.participants - c.conversions,
221
+ 1 + d.conversions,
222
+ 1 + d.participants - d.conversions
223
+ );
224
+
225
+ probs.push_back(prob);
226
+ total += prob;
227
+ }
228
+ probs.push_back(1 - total);
229
+ }
230
+ }
231
+ return probs;
232
+ }
233
+
234
+ private:
235
+ struct Variant {
236
+ Variant(int participants, int conversions) : participants(participants), conversions(conversions) {};
237
+ int participants;
238
+ int conversions;
239
+ };
240
+
241
+ std::vector<Variant> variants;
242
+ };
243
+
244
+ class CountTest {
245
+ public:
246
+ void add(int events, int exposure) {
247
+ variants.emplace_back(events, exposure);
248
+ }
249
+
250
+ std::vector<double> probabilities() {
251
+ std::vector<double> probs;
252
+ probs.reserve(variants.size());
253
+
254
+ switch (variants.size()) {
255
+ case 0: {
256
+ break;
257
+ }
258
+ case 1: {
259
+ probs.push_back(1);
260
+
261
+ break;
262
+ }
263
+ case 2: {
264
+ auto a = variants[0];
265
+ auto b = variants[1];
266
+
267
+ auto prob = prob_1_beats_2(
268
+ a.events,
269
+ a.exposure,
270
+ b.events,
271
+ b.exposure
272
+ );
273
+ probs.push_back(prob);
274
+ probs.push_back(1 - prob);
275
+
276
+ break;
277
+ }
278
+ default: {
279
+ auto total = 0.0;
280
+ for (auto i = 0; i < 2; i++) {
281
+ auto a = variants[i];
282
+ auto b = variants[(i + 1) % 3];
283
+ auto c = variants[(i + 2) % 3];
284
+
285
+ auto prob = prob_1_beats_23(
286
+ a.events,
287
+ a.exposure,
288
+ b.events,
289
+ b.exposure,
290
+ c.events,
291
+ c.exposure
292
+ );
293
+
294
+ probs.push_back(prob);
295
+ total += prob;
296
+ }
297
+ probs.push_back(1 - total);
298
+ }
299
+ }
300
+ return probs;
301
+ }
302
+
303
+ private:
304
+ struct Variant {
305
+ Variant(int events, int exposure) : events(events), exposure(exposure) {};
306
+ int events;
307
+ int exposure;
308
+ };
309
+
310
+ std::vector<Variant> variants;
311
+ };
312
+
313
+ }
@@ -1,12 +1,16 @@
1
1
  #include <rice/rice.hpp>
2
- #include "bayesian_ab.hpp"
2
+ #include <rice/stl.hpp>
3
+
4
+ #include "bayestest.hpp"
5
+
6
+ using bayestest::BinaryTest;
3
7
 
4
8
  extern "C"
5
9
  void Init_ext() {
6
10
  auto rb_mFieldTest = Rice::define_module("FieldTest");
7
11
 
8
- Rice::define_class_under(rb_mFieldTest, "Calculations")
9
- .define_singleton_function("prob_b_beats_a", &bayesian_ab::prob_b_beats_a)
10
- .define_singleton_function("prob_c_beats_a_and_b", &bayesian_ab::prob_c_beats_a_and_b)
11
- .define_singleton_function("prob_d_beats_a_and_b_and_c", &bayesian_ab::prob_d_beats_a_and_b_and_c);
12
+ Rice::define_class_under<BinaryTest>(rb_mFieldTest, "BinaryTest")
13
+ .define_constructor(Rice::Constructor<BinaryTest>())
14
+ .define_method("add", &BinaryTest::add)
15
+ .define_method("probabilities", &BinaryTest::probabilities);
12
16
  }
@@ -114,9 +114,9 @@ module FieldTest
114
114
  elsif adapter_name =~ /postg/i # postgres
115
115
  "(participant_type, participant_id)"
116
116
  elsif adapter_name =~ /mysql/i
117
- "participant_type, participant_id"
117
+ "COALESCE(participant_type, ''), participant_id"
118
118
  else
119
- # not perfect, but it'll do
119
+ # SQLite supports single column
120
120
  "COALESCE(participant_type, '') || ':' || participant_id"
121
121
  end
122
122
 
@@ -141,40 +141,21 @@ module FieldTest
141
141
  }
142
142
  end
143
143
 
144
- case variants.size
145
- when 1, 2, 3
146
- total = 0.0
147
-
148
- (variants.size - 1).times do |i|
149
- c = results.values[i]
150
- b = results.values[(i + 1) % variants.size]
151
- a = results.values[(i + 2) % variants.size]
152
-
153
- alpha_a = 1 + a[:converted]
154
- beta_a = 1 + a[:participated] - a[:converted]
155
- alpha_b = 1 + b[:converted]
156
- beta_b = 1 + b[:participated] - b[:converted]
157
- alpha_c = 1 + c[:converted]
158
- beta_c = 1 + c[:participated] - c[:converted]
159
-
160
- # TODO calculate this incrementally by caching intermediate results
161
- prob_winning =
162
- if variants.size == 2
163
- cache_fetch ["field_test", "prob_b_beats_a", alpha_b, beta_b, alpha_c, beta_c] do
164
- Calculations.prob_b_beats_a(alpha_b, beta_b, alpha_c, beta_c)
165
- end
166
- else
167
- cache_fetch ["field_test", "prob_c_beats_a_and_b", alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c] do
168
- Calculations.prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_c, beta_c)
169
- end
144
+ if variants.size <= 3
145
+ probabilities =
146
+ cache_fetch(["field_test", "probabilities"] + results.flat_map { |_, v| [v[:participated], v[:converted]] }) do
147
+ binary_test = BinaryTest.new
148
+ results.each do |_, v|
149
+ binary_test.add(v[:participated], v[:converted])
170
150
  end
151
+ binary_test.probabilities.to_a
152
+ end
171
153
 
172
- results[variants[i]][:prob_winning] = prob_winning
173
- total += prob_winning
154
+ results.each_key.zip(probabilities) do |variant, prob_winning|
155
+ results[variant][:prob_winning] = prob_winning
174
156
  end
175
-
176
- results[variants.last][:prob_winning] = 1 - total
177
157
  end
158
+
178
159
  results
179
160
  end
180
161
 
@@ -13,7 +13,7 @@ module FieldTest
13
13
  end
14
14
 
15
15
  if FieldTest.exclude_bots?
16
- options[:exclude] = Browser.new(request.user_agent).bot?
16
+ options[:exclude] ||= Browser.new(request.user_agent).bot?
17
17
  end
18
18
 
19
19
  options[:exclude] ||= FieldTest.excluded_ips.any? { |ip| ip.include?(request.remote_ip) }
@@ -1,3 +1,3 @@
1
1
  module FieldTest
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.4"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: field_test
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2022-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -89,7 +89,7 @@ files:
89
89
  - app/views/field_test/participants/show.html.erb
90
90
  - app/views/layouts/field_test/application.html.erb
91
91
  - config/routes.rb
92
- - ext/field_test/bayesian_ab.hpp
92
+ - ext/field_test/bayestest.hpp
93
93
  - ext/field_test/ext.cpp
94
94
  - ext/field_test/extconf.rb
95
95
  - lib/field_test.rb
@@ -124,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
126
  requirements: []
127
- rubygems_version: 3.2.22
127
+ rubygems_version: 3.3.7
128
128
  signing_key:
129
129
  specification_version: 4
130
130
  summary: A/B testing for Rails
@@ -1,103 +0,0 @@
1
- #pragma once
2
-
3
- #include <cmath>
4
- #include <vector>
5
-
6
- namespace bayesian_ab {
7
-
8
- double logbeta(double a, double b) {
9
- return std::lgamma(a) + std::lgamma(b) - std::lgamma(a + b);
10
- }
11
-
12
- double prob_b_beats_a(int alpha_a, int beta_a, int alpha_b, int beta_b) {
13
- double total = 0.0;
14
- double logbeta_aa_ba = logbeta(alpha_a, beta_a);
15
- double beta_ba = beta_b + beta_a;
16
-
17
- for (auto i = 0; i < alpha_b; i++) {
18
- total += exp(logbeta(alpha_a + i, beta_ba) - log(beta_b + i) - logbeta(1 + i, beta_b) - logbeta_aa_ba);
19
- }
20
-
21
- return total;
22
- }
23
-
24
- double prob_c_beats_a_and_b(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c) {
25
- double total = 0.0;
26
-
27
- double logbeta_ac_bc = logbeta(alpha_c, beta_c);
28
-
29
- std::vector<double> log_bb_j_logbeta_j_bb;
30
- log_bb_j_logbeta_j_bb.reserve(alpha_b);
31
-
32
- for (auto j = 0; j < alpha_b; j++) {
33
- log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
34
- }
35
-
36
- double abc = beta_a + beta_b + beta_c;
37
- std::vector<double> logbeta_ac_i_j;
38
- logbeta_ac_i_j.reserve(alpha_a + alpha_b);
39
-
40
- for (auto i = 0; i < alpha_a + alpha_b; i++) {
41
- logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
42
- }
43
-
44
- for (auto i = 0; i < alpha_a; i++) {
45
- double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
46
-
47
- for (auto j = 0; j < alpha_b; j++) {
48
- total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j_logbeta_j_bb[j]);
49
- }
50
- }
51
-
52
- return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
53
- prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
54
- }
55
-
56
- double prob_d_beats_a_and_b_and_c(int alpha_a, int beta_a, int alpha_b, int beta_b, int alpha_c, int beta_c, int alpha_d, int beta_d) {
57
- double total = 0.0;
58
-
59
- double logbeta_ad_bd = logbeta(alpha_d, beta_d);
60
-
61
- std::vector<double> log_bb_j_logbeta_j_bb;
62
- log_bb_j_logbeta_j_bb.reserve(alpha_b);
63
-
64
- for (auto j = 0; j < alpha_b; j++) {
65
- log_bb_j_logbeta_j_bb.push_back(log(beta_b + j) + logbeta(1 + j, beta_b));
66
- }
67
-
68
- std::vector<double> log_bc_k_logbeta_k_bc;
69
- log_bc_k_logbeta_k_bc.reserve(alpha_c);
70
-
71
- for (auto k = 0; k < alpha_c; k++) {
72
- log_bc_k_logbeta_k_bc.push_back(log(beta_c + k) + logbeta(1 + k, beta_c));
73
- }
74
-
75
- double abcd = beta_a + beta_b + beta_c + beta_d;
76
- std::vector<double> logbeta_bd_i_j_k;
77
- logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
78
-
79
- for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
80
- logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
81
- }
82
-
83
- for (auto i = 0; i < alpha_a; i++) {
84
- double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
85
-
86
- for (auto j = 0; j < alpha_b; j++) {
87
- double sum_j = sum_i - log_bb_j_logbeta_j_bb[j];
88
-
89
- for (auto k = 0; k < alpha_c; k++) {
90
- total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k_logbeta_k_bc[k]);
91
- }
92
- }
93
- }
94
-
95
- return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
96
- prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
97
- prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
98
- prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
99
- prob_c_beats_a_and_b(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
100
- prob_c_beats_a_and_b(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
101
- }
102
-
103
- }