Skip to content

Commit 764bfb0

Browse files
authored
Merge pull request #505 from pybop-team/bugfixes-for-logposterior-transformations
Fixes for LogPosterior w/ GaussianLogLikelihood
2 parents 782b57f + 4f926ef commit 764bfb0

File tree

6 files changed

+30
-17
lines changed

6 files changed

+30
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
## Bug Fixes
66

7+
- [#505](https://github.com/pybop-team/PyBOP/pull/505) - Bug fixes for `LogPosterior` with transformed `GaussianLogLikelihood` likelihood.
8+
79
## Breaking Changes
810

911
# [v24.9.0](https://github.com/pybop-team/PyBOP/tree/v24.9.0) - 2024-09-10

examples/scripts/spm_MAP.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
bounds=[0.3, 0.8],
2626
initial_value=0.653,
2727
true_value=parameter_set["Negative electrode active material volume fraction"],
28+
transformation=pybop.LogTransformation(),
2829
),
2930
pybop.Parameter(
3031
"Positive electrode active material volume fraction",
3132
prior=pybop.Uniform(0.3, 0.8),
3233
bounds=[0.4, 0.7],
3334
initial_value=0.657,
3435
true_value=parameter_set["Positive electrode active material volume fraction"],
36+
transformation=pybop.LogTransformation(),
3537
),
3638
)
3739

@@ -44,7 +46,7 @@
4446
),
4547
]
4648
)
47-
values = model.predict(initial_state={"Initial SoC": 0.7}, experiment=experiment)
49+
values = model.predict(initial_state={"Initial SoC": 0.5}, experiment=experiment)
4850
corrupt_values = values["Voltage [V]"].data + np.random.normal(
4951
0, sigma, len(values["Voltage [V]"].data)
5052
)
@@ -60,7 +62,7 @@
6062

6163
# Generate problem, cost function, and optimisation class
6264
problem = pybop.FittingProblem(model, parameters, dataset)
63-
cost = pybop.LogPosterior(pybop.GaussianLogLikelihoodKnownSigma(problem, sigma0=sigma))
65+
cost = pybop.LogPosterior(pybop.GaussianLogLikelihood(problem))
6466
optim = pybop.IRPropMin(
6567
cost,
6668
sigma0=0.05,

pybop/costs/_likelihoods.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def _add_single_sigma(self, index, value):
141141
Parameter(
142142
f"Sigma for output {index+1}",
143143
initial_value=value,
144-
prior=Uniform(0.5 * value, 1.5 * value),
144+
prior=Uniform(1e-3 * value, 1e3 * value),
145145
bounds=[1e-8, 3 * value],
146146
)
147147
)
@@ -235,10 +235,12 @@ def __init__(
235235
super().__init__(problem=log_likelihood.problem)
236236
self.gradient_step = gradient_step
237237

238-
# Store the likelihood and prior
238+
# Store the likelihood, prior, update parameters and transformation
239+
self.join_parameters(log_likelihood.parameters)
239240
self._log_likelihood = log_likelihood
240-
self._parameters = self._log_likelihood.parameters
241-
self._has_separable_problem = self._log_likelihood.has_separable_problem
241+
242+
for attr in ["transformation", "_has_separable_problem"]:
243+
setattr(self, attr, getattr(log_likelihood, attr))
242244

243245
if log_prior is None:
244246
self._prior = JointLogPrior(*self._parameters.priors())

tests/integration/test_optimisation_options.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ class TestOptimisation:
1313

1414
@pytest.fixture(autouse=True)
1515
def setup(self):
16-
self.ground_truth = np.asarray([0.55, 0.55]) + np.random.normal(
17-
loc=0.0, scale=0.05, size=2
16+
self.ground_truth = np.clip(
17+
np.asarray([0.55, 0.55]) + np.random.normal(loc=0.0, scale=0.05, size=2),
18+
a_min=0.4,
19+
a_max=0.75,
1820
)
1921

2022
@pytest.fixture

tests/integration/test_transformation.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def noise(self, sigma, values):
6868
pybop.SumofPower,
6969
pybop.Minkowski,
7070
pybop.LogPosterior,
71+
pybop.LogPosterior, # Second for GaussianLogLikelihood
7172
]
7273
)
7374
def cost_cls(self, request):
@@ -90,13 +91,19 @@ def cost(self, model, parameters, init_soc, cost_cls):
9091
problem = pybop.FittingProblem(model, parameters, dataset)
9192

9293
# Construct the cost
94+
first_map = True
9395
if cost_cls is pybop.GaussianLogLikelihoodKnownSigma:
9496
return cost_cls(problem, sigma0=self.sigma0)
9597
elif cost_cls is pybop.GaussianLogLikelihood:
9698
return cost_cls(problem)
99+
elif cost_cls is pybop.LogPosterior and first_map:
100+
first_map = False
101+
return cost_cls(log_likelihood=pybop.GaussianLogLikelihood(problem))
97102
elif cost_cls is pybop.LogPosterior:
98103
return cost_cls(
99-
pybop.GaussianLogLikelihoodKnownSigma(problem, sigma0=self.sigma0)
104+
log_likelihood=pybop.GaussianLogLikelihoodKnownSigma(
105+
problem, sigma0=self.sigma0
106+
)
100107
)
101108
else:
102109
return cost_cls(problem)
@@ -114,7 +121,7 @@ def test_thevenin_transformation(self, optimiser, cost):
114121
optim = optimiser(
115122
cost=cost,
116123
sigma0=[0.03, 0.03, 1e-3]
117-
if isinstance(cost, pybop.GaussianLogLikelihood)
124+
if isinstance(cost, (pybop.GaussianLogLikelihood, pybop.LogPosterior))
118125
else [0.03, 0.03],
119126
max_unchanged_iterations=35,
120127
absolute_tolerance=1e-6,
@@ -125,7 +132,7 @@ def test_thevenin_transformation(self, optimiser, cost):
125132
x, final_cost = optim.run()
126133

127134
# Add sigma0 to ground truth for GaussianLogLikelihood
128-
if isinstance(optim.cost, pybop.GaussianLogLikelihood):
135+
if isinstance(optim.cost, (pybop.GaussianLogLikelihood, pybop.LogPosterior)):
129136
self.ground_truth = np.concatenate(
130137
(self.ground_truth, np.asarray([self.sigma0]))
131138
)

tests/unit/test_posterior.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,13 @@ def prior(self):
6868
def test_log_posterior_construction(self, likelihood, prior):
6969
# Test log posterior construction
7070
posterior = pybop.LogPosterior(likelihood, prior)
71+
keys = likelihood.parameters.keys()
7172

7273
assert posterior._log_likelihood == likelihood
7374
assert posterior._prior == prior
74-
75-
# Test log posterior construction without parameters
76-
likelihood.parameters.priors = None
77-
78-
with pytest.raises(TypeError, match="'NoneType' object is not callable"):
79-
pybop.LogPosterior(likelihood, log_prior=None)
75+
assert posterior.parameters[keys[0]] == likelihood.parameters[keys[0]]
76+
assert posterior.has_separable_problem == likelihood.has_separable_problem
77+
assert posterior.transformation == likelihood.transformation
8078

8179
@pytest.mark.unit
8280
def test_log_posterior_construction_no_prior(self, likelihood):

0 commit comments

Comments
 (0)