@@ -35,7 +35,10 @@ use std::fmt;
3535// }
3636// }
3737
38- /// [CDVM distribution](https://arxiv.org/pdf/2009.05437),
38+ /// Conditionalized Discrete von Mises (CDVM)
39+ ///
40+ /// This is defined under "Definition 4" in
41+ /// [Families of discrete circular distributions with some novel applications](https://arxiv.org/pdf/2009.05437),
3942/// A unimodal distribution over x in (0, m-1) where m is the number of categories.
4043///
4144/// Note that in while the paper uses μ ∈ [0, 2π), we use μ ∈ [0, m)
@@ -90,6 +93,20 @@ impl Cdvm {
9093 /// * `mu` - mean direction (must be in [0, modulus))
9194 /// * `k` - concentration (must be non-negative)
9295 /// * `modulus` - Number of categories
96+ ///
97+ /// # Example
98+ /// ```rust
99+ /// use rv::prelude::*;
100+ /// use rv::dist::{Cdvm, CdvmError};
101+ ///
102+ /// assert!(matches!(Cdvm::new(5, f64::INFINITY, 0.0), Err(CdvmError::MuNotFinite { .. })));
103+ /// assert!(matches!(Cdvm::new(5, 0.0, f64::INFINITY), Err(CdvmError::KNotFinite { .. })));
104+ /// assert!(matches!(Cdvm::new(5, 0.0, -1.0), Err(CdvmError::KNegative { .. })));
105+ /// assert!(matches!(Cdvm::new(2, 1.0, 1.0), Err(CdvmError::InvalidCategories { .. })));
106+ ///
107+ /// let cdvm = Cdvm::new(3, 1.0, 1.0).expect("valid parameters");
108+ ///
109+ /// ```
93110 pub fn new ( modulus : usize , mu : f64 , k : f64 ) -> Result < Self , CdvmError > {
94111 // Validate parameters
95112 if !mu. is_finite ( ) {
@@ -110,14 +127,14 @@ impl Cdvm {
110127
111128 // Test that dependent fields are properly set
112129 // This is just for testing purposes
113- #[ must_use ]
114- pub fn is_consistent ( & self ) -> bool {
130+ #[ cfg ( test ) ]
131+ fn is_consistent ( & self ) -> bool {
115132 let other = Cdvm :: new ( self . modulus , self . mu , self . k ) . unwrap ( ) ;
116- self . mu == other. mu
117- && self . k == other. k
118- && self . modulus == other. modulus
119- && self . log_norm_const == other. log_norm_const
120- && self . twopi_over_m == other. twopi_over_m
133+ self . mu ( ) == other. mu ( )
134+ && self . k ( ) == other. k ( )
135+ && self . modulus ( ) == other. modulus ( )
136+ && self . log_norm_const ( ) == other. log_norm_const ( )
137+ && self . twopi_over_m ( ) == other. twopi_over_m ( )
121138 }
122139
123140 /// Creates a new CDVM without checking whether the parameters are valid.
@@ -258,6 +275,7 @@ impl_display!(Cdvm);
258275
259276impl std:: error:: Error for CdvmError { }
260277
278+ #[ cfg_attr( coverage_nightly, coverage( off) ) ]
261279impl fmt:: Display for CdvmError {
262280 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
263281 match self {
@@ -322,8 +340,11 @@ impl HasSuffStat<usize> for Cdvm {
322340
323341#[ cfg( test) ]
324342mod tests {
343+ use crate :: misc:: x2_test;
344+
325345 use super :: * ;
326346 use proptest:: prelude:: * ;
347+ use rand:: { SeedableRng , rngs:: SmallRng } ;
327348
328349 const TOL : f64 = 1E-12 ;
329350
@@ -486,4 +507,29 @@ mod tests {
486507 "CDVM not consistent after set_mu: m={}, mu1={}, mu2={}, k={}" , m, mu1, mu2, k) ;
487508 }
488509 }
510+
511+ #[ test]
512+ fn f_is_probability_measure ( ) {
513+ let dist = Cdvm :: new_unchecked ( 10 , 5.0 , 0.5 ) ;
514+
515+ assert:: close ( ( 0 ..10 ) . map ( |i| dist. f ( & i) ) . sum :: < f64 > ( ) , 1.0 , 1e-10 ) ;
516+ }
517+
518+ #[ test]
519+ fn ln_f_agrees_with_draw ( ) {
520+ let mut rng = SmallRng :: from_os_rng ( ) ;
521+ let dist = Cdvm :: new_unchecked ( 10 , 5.0 , 0.5 ) ;
522+
523+ let sample = dist. sample ( 100_000 , & mut rng) ;
524+ let ps: Vec < f64 > = ( 0 ..10 ) . map ( |i| dist. f ( & i) ) . collect ( ) ;
525+
526+ let observed_counts =
527+ sample. into_iter ( ) . fold ( vec ! [ 0 ; 10 ] , |mut acc, x| {
528+ acc[ x] += 1 ;
529+ acc
530+ } ) ;
531+
532+ let ( _, p) = x2_test ( & observed_counts, & ps) ;
533+ assert ! ( p > 0.05 ) ;
534+ }
489535}
0 commit comments