1use super::brownian::Rng;
6
7pub struct PoissonProcess {
13 pub rate: f64,
15}
16
17impl PoissonProcess {
18 pub fn new(rate: f64) -> Self {
19 assert!(rate > 0.0, "rate must be positive");
20 Self { rate }
21 }
22
23 pub fn next_arrival(&self, rng: &mut Rng) -> f64 {
26 let u = rng.uniform().max(1e-15);
27 -u.ln() / self.rate
28 }
29
30 pub fn arrivals(&self, rng: &mut Rng, duration: f64) -> Vec<f64> {
32 let mut times = Vec::new();
33 let mut t = 0.0;
34 loop {
35 t += self.next_arrival(rng);
36 if t > duration {
37 break;
38 }
39 times.push(t);
40 }
41 times
42 }
43
44 pub fn count(&self, duration: f64) -> PoissonDistribution {
46 PoissonDistribution {
47 lambda: self.rate * duration,
48 }
49 }
50
51 pub fn counting_path(&self, rng: &mut Rng, duration: f64) -> Vec<(f64, usize)> {
53 let arrivals = self.arrivals(rng, duration);
54 let mut path = Vec::with_capacity(arrivals.len() + 2);
55 path.push((0.0, 0));
56 for (i, &t) in arrivals.iter().enumerate() {
57 path.push((t, i + 1));
58 }
59 path.push((duration, arrivals.len()));
60 path
61 }
62
63 pub fn superpose(&self, other: &PoissonProcess) -> PoissonProcess {
65 PoissonProcess::new(self.rate + other.rate)
66 }
67
68 pub fn thin(&self, p: f64) -> PoissonProcess {
70 assert!(p > 0.0 && p <= 1.0);
71 PoissonProcess::new(self.rate * p)
72 }
73}
74
75pub struct PoissonDistribution {
81 pub lambda: f64,
82}
83
84impl PoissonDistribution {
85 pub fn new(lambda: f64) -> Self {
86 Self { lambda }
87 }
88
89 pub fn pmf(&self, k: usize) -> f64 {
91 (-self.lambda).exp() * self.lambda.powi(k as i32) / factorial(k) as f64
92 }
93
94 pub fn cdf(&self, k: usize) -> f64 {
96 (0..=k).map(|i| self.pmf(i)).sum()
97 }
98
99 pub fn mean(&self) -> f64 {
101 self.lambda
102 }
103
104 pub fn variance(&self) -> f64 {
106 self.lambda
107 }
108
109 pub fn sample(&self, rng: &mut Rng) -> usize {
111 let l = (-self.lambda).exp();
113 let mut k = 0usize;
114 let mut p = 1.0;
115 loop {
116 k += 1;
117 p *= rng.uniform();
118 if p <= l {
119 return k - 1;
120 }
121 }
122 }
123}
124
125fn factorial(n: usize) -> f64 {
126 if n <= 1 {
127 1.0
128 } else {
129 (2..=n).fold(1.0, |acc, i| acc * i as f64)
130 }
131}
132
133pub struct NonHomogeneousPoisson {
139 pub rate_fn: Box<dyn Fn(f64) -> f64>,
141 pub rate_max: f64,
143}
144
145impl NonHomogeneousPoisson {
146 pub fn new(rate_fn: Box<dyn Fn(f64) -> f64>, rate_max: f64) -> Self {
147 Self { rate_fn, rate_max }
148 }
149
150 pub fn arrivals(&self, rng: &mut Rng, duration: f64) -> Vec<f64> {
155 let mut times = Vec::new();
156 let mut t = 0.0;
157 let homogeneous = PoissonProcess::new(self.rate_max);
158
159 loop {
160 t += homogeneous.next_arrival(rng);
161 if t > duration {
162 break;
163 }
164 let acceptance_prob = (self.rate_fn)(t) / self.rate_max;
166 if rng.uniform() < acceptance_prob {
167 times.push(t);
168 }
169 }
170 times
171 }
172
173 pub fn counting_path(&self, rng: &mut Rng, duration: f64) -> Vec<(f64, usize)> {
175 let arrivals = self.arrivals(rng, duration);
176 let mut path = Vec::with_capacity(arrivals.len() + 2);
177 path.push((0.0, 0));
178 for (i, &t) in arrivals.iter().enumerate() {
179 path.push((t, i + 1));
180 }
181 path.push((duration, arrivals.len()));
182 path
183 }
184
185 pub fn expected_count(&self, duration: f64, n_points: usize) -> f64 {
188 let dx = duration / n_points as f64;
189 let mut sum = 0.5 * ((self.rate_fn)(0.0) + (self.rate_fn)(duration));
190 for i in 1..n_points {
191 sum += (self.rate_fn)(i as f64 * dx);
192 }
193 sum * dx
194 }
195}
196
197pub struct CompoundPoisson {
204 pub rate: f64,
206 pub jump_distribution: Box<dyn Fn(&mut Rng) -> f64>,
208}
209
210impl CompoundPoisson {
211 pub fn new(rate: f64, jump_distribution: Box<dyn Fn(&mut Rng) -> f64>) -> Self {
212 Self { rate, jump_distribution }
213 }
214
215 pub fn normal_jumps(rate: f64, jump_mean: f64, jump_std: f64) -> Self {
217 Self {
218 rate,
219 jump_distribution: Box::new(move |rng: &mut Rng| rng.normal_with(jump_mean, jump_std)),
220 }
221 }
222
223 pub fn exponential_jumps(rate: f64, jump_rate: f64) -> Self {
225 Self {
226 rate,
227 jump_distribution: Box::new(move |rng: &mut Rng| {
228 let u = rng.uniform().max(1e-15);
229 -u.ln() / jump_rate
230 }),
231 }
232 }
233
234 pub fn path(&self, rng: &mut Rng, duration: f64) -> Vec<(f64, f64)> {
236 let pp = PoissonProcess::new(self.rate);
237 let arrivals = pp.arrivals(rng, duration);
238 let mut path = Vec::with_capacity(arrivals.len() + 2);
239 path.push((0.0, 0.0));
240 let mut cumsum = 0.0;
241 for &t in &arrivals {
242 cumsum += (self.jump_distribution)(rng);
243 path.push((t, cumsum));
244 }
245 path.push((duration, cumsum));
246 path
247 }
248
249 pub fn jumps(&self, rng: &mut Rng, duration: f64) -> Vec<(f64, f64)> {
251 let pp = PoissonProcess::new(self.rate);
252 let arrivals = pp.arrivals(rng, duration);
253 arrivals
254 .iter()
255 .map(|&t| (t, (self.jump_distribution)(rng)))
256 .collect()
257 }
258}
259
260#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_poisson_arrival_positive() {
270 let pp = PoissonProcess::new(5.0);
271 let mut rng = Rng::new(42);
272 for _ in 0..100 {
273 let t = pp.next_arrival(&mut rng);
274 assert!(t > 0.0, "inter-arrival time should be positive");
275 }
276 }
277
278 #[test]
279 fn test_poisson_mean_count() {
280 let rate = 3.0;
282 let duration = 10.0;
283 let pp = PoissonProcess::new(rate);
284 let trials = 5000;
285 let mut rng = Rng::new(42);
286
287 let total_count: usize = (0..trials)
288 .map(|_| pp.arrivals(&mut rng, duration).len())
289 .sum();
290 let empirical_mean = total_count as f64 / trials as f64;
291 let expected = rate * duration; assert!(
294 (empirical_mean - expected).abs() < 2.0,
295 "mean count {} should be ~{}",
296 empirical_mean,
297 expected
298 );
299 }
300
301 #[test]
302 fn test_poisson_inter_arrivals_exponential() {
303 let rate = 4.0;
305 let pp = PoissonProcess::new(rate);
306 let mut rng = Rng::new(123);
307 let n = 10_000;
308 let inter_arrivals: Vec<f64> = (0..n).map(|_| pp.next_arrival(&mut rng)).collect();
309 let mean = inter_arrivals.iter().sum::<f64>() / n as f64;
310 let expected = 1.0 / rate;
311
312 assert!(
313 (mean - expected).abs() < 0.05,
314 "inter-arrival mean {} should be ~{}",
315 mean,
316 expected
317 );
318 }
319
320 #[test]
321 fn test_poisson_arrivals_sorted() {
322 let pp = PoissonProcess::new(2.0);
323 let mut rng = Rng::new(42);
324 let arrivals = pp.arrivals(&mut rng, 100.0);
325 for w in arrivals.windows(2) {
326 assert!(w[0] < w[1], "arrivals should be sorted");
327 }
328 }
329
330 #[test]
331 fn test_poisson_distribution_pmf() {
332 let pd = PoissonDistribution::new(3.0);
333 assert!((pd.pmf(0) - (-3.0_f64).exp()).abs() < 1e-10);
335 let total: f64 = (0..30).map(|k| pd.pmf(k)).sum();
337 assert!((total - 1.0).abs() < 1e-6);
338 }
339
340 #[test]
341 fn test_poisson_distribution_sample_mean() {
342 let pd = PoissonDistribution::new(5.0);
343 let mut rng = Rng::new(42);
344 let n = 10_000;
345 let mean: f64 = (0..n).map(|_| pd.sample(&mut rng) as f64).sum::<f64>() / n as f64;
346 assert!(
347 (mean - 5.0).abs() < 0.3,
348 "sample mean {} should be ~5",
349 mean
350 );
351 }
352
353 #[test]
354 fn test_nhpp_thinning() {
355 let nhpp = NonHomogeneousPoisson::new(
357 Box::new(|t: f64| 5.0 + 3.0 * t.sin()),
358 8.0,
359 );
360 let mut rng = Rng::new(42);
361 let arrivals = nhpp.arrivals(&mut rng, 10.0);
362 assert!(!arrivals.is_empty());
364 assert!(arrivals.iter().all(|&t| t >= 0.0 && t <= 10.0));
366 }
367
368 #[test]
369 fn test_compound_poisson() {
370 let cp = CompoundPoisson::normal_jumps(2.0, 1.0, 0.5);
371 let mut rng = Rng::new(42);
372 let path = cp.path(&mut rng, 10.0);
373 assert!(path.len() >= 2); assert_eq!(path[0], (0.0, 0.0));
375 }
376
377 #[test]
378 fn test_superpose_and_thin() {
379 let p1 = PoissonProcess::new(3.0);
380 let p2 = PoissonProcess::new(5.0);
381 let merged = p1.superpose(&p2);
382 assert!((merged.rate - 8.0).abs() < 1e-10);
383
384 let thinned = merged.thin(0.5);
385 assert!((thinned.rate - 4.0).abs() < 1e-10);
386 }
387
388 #[test]
389 fn test_counting_path() {
390 let pp = PoissonProcess::new(2.0);
391 let mut rng = Rng::new(42);
392 let path = pp.counting_path(&mut rng, 5.0);
393 assert_eq!(path[0], (0.0, 0));
394 for w in path.windows(2) {
396 assert!(w[1].1 >= w[0].1);
397 }
398 }
399}