Skip to content

Commit 47cd2ae

Browse files
authored
Merge pull request #424 from elfi-dev/dev
Release v0.8.4
2 parents b4663c8 + 6b1b17a commit 47cd2ae

27 files changed

+596
-244
lines changed

CHANGELOG.rst

+23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
Changelog
22
=========
33

4+
0.8.4 (2021-06-13)
5+
------------------
6+
- Modify Lotka-Volterra model's priors as many methods do not support discrete random variables.
7+
- Fix acquisition index in state plot
8+
- Reformat `summary()` for `Sample(ParameterInferenceResult)`
9+
- Fix linting in `arch.py`
10+
- Add summary statistics to Lotka-Volterra model
11+
- Add boolean `observation_noise` option to `lotka_volterra`
12+
- Add parameter names as an optional input in model prior and fix the parameter order in priors used in BOLFI and BOLFIRE
13+
- Add feature names as an optional input and make training data size a required input in BOLFIRE
14+
- Fix the observed property in simulator nodes
15+
- Fix default outputs in generate
16+
- Add docstring description to ARCH-model
17+
- Make MAP estimates calculation in BOLFIRE optional and based on log-posterior
18+
- Use batch system to run simulations in BOLFIRE
19+
- Use `target_model.parameter_names` from instead of `model.parameter_names` in `BOLFIRE`
20+
- Extract BO results using `target_model.parameter_names` from instead of `model.parameter_names`
21+
- Update tox.ini
22+
- Add option to use additive acquisition cost in LCBSC
23+
- Change sigma_proposals-input in metropolis from list to dict
24+
- Fix is_array in utils
25+
- Fix acq_noise_var-bug in acquisition.py. Influenced BOLFI.
26+
427
0.8.3 (2021-02-17)
528
------------------
629
- Add a new inference method: BOLFIRE

MANIFEST.in

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
recursive-include elfi/examples/cpp *.txt *.cpp Makefile
2+
include docs/description.rst
3+
include requirements.txt

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
**Version 0.8.3 released!** See the [CHANGELOG](CHANGELOG.rst) and [notebooks](https://github.com/elfi-dev/notebooks).
1+
**Version 0.8.4 released!** See the [CHANGELOG](CHANGELOG.rst) and [notebooks](https://github.com/elfi-dev/notebooks).
22

33
<img src="https://raw.githubusercontent.com/elfi-dev/elfi/dev/docs/logos/elfi_logo_text_nobg.png" width="200" />
44

docs/usage/BOLFI.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ surface is updated after each batch (especially so if the noise is 0!).
9292
.. code:: ipython3
9393
9494
bolfi = elfi.BOLFI(log_d, batch_size=1, initial_evidence=20, update_interval=10,
95-
bounds={'t1':(-2, 2), 't2':(-1, 1)}, acq_noise_var=[0.1, 0.1], seed=seed)
95+
bounds={'t1':(-2, 2), 't2':(-1, 1)}, acq_noise_var=0.1, seed=seed)
9696
9797
Sometimes you may have some samples readily available. You could then
9898
initialize the GP model with a dictionary of previous results by giving

elfi/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
__email__ = 'elfi-support@hiit.fi'
3232

3333
# make sure __version_ is on the last non-empty line (read by setup.py)
34-
__version__ = '0.8.3'
34+
__version__ = '0.8.4'

elfi/examples/arch.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,21 @@ def get_model(n_obs=100, true_params=None, seed_obs=None, n_lags=5):
6363

6464

6565
def arch(t1, t2, n_obs=100, batch_size=1, random_state=None):
66-
"""Generate a sequence of samples from the ARCH(1) model.
66+
r"""Generate a sequence of samples from the ARCH(1) regression model.
67+
68+
Autoregressive conditional heteroskedasticity (ARCH) sequence describes the variance
69+
of the error term as a function of previous error terms.
70+
71+
x_i = t_1 x_{i-1} + \epsilon_i
72+
73+
\epsilon_i = w_i \sqrt{0.2 + t_2 \epsilon_{i-1}^2}
74+
75+
where w_i is white noise ~ N(0,1) independent of \epsilon_0 ~ N(0,1)
76+
77+
References
78+
----------
79+
Engle, R.F. (1982). Autoregressive Conditional Heteroscedasticity with
80+
Estimates of the Variance of United Kingdom Inflation. Econometrica, 50(4): 987-1007
6781
6882
Parameters
6983
----------
@@ -87,6 +101,7 @@ def arch(t1, t2, n_obs=100, batch_size=1, random_state=None):
87101
e = E(t2, n_obs, batch_size, random_state)
88102
for i in range(1, n_obs + 1):
89103
y[:, i] = t1 * y[:, i - 1] + e[:, i]
104+
90105
return y[:, 1:]
91106

92107

elfi/examples/lotka_volterra.py

+104-32
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ def lotka_volterra(r1, r2, r3, prey_init=50, predator_init=100, sigma=0., n_obs=
7474

7575
n_full = 20000
7676
stock = np.empty((batch_size, n_full, 2), dtype=np.int32)
77-
stock[:, 0, 0] = prey_init
78-
stock[:, 0, 1] = predator_init
77+
# As we use approximate continuous priors for prey_init and
78+
# predator_init, we'll round them down to closest integers
79+
stock[:, 0, 0] = np.floor(prey_init)
80+
stock[:, 0, 1] = np.floor(predator_init)
7981
stoichiometry = np.array([[1, 0], [-1, 1], [0, -1], [0, 0]], dtype=np.int32)
8082
times = np.empty((batch_size, n_full))
8183
times[:, 0] = 0
@@ -141,9 +143,11 @@ def lotka_volterra(r1, r2, r3, prey_init=50, predator_init=100, sigma=0., n_obs=
141143
return stock_out
142144

143145

144-
def get_model(n_obs=50, true_params=None, seed_obs=None, **kwargs):
146+
def get_model(n_obs=50, true_params=None, observation_noise=False, seed_obs=None, **kwargs):
145147
"""Return a complete Lotka-Volterra model in inference task.
146148
149+
Including observation noise to system is optional.
150+
147151
Parameters
148152
----------
149153
n_obs : int, optional
@@ -152,6 +156,8 @@ def get_model(n_obs=50, true_params=None, seed_obs=None, **kwargs):
152156
Parameters with which the observed data is generated.
153157
seed_obs : int, optional
154158
Seed for the observed data generation.
159+
observation_noise : bool, optional
160+
Whether or not add normal noise to observations.
155161
156162
Returns
157163
-------
@@ -160,34 +166,117 @@ def get_model(n_obs=50, true_params=None, seed_obs=None, **kwargs):
160166
"""
161167
logger = logging.getLogger()
162168
if true_params is None:
163-
true_params = [1.0, 0.005, 0.6, 50, 100, 10.]
169+
if observation_noise:
170+
true_params = [1.0, 0.005, 0.6, 50, 100, 10.]
171+
else:
172+
true_params = [1.0, 0.005, 0.6, 50, 100, 0.]
173+
else:
174+
if observation_noise:
175+
if len(true_params) != 6:
176+
raise ValueError(
177+
"Option observation_noise = True."
178+
" Provide six input parameters."
179+
)
180+
else:
181+
if len(true_params) != 5:
182+
raise ValueError(
183+
"Option observation_noise = False."
184+
" Provide five input parameters."
185+
)
186+
true_params = true_params + [0]
164187

165188
kwargs['n_obs'] = n_obs
166189
y_obs = lotka_volterra(*true_params, random_state=np.random.RandomState(seed_obs), **kwargs)
167190

168191
m = elfi.ElfiModel()
169192
sim_fn = partial(lotka_volterra, **kwargs)
170-
priors = []
171-
sumstats = []
193+
priors = [
194+
elfi.Prior(ExpUniform, -6., 2., model=m, name='r1'),
195+
elfi.Prior(ExpUniform, -6., 2., model=m, name='r2'), # easily kills populations
196+
elfi.Prior(ExpUniform, -6., 2., model=m, name='r3'),
197+
elfi.Prior('normal', 50, np.sqrt(50), model=m, name='prey0'),
198+
elfi.Prior('normal', 100, np.sqrt(100), model=m, name='predator0')
199+
]
172200

173-
priors.append(elfi.Prior(ExpUniform, -6., 2., model=m, name='r1'))
174-
priors.append(elfi.Prior(ExpUniform, -6., 2., model=m, name='r2')) # easily kills populations
175-
priors.append(elfi.Prior(ExpUniform, -6., 2., model=m, name='r3'))
176-
priors.append(elfi.Prior('poisson', 50, model=m, name='prey0'))
177-
priors.append(elfi.Prior('poisson', 100, model=m, name='predator0'))
178-
priors.append(elfi.Prior(ExpUniform, np.log(0.5), np.log(50), model=m, name='sigma'))
201+
if observation_noise:
202+
priors.append(elfi.Prior(ExpUniform, np.log(0.5), np.log(50), model=m, name='sigma'))
179203

180204
elfi.Simulator(sim_fn, *priors, observed=y_obs, name='LV')
181-
sumstats.append(elfi.Summary(partial(pick_stock, species=0), m['LV'], name='prey'))
182-
sumstats.append(elfi.Summary(partial(pick_stock, species=1), m['LV'], name='predator'))
183-
elfi.Distance('sqeuclidean', *sumstats, name='d')
205+
206+
sumstats = [
207+
elfi.Summary(partial(stock_mean, species=0), m['LV'], name='prey_mean'),
208+
elfi.Summary(partial(stock_mean, species=1), m['LV'], name='pred_mean'),
209+
elfi.Summary(partial(stock_log_variance, species=0), m['LV'], name='prey_log_var'),
210+
elfi.Summary(partial(stock_log_variance, species=1), m['LV'], name='pred_log_var'),
211+
elfi.Summary(partial(stock_autocorr, species=0, lag=1), m['LV'], name='prey_autocorr_1'),
212+
elfi.Summary(partial(stock_autocorr, species=1, lag=1), m['LV'], name='pred_autocorr_1'),
213+
elfi.Summary(partial(stock_autocorr, species=0, lag=2), m['LV'], name='prey_autocorr_2'),
214+
elfi.Summary(partial(stock_autocorr, species=1, lag=2), m['LV'], name='pred_autocorr_2'),
215+
elfi.Summary(stock_crosscorr, m['LV'], name='crosscorr')
216+
]
217+
218+
elfi.Distance('euclidean', *sumstats, name='d')
184219

185220
logger.info("Generated %i observations with true parameters r1: %.1f, r2: %.3f, r3: %.1f, "
186221
"prey0: %i, predator0: %i, sigma: %.1f.", n_obs, *true_params)
187222

188223
return m
189224

190225

226+
def stock_mean(stock, species=0, mu=0, std=1):
227+
"""Calculate the mean of the trajectory by species."""
228+
stock = np.atleast_2d(stock[:, :, species])
229+
mu_x = np.mean(stock, axis=1)
230+
231+
return (mu_x - mu) / std
232+
233+
234+
def stock_log_variance(stock, species=0, mu=0, std=1):
235+
"""Calculate the log variance of the trajectory by species."""
236+
stock = np.atleast_2d(stock[:, :, species])
237+
var_x = np.var(stock, axis=1, ddof=1)
238+
log_x = np.log(var_x + 1)
239+
240+
return (log_x - mu) / std
241+
242+
243+
def stock_autocorr(stock, species=0, lag=1, mu=0, std=1):
244+
"""Calculate the autocorrelation of lag n of the trajectory by species."""
245+
stock = np.atleast_2d(stock[:, :, species])
246+
n_obs = stock.shape[1]
247+
248+
mu_x = np.mean(stock, axis=1, keepdims=True)
249+
std_x = np.std(stock, axis=1, ddof=1, keepdims=True)
250+
sx = ((stock - np.repeat(mu_x, n_obs, axis=1)) / np.repeat(std_x, n_obs, axis=1))
251+
sx_t = sx[:, lag:]
252+
sx_s = sx[:, :-lag]
253+
254+
C = np.sum(sx_t * sx_s, axis=1) / (n_obs - 1)
255+
256+
return (C - mu) / std
257+
258+
259+
def stock_crosscorr(stock, mu=0, std=1):
260+
"""Calculate the cross correlation of the species trajectories."""
261+
n_obs = stock.shape[1]
262+
263+
x_preys = stock[:, :, 0] # preys
264+
x_preds = stock[:, :, 1] # predators
265+
266+
mu_preys = np.mean(x_preys, axis=1, keepdims=True)
267+
mu_preds = np.mean(x_preds, axis=1, keepdims=True)
268+
std_preys = np.std(x_preys, axis=1, keepdims=True)
269+
std_preds = np.std(x_preds, axis=1, keepdims=True)
270+
s_preys = ((x_preys - np.repeat(mu_preys, n_obs, axis=1))
271+
/ np.repeat(std_preys, n_obs, axis=1))
272+
s_preds = ((x_preds - np.repeat(mu_preds, n_obs, axis=1))
273+
/ np.repeat(std_preds, n_obs, axis=1))
274+
275+
C = np.sum(s_preys * s_preds, axis=1) / (n_obs - 1)
276+
277+
return (C - mu) / std
278+
279+
191280
class ExpUniform(elfi.Distribution):
192281
r"""Prior distribution for parameter.
193282
@@ -235,20 +324,3 @@ def pdf(cls, x, a, b):
235324
p = np.where((x < np.exp(a)) | (x > np.exp(b)), 0, np.reciprocal(x))
236325
p /= (b - a) # normalize
237326
return p
238-
239-
240-
def pick_stock(stock, species):
241-
"""Return the stock for single species.
242-
243-
Parameters
244-
----------
245-
stock : np.array
246-
species : int
247-
0 for prey, 1 for predator.
248-
249-
Returns
250-
-------
251-
np.array
252-
253-
"""
254-
return stock[:, :, species]

elfi/executor.py

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def get_execution_order(cls, G):
107107
# Filter those output nodes who have an operation to run
108108
needed = tuple(sorted(node for node in output_nodes if 'operation' in G.nodes[node]))
109109

110+
if len(needed) == 0:
111+
return []
112+
110113
if needed not in cache:
111114
# Resolve the nodes that need to be executed in the graph
112115
nodes_to_execute = set(needed)

0 commit comments

Comments
 (0)