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
238
239
240
241
242
243
244
245
246
247
248
//! # Probabilistic Bisector
//!
//! `probabilistic_bisector` provides a probabilistic bisection algorithm for
//! locating roots of scalar objective functions observed in noise.
//!
//! Classical bisection assumes that evaluating an objective function gives an
//! exact sign. In many numerical, simulation, and experimental settings this is
//! not true: repeated evaluations at the same point may produce different
//! values, and the sign of the objective may only be inferable statistically.
//!
//! This crate is designed for that setting.
//!
//! ## Motivation
//!
//! Suppose we want to find a root `x*` of a scalar objective function
//!
//! `text
//! f(x*) = 0
//! `
//!
//! but each call to the objective produces a noisy observation. A single
//! evaluation may not reliably tell us whether `f(x)` is positive or negative.
//!
//! Instead of treating each sign observation as exact, this crate maintains a
//! posterior distribution over the possible root location. Each observation
//! updates that distribution, and the algorithm returns a confidence interval
//! for the root.
//!
//! ## Theoretical basis
//!
//! The implementation follows the probabilistic bisection framework described
//! by Waeber.
//!
//! The algorithm maintains a posterior distribution over the root location on a
//! fixed internal domain. At each step:
//!
//! 1. A query point is selected from the current posterior.
//! 2. The objective is evaluated repeatedly at that point.
//! 3. A curved-boundary sign test determines the sign of the objective to the
//! requested confidence level.
//! 4. The posterior mass is updated according to whether the root is expected
//! to lie to the left or right of the query point.
//! 5. A sequential confidence interval is updated by intersecting the previous
//! interval with the current Waeber-style confidence region.
//!
//! Internally, posterior mass is stored over a partition of the search domain.
//! The posterior is represented in log-space for numerical stability.
//!
//! ## Coordinate scaling
//!
//! The posterior is represented on a numerically convenient internal domain,
//! usually `[0, 1]`. User objective functions are evaluated on their original
//! raw domain.
//!
//! A `Scaler` maps between these coordinate systems. For strictly positive
//! domains spanning several orders of magnitude, logarithmic scaling may be used
//! so that posterior resolution is distributed multiplicatively rather than
//! linearly.
//!
//! ## Implementing an objective
//!
//! Problems are represented by implementing [`RootOracle`].
//!
//! The oracle provides noisy evaluations of the objective. Implementations may
//! be deterministic or stochastic.
//!
//! ```rust
//! use confi::ConfidenceLevel;
//! use probabilistic_bisector::{run, BisectorConfig, RootOracle};
//!
//! struct Linear {
//! root: f64,
//! }
//!
//! impl RootOracle<f64> for Linear {
//! fn evaluate(&mut self, x: f64) -> f64 {
//! x - self.root
//! }
//! }
//!
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let config = BisectorConfig {
//! max_observations: 10000, // Maximum observations in the loop
//! max_knots: 1000, // Maximum knots in the posterior distribution
//! max_sign_evaluations: 1000, // Maximum function calls in evaluating the objective sign
//! rel_tol: 1e-5, // Target relative tolerance
//! tolerance_window: 10, // Number of evaluations relative tolerance should be stable
//! };
//!
//! let result = run(
//! 0.0..10.0, // Search range
//! ConfidenceLevel::new(0.8)?, // Required confidence level
//! Linear { root: 2.5 }, // Problem
//! config,
//! )?;
//!
//! println!("root interval: {:?}", result.interval);
//! println!("termination: {:?}", result.termination);
//! Ok(())
//! }
//! ```
//!
//! ## Stochastic objectives
//!
//! A `RootOracle` may use internal randomness. Since [`RootOracle::evaluate`]
//! takes `&mut self`, objectives can store their own random number generator,
//! allowing reproducible seeded tests.
//!
//! `rust
//! use probabilistic_bisector::RootOracle;
//!
//! struct NoisyLinear {
//! root: f64,
//! index: usize,
//! }
//!
//! impl RootOracle<f64> for NoisyLinear {
//! fn evaluate(&mut self, x: f64) -> f64 {
//! let noise = if self.index % 2 == 0 { 0.001 } else { -0.001 };
//! self.index += 1;
//! x - self.root + noise
//! }
//! }
//! `
//!
//! ## Termination
//!
//! The solver may terminate because:
//!
//! - the requested tolerance was reached,
//! - the maximum iteration budget was reached,
//! - or the objective sign became indeterminate at all useful query points.
//!
//! Sign indeterminacy is not necessarily an error. It usually means that the
//! algorithm has reached the noise floor of the objective: additional samples at
//! nearby points do not determine the sign reliably within the configured
//! evaluation budget -> Given the noise in the objective function the requested
//! tolerance might be unachievable.
//!
//! ## Output
//!
//! Successful runs return a result containing:
//!
//! - a confidence interval for the root,
//! - execution summary information,
//! - and the reason the solver terminated.
//!
//! Exceptional failures are reserved for invalid inputs, invalid oracle values,
//! or internal invariant violations.
pub
pub
use InferenceState;
pub use ConfidenceLevel;
pub use ;
use ;
pub use PBError;
pub use BisectionError;
use ;
use ;
use SupportSet;
use RootFinder;
pub use ;
use ;
use Range;
use ;