Skip to content
Snippets Groups Projects
Commit 72235274 authored by Rob Moss's avatar Rob Moss
Browse files

Support parameter-free and state-free models

This may be useful for null models that are not time-varying.
parent 0886c03b
No related branches found
No related tags found
No related merge requests found
Pipeline #17297 passed
......@@ -34,8 +34,10 @@ class Context:
"""
def __init__(self, params):
# Ensure that prior distributions have been defined.
# NOTE: to support models that have no parameters (or even no state
# vector) we need to allow the priors to be an empty dictionary.
if 'prior' not in params['model'] or not params['model']['prior']:
raise ValueError('Model prior distribution(s) are undefined')
params['model']['prior'] = {}
params['epoch'] = params['time']['start']
params['dt'] = 1.0 / params['steps_per_unit']
......
......@@ -68,6 +68,11 @@ def state_vec_dtype(ctx):
Note that this is returned as a list, not a ``numpy.dtype`` value.
"""
sv_dtype = ctx.component['model'].field_types(ctx)
# NOTE: h5py doesn't allow zero-sized dimensions, so state_vec must
# contain *something*; see https://github.com/h5py/h5py/issues/944.
# Here we use a Boolean (single byte); by default it will be `False`.
if len(sv_dtype) == 0:
return [('state_vec', np.bool_)]
return [('state_vec', sv_dtype)]
......
"""Test that pypfilt accepts models with empty state vectors."""
import numpy as np
import pypfilt
def test_empty_state_model():
time_scale = pypfilt.Scalar()
start = 0.0
forecast = 10.0
until = 15.0
# Run the forecast.
state = run_forecast(time_scale, start, forecast, until)
# Sanity check: the model state should remain unchanged.
init_state = state[forecast]['hist'][0]
final_state = state[forecast]['hist'][15]
assert np.array_equal(init_state, final_state)
# Sanity check: there should be no summary tables, since the ModelCIs and
# SimulatedObs tables should have produced no rows.
assert state[forecast]['summary'] == {}
assert state['complete']['summary'] == {}
def run_forecast(time_scale, start, forecast, until):
"""Generate an EmptyModel forecast."""
params = make_params()
params['time']['start'] = start
params['time']['until'] = until
summary = pypfilt.summary.HDF5(params, [])
params['component']['summary'] = summary
params['component']['summary_table'] = {
'model_cints': pypfilt.summary.ModelCIs(),
'sim_obs': pypfilt.summary.SimulatedObs(),
}
return pypfilt.forecast(params, [], [forecast], filename=None)
def make_params(px_count=20, seed=42):
model = EmptyModel()
time_scale = pypfilt.Scalar()
params = pypfilt.default_params(model, time_scale, max_days=15,
px_count=px_count, prng_seed=seed)
params['steps_per_unit'] = 1
params['summary']['from_first_day'] = True
# Write output to the working directory.
params['out_dir'] = '.'
params['tmp_dir'] = '.'
return params
class EmptyModel(pypfilt.Model):
def init(self, ctx, vec):
pass
def update(self, params, step_date, dt, is_fs, prev, curr):
pass
def field_types(self, ctx):
return []
def field_names(self):
return []
def bounds(self):
return {}
def can_smooth(self):
return {}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment