Skip to content

Commit 227e0dd

Browse files
committed
Avoid hangs and debug asserts on invalid parameters for Zipf
The generalized Zipf distribution is not well defined when its normalization constant is infinity (when n is inf and s is <= 1). Also, when s is inf, there is a reasonable limiting distribution, which can be produced with a small special case in Zipf::new.
1 parent fe2a875 commit 227e0dd

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
### Fixes
2020
- Fix `Geometric::new` for small `p > 0` where `1 - p` rounds to 1 (#36)
2121
- Fix panic in `FisherF::new` on almost zero parameters (#39)
22+
- Fix hang and debug assertion in `Zipf::new` on invalid parameters (#41)
2223

2324
## [0.5.2]
2425

src/zipf.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,20 @@ where
6666
/// Error type returned from [`Zipf::new`].
6767
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6868
pub enum Error {
69-
/// `s < 0` or `nan`.
69+
/// `s < 0` or `s` is `nan`
7070
STooSmall,
71-
/// `n < 1`.
71+
/// `n < 1` or `n` is `nan`
7272
NTooSmall,
73+
/// `n = inf` and `s <= 1`
74+
IllDefined,
7375
}
7476

7577
impl fmt::Display for Error {
7678
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7779
f.write_str(match self {
7880
Error::STooSmall => "s < 0 or is NaN in Zipf distribution",
79-
Error::NTooSmall => "n < 1 in Zipf distribution",
81+
Error::NTooSmall => "n < 1 or is NaN in Zipf distribution",
82+
Error::IllDefined => "n = inf and s <= 1 in Zipf distribution",
8083
})
8184
}
8285
}
@@ -100,17 +103,22 @@ where
100103
if !(s >= F::zero()) {
101104
return Err(Error::STooSmall);
102105
}
103-
if n < F::one() {
106+
if !(n >= F::one()) {
104107
return Err(Error::NTooSmall);
105108
}
109+
if n.is_infinite() && s <= F::one() {
110+
return Err(Error::IllDefined);
111+
}
106112
let q = if s != F::one() {
107113
// Make sure to calculate the division only once.
108114
F::one() / (F::one() - s)
109115
} else {
110116
// This value is never used.
111117
F::zero()
112118
};
113-
let t = if s != F::one() {
119+
let t = if s == F::infinity() {
120+
F::one()
121+
} else if s != F::one() {
114122
(n.powf(F::one() - s) - s) * q
115123
} else {
116124
F::one() + n.ln()
@@ -220,6 +228,16 @@ mod tests {
220228
// TODO: verify that this is a uniform distribution
221229
}
222230

231+
#[test]
232+
fn zipf_sample_s_inf() {
233+
let d = Zipf::new(10., f64::infinity()).unwrap();
234+
let mut rng = crate::test::rng(2);
235+
for _ in 0..1000 {
236+
let r = d.sample(&mut rng);
237+
assert!(r == 1.);
238+
}
239+
}
240+
223241
#[test]
224242
fn zipf_sample_large_n() {
225243
let d = Zipf::new(f64::MAX, 1.5).unwrap();

0 commit comments

Comments
 (0)