Skip to content

Commit

Permalink
Merge branch 'master' into issue-1294-functional-testing-module
Browse files Browse the repository at this point in the history
  • Loading branch information
fcooper8472 committed Feb 22, 2021
2 parents 5e14e66 + 288488e commit a1be118
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 74 deletions.
1 change: 1 addition & 0 deletions .github/workflows/copyright-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/coverage-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docs-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/style-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/unit-test-os-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/unit-test-python-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
if: github.event.pull_request.draft == false
branches:
- '**'

Expand Down
162 changes: 98 additions & 64 deletions pints/_mcmc/_differential_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,25 @@ def current_log_pdfs(self):
""" See :meth:`MultiChainMCMC.current_log_pdfs()`. """
return self._current_log_pdfs

def set_gaussian_error(self, gaussian_error):
def gamma(self):
"""
If ``True`` sets the error process to be a gaussian error,
``N(0, b*)``; if ``False``, it uses a uniform error ``U(-b*, b*)``;
where ``b* = b`` if absolute scaling used and ``b* = mu * b`` if
relative scaling is used instead.
Returns the coefficient ``gamma`` used in updating the position of each
chain.
"""
gaussian_error = bool(gaussian_error)
self._gaussian_error = gaussian_error
return self._gamma

def gamma_switch_rate(self):
"""
Returns the number of steps between iterations where gamma is set to 1
(then reset immediately afterwards).
"""
return self._gamma_switch_rate

def gaussian_error(self):
"""
Returns whether a Gaussian versus uniform error process is used.
"""
return self._gaussian_error

def _initialise(self):
"""
Expand Down Expand Up @@ -167,10 +177,52 @@ def _initialise(self):
# Update sampler state
self._running = True

def n_hyper_parameters(self):
""" See :meth:`TunableMethod.n_hyper_parameters()`. """
return 5

def name(self):
""" See :meth:`pints.MCMCSampler.name()`. """
return 'Differential Evolution MCMC'

def _r_draw(self, i, num_chains):
"""
Chooses two chain indexes uniformly at random such that they are
not the same nor do they equal `i`.
"""
indexes = list(range(num_chains))
indexes.pop(i)
r1, r2 = np.random.choice(indexes, 2, replace=False)
return r1, r2

def relative_scaling(self):
"""
Returns whether an error process whose standard deviation scales
relatively is used (False indicates absolute scale).
"""
return self._relative_scaling

def scale_coefficient(self):
"""
Sets the scale coefficient ``b`` of the error process used in updating
the position of each chain.
"""
return self._b

def set_gamma(self, gamma):
"""
Sets the coefficient ``gamma`` used in updating the position of each
chain.
"""
gamma = float(gamma)
if gamma < 0:
raise ValueError('Gamma must be non-negative.')
self._gamma = gamma

def set_gamma_switch_rate(self, gamma_switch_rate):
"""
Sets the number of steps between iterations where gamma is set to 1
(then reset immediately afterwards)
(then reset immediately afterwards).
"""
if gamma_switch_rate < 1:
raise ValueError('The interval number of steps between ' +
Expand All @@ -180,6 +232,35 @@ def set_gamma_switch_rate(self, gamma_switch_rate):
' gamma=1 iterations must be an integer.')
self._gamma_switch_rate = gamma_switch_rate

def set_gaussian_error(self, gaussian_error):
"""
If ``True`` sets the error process to be a gaussian error,
``N(0, b*)``; if ``False``, it uses a uniform error ``U(-b*, b*)``;
where ``b* = b`` if absolute scaling used and ``b* = mu * b`` if
relative scaling is used instead.
"""
gaussian_error = bool(gaussian_error)
self._gaussian_error = gaussian_error

def set_hyper_parameters(self, x):
"""
The hyper-parameter vector is ``[gamma, gaussian_scale_coefficient,
gamma_switch_rate, gaussian_error, relative_scaling]``.
See :meth:`TunableMethod.set_hyper_parameters()`.
"""
self.set_gamma(x[0])
self.set_scale_coefficient(x[1])
try:
int_x2 = int(x[2])
except (ValueError, TypeError):
raise ValueError('The interval number of steps between ' +
'gamma=1 iterations must be convertable ' +
'to an integer.')
self.set_gamma_switch_rate(int_x2)
self.set_gaussian_error(x[3])
self.set_relative_scaling(x[4])

def set_relative_scaling(self, relative_scaling):
"""
Sets whether to use an error process whose standard deviation scales
Expand All @@ -193,9 +274,15 @@ def set_relative_scaling(self, relative_scaling):
else:
self._b_star = np.repeat(self._b, self._n_parameters)

def name(self):
""" See :meth:`pints.MCMCSampler.name()`. """
return 'Differential Evolution MCMC'
def set_scale_coefficient(self, b):
"""
Sets the scale coefficient ``b`` of the error process used in updating
the position of each chain.
"""
b = float(b)
if b < 0:
raise ValueError('Scale coefficient must be non-negative.')
self._b = b

def tell(self, proposed_log_pdfs):
""" See :meth:`pints.MultiChainMCMC.tell()`. """
Expand Down Expand Up @@ -246,56 +333,3 @@ def tell(self, proposed_log_pdfs):
# Return samples to add to chains
self._current.setflags(write=False)
return self._current

def set_scale_coefficient(self, b):
"""
Sets the scale coefficient ``b`` of the error process used in updating
the position of each chain.
"""
b = float(b)
if b < 0:
raise ValueError('Scale coefficient must be non-negative.')
self._b = b

def set_gamma(self, gamma):
"""
Sets the coefficient ``gamma`` used in updating the position of each
chain.
"""
gamma = float(gamma)
if gamma < 0:
raise ValueError('Gamma must be non-negative.')
self._gamma = gamma

def n_hyper_parameters(self):
""" See :meth:`TunableMethod.n_hyper_parameters()`. """
return 5

def set_hyper_parameters(self, x):
"""
The hyper-parameter vector is ``[gamma, gaussian_scale_coefficient,
gamma_switch_rate, gaussian_error, relative_scaling]``.
See :meth:`TunableMethod.set_hyper_parameters()`.
"""
self.set_gamma(x[0])
self.set_scale_coefficient(x[1])
try:
int_x2 = int(x[2])
except (ValueError, TypeError):
raise ValueError('The interval number of steps between ' +
'gamma=1 iterations must be convertable ' +
'to an integer.')
self.set_gamma_switch_rate(int_x2)
self.set_gaussian_error(x[3])
self.set_relative_scaling(x[4])

def _r_draw(self, i, num_chains):
"""
Chooses two chain indexes uniformly at random such that they are
not the same nor do they equal `i`.
"""
indexes = list(range(num_chains))
indexes.pop(i)
r1, r2 = np.random.choice(indexes, 2, replace=False)
return r1, r2
37 changes: 37 additions & 0 deletions pints/tests/test_mcmc_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,43 @@ def f(x):
self.assertEqual(chains.shape[2], n_parameters)
self.assertIs(chains, mcmc.chains())

def test_hyperparameters_constant(self):
# Test that sampler hyperparameter remain same before and after run

# single chain method
n_chains = 1
x0 = np.array(self.real_parameters) * 1.1
xs = [x0]
mcmc = pints.MCMCController(
self.log_posterior, n_chains, xs, method=pints.HamiltonianMCMC)
step_size = 0.77
for sampler in mcmc.samplers():
sampler.set_leapfrog_step_size(step_size)
mcmc.set_max_iterations(5)
mcmc.set_log_to_screen(False)
mcmc.run()
for sampler in mcmc.samplers():
self.assertEqual(sampler.leapfrog_step_size()[0], step_size)

# test multiple chain method
# Set up problem for 10 chains
x0 = np.array(self.real_parameters)
xs = []
for i in range(10):
f = 0.9 + 0.2 * np.random.rand()
xs.append(x0 * f)
n_chains = len(xs)

meth = pints.DifferentialEvolutionMCMC
mcmc = pints.MCMCController(
self.log_posterior, n_chains, xs, method=meth)
switch_rate = 4
mcmc.samplers()[0].set_gamma_switch_rate(switch_rate)
mcmc.set_max_iterations(5)
mcmc.set_log_to_screen(False)
mcmc.run()
self.assertEqual(mcmc.samplers()[0].gamma_switch_rate(), switch_rate)

def test_multi(self):
# Test with a multi-chain method

Expand Down
20 changes: 10 additions & 10 deletions pints/tests/test_mcmc_differential_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,31 +149,31 @@ def test_set_hyper_parameters(self):
self.assertEqual(mcmc.n_hyper_parameters(), 5)

mcmc.set_hyper_parameters([0.5, 0.6, 20, 0, 0])
self.assertEqual(mcmc._gamma, 0.5)
self.assertEqual(mcmc._b, 0.6)
self.assertEqual(mcmc._gamma_switch_rate, 20)
self.assertTrue(not mcmc._gaussian_error)
self.assertTrue(not mcmc._relative_scaling)
self.assertEqual(mcmc.gamma(), 0.5)
self.assertEqual(mcmc.scale_coefficient(), 0.6)
self.assertEqual(mcmc.gamma_switch_rate(), 20)
self.assertTrue(not mcmc.gaussian_error())
self.assertTrue(not mcmc.relative_scaling())

mcmc.set_gamma(0.5)
self.assertEqual(mcmc._gamma, 0.5)
self.assertEqual(mcmc.gamma(), 0.5)
self.assertRaisesRegex(ValueError,
'non-negative', mcmc.set_gamma, -1)

mcmc.set_scale_coefficient(1)
self.assertTrue(not mcmc._relative_scaling)
self.assertTrue(not mcmc.relative_scaling())
self.assertRaisesRegex(ValueError,
'non-negative', mcmc.set_scale_coefficient, -1)

mcmc.set_gamma_switch_rate(11)
self.assertEqual(mcmc._gamma_switch_rate, 11)
self.assertEqual(mcmc.gamma_switch_rate(), 11)
self.assertRaisesRegex(
ValueError, 'integer', mcmc.set_gamma_switch_rate, 11.5)
self.assertRaisesRegex(
ValueError, 'exceed 1', mcmc.set_gamma_switch_rate, 0)

mcmc.set_gaussian_error(False)
self.assertTrue(not mcmc._gaussian_error)
self.assertTrue(not mcmc.gaussian_error())

mcmc.set_relative_scaling(0)
self.assertTrue(np.array_equal(mcmc._b_star,
Expand All @@ -184,7 +184,7 @@ def test_set_hyper_parameters(self):

# test implicit conversion to int
mcmc.set_hyper_parameters([0.5, 0.6, 20.2, 0, 0])
self.assertEqual(mcmc._gamma_switch_rate, 20)
self.assertEqual(mcmc.gamma_switch_rate(), 20)
self.assertRaisesRegex(
ValueError, 'convertable to an integer',
mcmc.set_hyper_parameters, (0.5, 0.6, 'sdf', 0, 0))
Expand Down

0 comments on commit a1be118

Please sign in to comment.