Skip to content

Commit ca480a8

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 ca480a8

File tree

2 files changed

+26
-6
lines changed

2 files changed

+26
-6
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: 25 additions & 6 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
}
@@ -103,19 +106,25 @@ where
103106
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() {
114-
(n.powf(F::one() - s) - s) * q
119+
let t = if s == F::infinity() {
120+
F::zero()
121+
} else if s != F::one() {
122+
let t = (n.powf(F::one() - s) - s) * q;
123+
debug_assert!(t > F::zero());
124+
t
115125
} else {
116126
F::one() + n.ln()
117127
};
118-
debug_assert!(t > F::zero());
119128
Ok(Zipf { s, t, q })
120129
}
121130

@@ -220,6 +229,16 @@ mod tests {
220229
// TODO: verify that this is a uniform distribution
221230
}
222231

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

0 commit comments

Comments
 (0)