@@ -214,23 +214,24 @@ def test_logp(self):
214
214
class TestGrassiaIIGeometric :
215
215
class TestRandomVariable (BaseTestDistributionRandom ):
216
216
pymc_dist = GrassiaIIGeometric
217
- pymc_dist_params = {"r" : .5 , "alpha" : 2.0 }
218
- expected_rv_op_params = {"r" : .5 , "alpha" : 2.0 }
217
+ pymc_dist_params = {"r" : .5 , "alpha" : 2.0 , "time_covariates_sum" : 1.0 }
218
+ expected_rv_op_params = {"r" : .5 , "alpha" : 2.0 , "time_covariates_sum" : 1.0 }
219
219
tests_to_run = [
220
220
"check_pymc_params_match_rv_op" ,
221
221
"check_rv_size" ,
222
222
]
223
223
224
224
def test_random_basic_properties (self ):
225
- # Test standard parameter values
225
+ # Test standard parameter values with time covariates
226
226
discrete_random_tester (
227
227
dist = self .pymc_dist ,
228
228
paramdomains = {
229
229
"r" : Domain ([0.5 , 1.0 , 2.0 ], edges = (None , None )), # Standard values
230
230
"alpha" : Domain ([0.5 , 1.0 , 2.0 ], edges = (None , None )), # Standard values
231
+ "time_covariates_sum" : Domain ([- 1.0 , 1.0 , 2.0 ], edges = (None , None )), # Time covariates
231
232
},
232
- ref_rand = lambda r , alpha , size : np .random .geometric (
233
- 1 - np .exp (- np .random .gamma (r , 1 / alpha , size = size )), size = size
233
+ ref_rand = lambda r , alpha , time_covariates_sum , size : np .random .geometric (
234
+ 1 - np .exp (- np .random .gamma (r , 1 / alpha , size = size ) * np . exp ( time_covariates_sum ) ), size = size
234
235
),
235
236
)
236
237
@@ -240,20 +241,21 @@ def test_random_basic_properties(self):
240
241
paramdomains = {
241
242
"r" : Domain ([0.01 , 0.1 ], edges = (None , None )), # Small r values
242
243
"alpha" : Domain ([10.0 , 100.0 ], edges = (None , None )), # Large alpha values
244
+ "time_covariates_sum" : Domain ([0.0 , 1.0 ], edges = (None , None )), # Time covariates
243
245
},
244
- ref_rand = lambda r , alpha , size : np .random .geometric (
245
- np .clip (np .random .gamma (r , 1 / alpha , size = size ), 1e-5 , 1.0 ), size = size
246
+ ref_rand = lambda r , alpha , time_covariates_sum , size : np .random .geometric (
247
+ np .clip (np .random .gamma (r , 1 / alpha , size = size ) * np . exp ( time_covariates_sum ) , 1e-5 , 1.0 ), size = size
246
248
),
247
249
)
248
250
249
- @pytest .mark .parametrize ("r,alpha" , [
250
- (0.5 , 1.0 ),
251
- (1.0 , 2.0 ),
252
- (2.0 , 0.5 ),
253
- (5.0 , 1.0 ),
251
+ @pytest .mark .parametrize ("r,alpha,time_covariates_sum " , [
252
+ (0.5 , 1.0 , 0.0 ),
253
+ (1.0 , 2.0 , 1.0 ),
254
+ (2.0 , 0.5 , - 1.0 ),
255
+ (5.0 , 1.0 , None ),
254
256
])
255
- def test_random_moments (self , r , alpha ):
256
- dist = self .pymc_dist .dist (r = r , alpha = alpha , size = 10_000 )
257
+ def test_random_moments (self , r , alpha , time_covariates_sum ):
258
+ dist = self .pymc_dist .dist (r = r , alpha = alpha , time_covariates_sum = time_covariates_sum , size = 10_000 )
257
259
draws = dist .eval ()
258
260
259
261
# Check that all values are positive integers
@@ -269,65 +271,102 @@ def test_random_moments(self, r, alpha):
269
271
def test_logp_basic (self ):
270
272
r = pt .scalar ("r" )
271
273
alpha = pt .scalar ("alpha" )
274
+ time_covariates_sum = pt .scalar ("time_covariates_sum" )
272
275
value = pt .vector ("value" , dtype = "int64" )
273
276
274
- logp = pm .logp (GrassiaIIGeometric .dist (r , alpha ), value )
275
- logp_fn = pytensor .function ([value , r , alpha ], logp )
277
+ logp = pm .logp (GrassiaIIGeometric .dist (r , alpha , time_covariates_sum ), value )
278
+ logp_fn = pytensor .function ([value , r , alpha , time_covariates_sum ], logp )
276
279
277
280
# Test basic properties of logp
278
281
test_value = np .array ([1 , 2 , 3 , 4 , 5 ])
279
282
test_r = 1.0
280
283
test_alpha = 1.0
284
+ test_time_covariates_sum = 1.0
281
285
282
- logp_vals = logp_fn (test_value , test_r , test_alpha )
286
+ logp_vals = logp_fn (test_value , test_r , test_alpha , test_time_covariates_sum )
283
287
assert not np .any (np .isnan (logp_vals ))
284
288
assert np .all (np .isfinite (logp_vals ))
285
289
286
290
# Test invalid values
287
- assert logp_fn (np .array ([0 ]), test_r , test_alpha ) == np .inf # Value must be > 0
291
+ assert logp_fn (np .array ([0 ]), test_r , test_alpha , test_time_covariates_sum ) == np .inf # Value must be > 0
288
292
289
293
with pytest .raises (TypeError ):
290
- logp_fn (np .array ([1.5 ]), test_r , test_alpha ) == - np . inf # Value must be integer
294
+ logp_fn (np .array ([1.5 ]), test_r , test_alpha , test_time_covariates_sum ) # Value must be integer
291
295
292
296
# Test parameter restrictions
293
297
with pytest .raises (ParameterValueError ):
294
- logp_fn (np .array ([1 ]), - 1.0 , test_alpha ) # r must be > 0
298
+ logp_fn (np .array ([1 ]), - 1.0 , test_alpha , test_time_covariates_sum ) # r must be > 0
295
299
296
300
with pytest .raises (ParameterValueError ):
297
- logp_fn (np .array ([1 ]), test_r , - 1.0 ) # alpha must be > 0
301
+ logp_fn (np .array ([1 ]), test_r , - 1.0 , test_time_covariates_sum ) # alpha must be > 0
298
302
299
303
def test_sampling_consistency (self ):
300
304
"""Test that sampling from the distribution produces reasonable results"""
301
305
r = 2.0
302
306
alpha = 1.0
307
+ time_covariates_sum = None
308
+
309
+ # First test direct sampling from the distribution
310
+ dist = GrassiaIIGeometric .dist (r = r , alpha = alpha , time_covariates_sum = time_covariates_sum )
311
+ direct_samples = dist .eval ()
312
+
313
+ # Convert to numpy array if it's not already
314
+ if not isinstance (direct_samples , np .ndarray ):
315
+ direct_samples = np .array ([direct_samples ])
316
+
317
+ # Ensure we have a 1D array
318
+ if direct_samples .ndim == 0 :
319
+ direct_samples = direct_samples .reshape (1 )
320
+
321
+ assert direct_samples .size > 0 , "Direct sampling produced no samples"
322
+ assert np .all (direct_samples > 0 ), "Direct sampling produced non-positive values"
323
+ assert np .all (direct_samples .astype (int ) == direct_samples ), "Direct sampling produced non-integer values"
324
+
325
+ # Then test MCMC sampling
303
326
with pm .Model ():
304
- x = GrassiaIIGeometric ("x" , r = r , alpha = alpha )
327
+ x = GrassiaIIGeometric ("x" , r = r , alpha = alpha , time_covariates_sum = time_covariates_sum )
305
328
trace = pm .sample (chains = 1 , draws = 1000 , random_seed = 42 ).posterior
306
329
307
- samples = trace ["x" ].values .flatten ()
330
+ # Extract samples and ensure they're in the correct shape
331
+ samples = trace ["x" ].values
332
+ assert samples is not None , "No samples were returned from MCMC"
333
+ assert samples .size > 0 , "MCMC sampling produced empty array"
334
+
335
+ if samples .ndim > 1 :
336
+ samples = samples .reshape (- 1 ) # Flatten if needed
308
337
309
338
# Check basic properties of samples
310
- assert np .all (samples > 0 ) # All values should be positive
311
- assert np .all (samples .astype (int ) == samples ) # All values should be integers
339
+ assert samples .size > 0 , "No samples after reshaping"
340
+ assert np .all (samples > 0 ), "Found non-positive values in samples"
341
+ assert np .all (samples .astype (int ) == samples ), "Found non-integer values in samples"
312
342
313
343
# Check mean and variance are reasonable
314
- # (exact values depend on the parameterization)
315
- assert 0 < np .mean (samples ) < np .inf
316
- assert 0 < np .var (samples ) < np .inf
344
+ mean = np .mean (samples )
345
+ var = np .var (samples )
346
+ assert 0 < mean < np .inf , f"Mean { mean } is not in valid range"
347
+ assert 0 < var < np .inf , f"Variance { var } is not in valid range"
348
+
349
+ # Additional checks for distribution properties
350
+ # The mean should be greater than 1 for these parameters
351
+ assert mean > 1 , f"Mean { mean } is not greater than 1"
352
+ # The variance should be positive and finite
353
+ assert var > 0 , f"Variance { var } is not positive"
317
354
318
355
@pytest .mark .parametrize (
319
- "r, alpha, size, expected_shape" ,
356
+ "r, alpha, time_covariates_sum, size, expected_shape" ,
320
357
[
321
- (1.0 , 1.0 , None , ()), # Scalar output
322
- ([1.0 , 2.0 ], 1.0 , None , (2 ,)), # Vector output from r
323
- (1.0 , [1.0 , 2.0 ], None , (2 ,)), # Vector output from alpha
324
- (1.0 , 1.0 , (3 , 2 ), (3 , 2 )), # Explicit size
358
+ (1.0 , 1.0 , 1.0 , None , ()), # Scalar output with covariates
359
+ ([1.0 , 2.0 ], 1.0 , 1.0 , None , (2 ,)), # Vector output from r
360
+ (1.0 , [1.0 , 2.0 ], 1.0 , None , (2 ,)), # Vector output from alpha
361
+ (1.0 , 1.0 , None , None , ()), # No time covariates
362
+ (1.0 , 1.0 , [1.0 , 2.0 ], None , (2 ,)), # Vector output from time covariates
363
+ (1.0 , 1.0 , 1.0 , (3 , 2 ), (3 , 2 )), # Explicit size
325
364
],
326
365
)
327
- def test_support_point (self , r , alpha , size , expected_shape ):
366
+ def test_support_point (self , r , alpha , time_covariates_sum , size , expected_shape ):
328
367
"""Test that support_point returns reasonable values with correct shapes"""
329
368
with pm .Model () as model :
330
- GrassiaIIGeometric ("x" , r = r , alpha = alpha , size = size )
369
+ GrassiaIIGeometric ("x" , r = r , alpha = alpha , time_covariates_sum = time_covariates_sum , size = size )
331
370
332
371
init_point = model .initial_point ()["x" ]
333
372
0 commit comments