field_test 0.5.0 → 0.5.3

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: 913c2ad9eaefe134cdabd915eec17f4f48fa9062c9066e37aa638989f0c05072
4
- data.tar.gz: bfe73138f6b18fbf8b72e144d2f3c6e727a1d398e6c0f09860981aade30b9d45
3
+ metadata.gz: fdb76c20b686424fab97716c260d114977a33ca9d9b41f8ba03a25523742fb49
4
+ data.tar.gz: 1879b26b2b96ce4083a66f41d15abf07ab61eb2d264308076a65a79adb319df2
5
5
  SHA512:
6
- metadata.gz: e35ca617ba6e37ad7650f14ef46fd225ce3469faac29cb91909cc682f42b160b0af6af522906cfb53015a381e8c0ddb27ebda2e1a219c186147716e38d7d9396
7
- data.tar.gz: 3464e1e26a219ddb43a8a766d965608c60ef014a158ac2df923403d915a684c58fdef6f6ab75b9ccf60d1201f658c84638cb9c2e5025ac48a69b45f721121290
6
+ metadata.gz: 2167ced106ba8f40dc2b8eab444101b7e422722daee96f9b19cee3e596fb22ac18fd6999f8afc9f454e496fffa3fd403cdfefca4d36d32d4601148745b507832
7
+ data.tar.gz: 440498c252008895b164cb59214ef3f98cf4d31572694df985f62a5393c66abd71c8dcb56a57ddb4cff346b227cff1e4d9f8d18c79c375996de53efaa0031ba0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.5.3 (2022-05-26)
2
+
3
+ - Fixed bug in results with MySQL and multiple goals
4
+
5
+ ## 0.5.2 (2021-12-16)
6
+
7
+ - Fixed error with `events` association
8
+ - Fixed `exclude` option with custom logic
9
+ - Fixed installation error on macOS 12
10
+
11
+ ## 0.5.1 (2021-09-22)
12
+
13
+ - Improved performance of Bayesian calculations
14
+
1
15
  ## 0.5.0 (2021-09-21)
2
16
 
3
17
  - Significantly 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
  }
@@ -113,10 +113,9 @@ module FieldTest
113
113
  :participant
114
114
  elsif adapter_name =~ /postg/i # postgres
115
115
  "(participant_type, participant_id)"
116
- elsif adapter_name =~ /mysql/i
117
- "participant_type, participant_id"
118
116
  else
119
- # not perfect, but it'll do
117
+ # SQLite supports single column
118
+ # MySQL supports multiple columns, but none can be NULL
120
119
  "COALESCE(participant_type, '') || ':' || participant_id"
121
120
  end
122
121
 
@@ -141,40 +140,21 @@ module FieldTest
141
140
  }
142
141
  end
143
142
 
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
143
+ if variants.size <= 3
144
+ probabilities =
145
+ cache_fetch(["field_test", "probabilities"] + results.flat_map { |_, v| [v[:participated], v[:converted]] }) do
146
+ binary_test = BinaryTest.new
147
+ results.each do |_, v|
148
+ binary_test.add(v[:participated], v[:converted])
170
149
  end
150
+ binary_test.probabilities.to_a
151
+ end
171
152
 
172
- results[variants[i]][:prob_winning] = prob_winning
173
- total += prob_winning
153
+ results.each_key.zip(probabilities) do |variant, prob_winning|
154
+ results[variant][:prob_winning] = prob_winning
174
155
  end
175
-
176
- results[variants.last][:prob_winning] = 1 - total
177
156
  end
157
+
178
158
  results
179
159
  end
180
160
 
@@ -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.0"
2
+ VERSION = "0.5.3"
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.0
4
+ version: 0.5.3
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-22 00:00:00.000000000 Z
11
+ date: 2022-05-27 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,120 +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;
30
- log_bb_j.reserve(alpha_b);
31
- std::vector<double> logbeta_j_bb;
32
- logbeta_j_bb.reserve(alpha_b);
33
-
34
- for (auto j = 0; j < alpha_b; j++) {
35
- log_bb_j.push_back(log(beta_b + j));
36
- logbeta_j_bb.push_back(logbeta(1 + j, beta_b));
37
- }
38
-
39
- double abc = beta_a + beta_b + beta_c;
40
- std::vector<double> logbeta_ac_i_j;
41
- logbeta_ac_i_j.reserve(alpha_a + alpha_b);
42
-
43
- for (auto i = 0; i < alpha_a + alpha_b; i++) {
44
- logbeta_ac_i_j.push_back(logbeta(alpha_c + i, abc));
45
- }
46
-
47
- for (auto i = 0; i < alpha_a; i++) {
48
- double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ac_bc;
49
-
50
- for (auto j = 0; j < alpha_b; j++) {
51
- total += exp(sum_i + logbeta_ac_i_j[i + j] - log_bb_j[j] - logbeta_j_bb[j]);
52
- }
53
- }
54
-
55
- return 1 - prob_b_beats_a(alpha_c, beta_c, alpha_a, beta_a) -
56
- prob_b_beats_a(alpha_c, beta_c, alpha_b, beta_b) + total;
57
- }
58
-
59
- 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) {
60
- double total = 0.0;
61
-
62
- double logbeta_ad_bd = logbeta(alpha_d, beta_d);
63
-
64
- std::vector<double> log_bb_j;
65
- log_bb_j.reserve(alpha_b);
66
- std::vector<double> logbeta_j_bb;
67
- logbeta_j_bb.reserve(alpha_b);
68
-
69
- for (auto j = 0; j < alpha_b; j++) {
70
- log_bb_j.push_back(log(beta_b + j));
71
- logbeta_j_bb.push_back(logbeta(1 + j, beta_b));
72
- }
73
-
74
- std::vector<double> log_bc_k;
75
- log_bc_k.reserve(alpha_c);
76
- std::vector<double> logbeta_k_bc;
77
- logbeta_k_bc.reserve(alpha_c);
78
-
79
- for (auto k = 0; k < alpha_c; k++) {
80
- log_bc_k.push_back(log(beta_c + k));
81
- logbeta_k_bc.push_back(logbeta(1 + k, beta_c));
82
- }
83
-
84
- double abcd = beta_a + beta_b + beta_c + beta_d;
85
- std::vector<double> logbeta_bd_i_j_k;
86
- logbeta_bd_i_j_k.reserve(alpha_a + alpha_b + alpha_c);
87
-
88
- for (auto i = 0; i < alpha_a + alpha_b + alpha_c; i++) {
89
- logbeta_bd_i_j_k.push_back(logbeta(alpha_d + i, abcd));
90
- }
91
-
92
- std::vector<double> sum_i_j;
93
- sum_i_j.reserve(alpha_a * alpha_b);
94
- for (auto i = 0; i < alpha_a; i++) {
95
- double sum_i = -log(beta_a + i) - logbeta(1 + i, beta_a) - logbeta_ad_bd;
96
-
97
- for (auto j = 0; j < alpha_b; j++) {
98
- sum_i_j.push_back(sum_i - log_bb_j[j] - logbeta_j_bb[j]);
99
- }
100
- }
101
-
102
- for (auto i = 0; i < alpha_a; i++) {
103
- for (auto j = 0; j < alpha_b; j++) {
104
- double sum_j = sum_i_j[i * alpha_b + j];
105
-
106
- for (auto k = 0; k < alpha_c; k++) {
107
- total += exp(sum_j + logbeta_bd_i_j_k[i + j + k] - log_bc_k[k] - logbeta_k_bc[k]);
108
- }
109
- }
110
- }
111
-
112
- return 1 - prob_b_beats_a(alpha_a, beta_a, alpha_d, beta_d) -
113
- prob_b_beats_a(alpha_b, beta_b, alpha_d, beta_d) -
114
- prob_b_beats_a(alpha_c, beta_c, alpha_d, beta_d) +
115
- prob_c_beats_a_and_b(alpha_a, beta_a, alpha_b, beta_b, alpha_d, beta_d) +
116
- prob_c_beats_a_and_b(alpha_a, beta_a, alpha_c, beta_c, alpha_d, beta_d) +
117
- prob_c_beats_a_and_b(alpha_b, beta_b, alpha_c, beta_c, alpha_d, beta_d) - total;
118
- }
119
-
120
- }