Skip to content

Commit e7ea46a

Browse files
committed
Gives optimal total cost and minimum tests possible.
1 parent 61b3034 commit e7ea46a

File tree

4 files changed

+129
-50
lines changed

4 files changed

+129
-50
lines changed

BayesTesting.html

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
2929
<div id="center_content">
3030
<div id="top_pane" class="row-container">
3131
<div id="illustration" class="row-item column-container">
32-
<div id="people_count_group" class="column-item slider-labels">
33-
<label for="people_count_slider" id="people_count_label">
34-
Number of
35-
People: <span id="people_count"></span>
36-
</label>
37-
<input type="number" min="100" max="1000000000" step="10"
38-
value="2500" id="people_count_slider"
39-
oninput="sliderUpdate(value, '#people_count_slider')"/>
40-
</div>
4132
<div id="people_dots_group" class="column-item row-container">
4233
<div id="people_dots" class="row-item"></div>
4334
<div id="displayAsForm" class="row-item">
@@ -49,6 +40,16 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
4940
<label for="Grid">Grid</label>
5041
</div>
5142
</div>
43+
44+
<div id="people_count_group" class="column-item margin-top-zero">
45+
<label for="people_count_slider" id="people_count_label">
46+
Number of
47+
People: <span id="people_count"></span>
48+
</label>
49+
<input type="number" min="100" max="1000000000" step="10"
50+
value="2500" id="people_count_slider" class="number-input-box"
51+
oninput="sliderUpdate(value, '#people_count_slider')"/>
52+
</div>
5253
<div class="data-metric">
5354
<div class="data-metric-def">
5455
<dl>
@@ -71,7 +72,7 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
7172

7273
</div>
7374

74-
<div class="slider-group column-item">
75+
<div class="margin-bottom-zero slider-group column-item">
7576
<label class="slider-labels" for="infection_rate_slider">
7677
Infection Rate:
7778
</label>
@@ -84,12 +85,13 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
8485
'#infection_rate')"/>
8586
</div>
8687

87-
<div id="cost_per_test_group" class="column-item slider-labels">
88+
<div id="cost_per_test_group" class="column-item margin-top-zero">
8889
<label for="cost_per_test_slider" id="cost_per_test_label">
8990
Cost Per Test: <span id="cost_per_test"></span>
91+
<span class="number-input-box">&nbsp;$</span>
9092
</label>
91-
<input type="number" min="1" max="200" step="1"
92-
value="100" id="cost_per_test_slider"
93+
<input type="number" min="1" max="any" step="1"
94+
value="100" id="cost_per_test_slider" class="number-input-box"
9395
oninput="sliderUpdate(value, '#cost_per_test_slider')"/>
9496
</div>
9597
</div>
@@ -241,6 +243,24 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
241243
<div id="pooling-testing" class="row-item column-container">
242244
<div class="stats_title column-item">Sample Pooling</div>
243245
<div class="stats_subtitle column-item">Expected Values</div>
246+
247+
<div class="slider-group column-item">
248+
<div class="column-item row-container margin-bottom-zero" id="optimal_container">
249+
<span class="row-item margin-bottom-zero margin-right-auto">
250+
<label class=" row-item margin-bottom-zero" for="pool_size_slider">
251+
Pool Size: </label>
252+
<output for="pool_size_slider" id="pool_size"
253+
class=" margin-bottom-zero">25
254+
</output>
255+
</span>
256+
<span id="optimal_pool_size_label" class="row-item margin-bottom-zero">Optimal Pool Size: <span id="pooling_optimal_pool_size"></span></span>
257+
</div>
258+
<input type="range"
259+
id="pool_size_slider" class="slider-widget margin-top-zero" min="1"
260+
max="40" step="1" value="25" oninput="sliderUpdate(value, '#pool_size')"/>
261+
</div>
262+
<hr class="row-line">
263+
244264
<div class="data-metric">
245265
<div class="data-metric-def">
246266
<dl>
@@ -251,6 +271,16 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
251271
<div class="answer-equals">$$=$$</div>
252272
<div id="pooling_total_cost" class="answer-value"></div>
253273
</div>
274+
<div class="data-metric">
275+
<div class="data-metric-def">
276+
<dl>
277+
<dt>Optimal Total Cost</dt>
278+
<dd>cost at optimal pool size</dd>
279+
</dl>
280+
</div>
281+
<div class="answer-equals">$$=$$</div>
282+
<div id="pooling_optimal_cost" class="answer-value"></div>
283+
</div>
254284
<div class="data-metric">
255285
<div class="data-metric-def">
256286
<dl>
@@ -261,6 +291,16 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
261291
<div class="answer-equals">$$=$$</div>
262292
<div id="pooling_tests_used" class="answer-value"></div>
263293
</div>
294+
<div class="data-metric">
295+
<div class="data-metric-def">
296+
<dl>
297+
<dt>Minimum Tests Possible</dt>
298+
<dd>tests used at optimal pool size</dd>
299+
</dl>
300+
</div>
301+
<div class="answer-equals">$$=$$</div>
302+
<div id="pooling_min_tests_possible" class="answer-value"></div>
303+
</div>
264304
<hr class="row-line">
265305
<div class="data-metric">
266306
<div class="data-metric-def">
@@ -338,25 +378,6 @@ <h1 id="page-heading">Comparison Between Individual Testing and Sample Pooling f
338378
<div id="pooling_specificity" class="answer-value"></div>
339379
</div>
340380

341-
<hr class="row-line">
342-
343-
<div class="slider-group column-item">
344-
<div class="column-item row-container">
345-
<span class="row-item">
346-
<label class="slider-labels row-item" for="pool_size_slider">
347-
Pool Size: </label>
348-
<output for="pool_size_slider" id="pool_size"
349-
class="slider-labels">25
350-
</output>
351-
</span>
352-
<span class="row-item slider-labels">Optimal Pool Size: <span id="pooling_optimal_pool_size"></span></span>
353-
</div>
354-
<br>
355-
<input type="range"
356-
id="pool_size_slider" class="slider-widget" min="1"
357-
max="40" step="1" value="25" oninput="sliderUpdate(value, '#pool_size')"/>
358-
</div>
359-
360381

361382
</div>
362383

bayestesting.css

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ body {
3838
z-index: 10;
3939
position: relative;
4040
background-color: white;
41+
padding-bottom: 1rem;
4142
/*display: flex;*/
4243
/*flex-flow: row wrap;*/
4344
/*justify-content: space-around;*/
@@ -64,6 +65,15 @@ body {
6465
box-shadow: inset 0 12px 12px -12px rgba(0, 0, 0, 0.5);
6566
}
6667

68+
.margin-bottom-zero{
69+
margin-bottom: 0;
70+
padding-bottom: 0;
71+
}
72+
.margin-top-zero{
73+
margin-top: 0;
74+
}
75+
76+
6777
#displayAsForm {
6878
/*position: relative;*/
6979
text-align: left;
@@ -159,29 +169,26 @@ dl {
159169
/*overflow-y: visible;*/
160170
/*background-color: aliceblue;*/
161171
align-items: flex-end;
172+
padding-top: 0;
162173
margin-bottom: 1.4rem;
163174
}
164175

176+
165177
#people_count_group {
166178

167-
/*font-size: large;*/
168-
text-align: center;
179+
/*text-align: center;*/
169180
vertical-align: top;
170181
display: inline-block;
171-
white-space: nowrap;
172-
width: 30rem;
173-
174-
/*style="display: inline-block; width: 240px; text-align: right"*/
182+
margin-bottom: .5rem;
183+
/*white-space: nowrap;*/
184+
/*width: 30rem;*/
175185
}
176186

187+
177188
#people_count{
178189
display: none;
179190
}
180191

181-
#people_count_slider{
182-
font-size: larger;
183-
max-width: 12rem;
184-
}
185192

186193
#graph_group {
187194
text-align: center;
@@ -215,12 +222,33 @@ dl {
215222
margin-top:0;
216223
}
217224

225+
#optimal_container{
226+
width: 30rem;
227+
margin-top: 1rem;
228+
}
229+
#optimal_pool_size_label{
230+
/*margin-top: 1rem;*/
231+
}
232+
233+
.margin-right-auto{
234+
margin-right: auto;
235+
}
236+
237+
218238
/* slider css */
219239

240+
.number-input-box{
241+
font-size: larger;
242+
max-width: 12rem;
243+
}
244+
220245
.slider-labels {
221246
white-space: nowrap;
247+
text-align: left;
222248
display: inline-block;
223249
margin-top: 1rem;
250+
margin-right: auto;
251+
/*width: 100%;*/
224252
}
225253

226254
.slider-widget {
@@ -229,5 +257,5 @@ dl {
229257

230258
.slider-group {
231259
margin-top: -1rem;
232-
margin-bottom: 1rem;
260+
/*margin-bottom: 1rem;*/
233261
}

bayestesting.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ function animationIntervalHandler(){
126126
}
127127
}
128128

129+
/**
130+
I had to write my own Lambert-W implementation, because the only version I could find online
131+
cannot handle input less than $$e$$. The function uses the well-known Halley's method, which
132+
is just Newton's method but with one more term of the Taylor series. I use an initial guess
133+
of $$w=1$$ regardless of the value of the input.
134+
*/
129135
function lambertW(x, tol) {
130136
if (!(tol)){
131137
tol = 0.00000001
@@ -144,14 +150,20 @@ function lambertW(x, tol) {
144150
return w;
145151
}
146152

153+
/**
154+
The number of tests required is given by
155+
$$f(s) = n \left(\text{sense} \left(1-(1-i)^s\right)+(1-\text{spec}) (1-i)^s\right)+\frac{n}{s}$$,
156+
which can be minimized with standard freshman calculus. Mathematica can find the solution to
157+
$$f'(s)=0$$, which involves the Lambert-W function.
158+
*/
147159
function optimalPoolSize(sensitivity, specificity, infection_rate){
160+
148161
// ln(1-i)
149162
let lnomi = Math.log(1 - infection_rate);
150163
// 1 - sensitivity - specificity
151164
let omsms = 1 - sensitivity - specificity;
152165

153166
return 2 * lambertW(-lnomi * Math.sqrt(omsms/lnomi)/(2*omsms)) / lnomi;
154-
// return lnomi * Math.sqrt(omsms/lnomi)/(2*omsms);
155167
}
156168

157169
function getRandomSample(array, count) {
@@ -184,6 +196,7 @@ function sliderUpdate(val, valueLabel) {
184196
DEFAULT_VALUES.infection_rate = Number(document.querySelector('#infection_rate_slider').value);
185197
DEFAULT_VALUES.people_count = Number(document.querySelector('#people_count_slider').value);
186198
DEFAULT_VALUES.pool_size = Number(document.querySelector('#pool_size_slider').value);
199+
DEFAULT_VALUES.cost_per_test = Number(document.querySelector('#cost_per_test_slider').value);
187200

188201
computeNaiveMetrics();
189202
computeSamplePoolingMetrics();
@@ -229,11 +242,13 @@ function updateMetrics(){
229242
'people_count',
230243
'pool_size',
231244
'tests_used',
232-
'optimal_pool_size'
245+
'optimal_pool_size',
246+
'min_tests_possible'
233247
];
234248
let currency = [
235249
'total_cost',
236-
'cost_per_test_label'
250+
'cost_per_test_out',
251+
'optimal_cost'
237252
]
238253

239254
let metrics;
@@ -493,7 +508,19 @@ function computeSamplePoolingMetrics() {
493508
let pooling_tests_used = people_count*(pool_positive + 1.0/pool_size);
494509

495510
spmetrics = computeMetrics(positive_given_infected, negative_given_not_infected, pooling_tests_used);
496-
spmetrics['optimal_pool_size'] = optimalPoolSize(sensitivity, specificity, infection_rate);
511+
512+
// Computation of Optimal Values //
513+
514+
optimal_pool_size = optimalPoolSize(sensitivity, specificity, infection_rate);
515+
spmetrics['optimal_pool_size'] = optimal_pool_size;
516+
// This just reiterates the computation of number of tests but with `optimal_pool_size`.
517+
pool_not_infected = (1 - infection_rate)**optimal_pool_size;
518+
pool_infected = 1 - pool_not_infected;
519+
pool_positive = sensitivity*pool_infected + false_positive_rate * pool_not_infected;
520+
pooling_tests_used = people_count*(pool_positive + 1.0/optimal_pool_size);
521+
let optimal_total_cost = DEFAULT_VALUES.cost_per_test * pooling_tests_used;
522+
spmetrics['optimal_cost'] = optimal_total_cost;
523+
spmetrics['min_tests_possible'] = pooling_tests_used;
497524
spmetrics['prefix'] = 'pooling_';
498525
}
499526

@@ -538,6 +565,8 @@ function processURLParams(){
538565
document.querySelector('#infection_rate_slider').value = DEFAULT_VALUES.infection_rate;
539566
document.querySelector('#people_count_slider').value = DEFAULT_VALUES.people_count;
540567
document.querySelector('#pool_size_slider').value = DEFAULT_VALUES.pool_size;
568+
document.querySelector(`#${DEFAULT_VALUES.select_stats}-tab`).checked = true;
569+
541570
}
542571

543572

tabs.css

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
.tabset > label {
2323
position: relative;
2424
display: inline-block;
25-
margin-top: -1.3rem;
26-
padding-top: 25px;
25+
margin-top: -2.3rem;
26+
padding-top: 40px;
2727
padding-right: 15px;
2828
padding-left: 15px;
29-
padding-bottom: 15px;
29+
padding-bottom: 20px;
30+
border-radius: 1rem;
3031
/*border: 1px solid transparent;*/
3132
border-top: 0;
3233
cursor: pointer;

0 commit comments

Comments
 (0)