diff --git a/bilby/core/likelihood.py b/bilby/core/likelihood.py index df30f3e4914bcb2a38c36ff7651e0397bb289872..7b90e21c110160b5a089bd860991d1ed9acd12cf 100644 --- a/bilby/core/likelihood.py +++ b/bilby/core/likelihood.py @@ -4,7 +4,7 @@ import numpy as np from scipy.special import gammaln, xlogy from scipy.stats import multivariate_normal -from .utils import infer_parameters_from_function +from .utils import infer_parameters_from_function, infer_args_from_function_except_n_args class Likelihood(object): @@ -570,5 +570,166 @@ class JointLikelihood(Likelihood): return sum([likelihood.noise_log_likelihood() for likelihood in self.likelihoods]) +def function_to_celerite_mean_model(func): + from celerite.modeling import Model as CeleriteModel + return _function_to_gp_model(func, CeleriteModel) + + +def function_to_george_mean_model(func): + from celerite.modeling import Model as GeorgeModel + return _function_to_gp_model(func, GeorgeModel) + + +def _function_to_gp_model(func, cls): + class MeanModel(cls): + parameter_names = tuple(infer_args_from_function_except_n_args(func=func, n=1)) + + def get_value(self, t): + params = {name: getattr(self, name) for name in self.parameter_names} + return func(t, **params) + + def compute_gradient(self, *args, **kwargs): + pass + + return MeanModel + + +class _GPLikelihood(Likelihood): + + def __init__(self, kernel, mean_model, t, y, yerr=1e-6, gp_class=None): + """ + Basic Gaussian Process likelihood interface for `celerite` and `george`. + For `celerite` documentation see: https://celerite.readthedocs.io/en/stable/ + For `george` documentation see: https://george.readthedocs.io/en/latest/ + + Parameters + ========== + kernel: Union[celerite.term.Term, george.kernels.Kernel] + `celerite` or `george` kernel. See the respective package documentation about the usage. + mean_model: Union[celerite.modeling.Model, george.modeling.Model] + Mean model + t: array_like + The `times` or `x` values of the data set. + y: array_like + The `y` values of the data set. + yerr: float, int, array_like, optional + The error values on the y-values. If a single value is given, it is assumed that the value + applies for all y-values. Default is 1e-6, effectively assuming that no y-errors are present. + gp_class: type, None, optional + GPClass to use. This is determined by the child class used to instantiate the GP. Should usually + not be given by the user and is mostly used for testing + """ + self.kernel = kernel + self.mean_model = mean_model + self.t = np.array(t) + self.y = np.array(y) + self.yerr = np.array(yerr) + self.GPClass = gp_class + self.gp = self.GPClass(kernel=self.kernel, mean=self.mean_model, fit_mean=True, fit_white_noise=True) + self.gp.compute(self.t, yerr=self.yerr) + super().__init__(parameters=self.gp.get_parameter_dict()) + + def set_parameters(self, parameters): + """ + Safely set a set of parameters to the internal instances of the `gp` and `mean_model`, as well as the + `parameters` dict. + + Parameters + ========== + parameters: dict, pandas.DataFrame + The set of parameters we would like to set. + """ + for name, value in parameters.items(): + try: + self.gp.set_parameter(name=name, value=value) + except ValueError: + pass + self.parameters[name] = value + + +class CeleriteLikelihood(_GPLikelihood): + + def __init__(self, kernel, mean_model, t, y, yerr=1e-6): + """ + Basic Gaussian Process likelihood interface for `celerite` and `george`. + For `celerite` documentation see: https://celerite.readthedocs.io/en/stable/ + For `george` documentation see: https://george.readthedocs.io/en/latest/ + + Parameters + ========== + kernel: celerite.term.Term + `celerite` or `george` kernel. See the respective package documentation about the usage. + mean_model: celerite.modeling.Model + Mean model + t: array_like + The `times` or `x` values of the data set. + y: array_like + The `y` values of the data set. + yerr: float, int, array_like, optional + The error values on the y-values. If a single value is given, it is assumed that the value + applies for all y-values. Default is 1e-6, effectively assuming that no y-errors are present. + """ + import celerite + super().__init__(kernel=kernel, mean_model=mean_model, t=t, y=y, yerr=yerr, gp_class=celerite.GP) + + def log_likelihood(self): + """ + Calculate the log-likelihood for the Gaussian process given the current parameters. + + Returns + ======= + float: The log-likelihood value. + """ + self.gp.set_parameter_vector(vector=np.array(list(self.parameters.values()))) + try: + return self.gp.log_likelihood(self.y) + except Exception: + return -np.inf + + +class GeorgeLikelihood(_GPLikelihood): + + def __init__(self, kernel, mean_model, t, y, yerr=1e-6): + """ + Basic Gaussian Process likelihood interface for `celerite` and `george`. + For `celerite` documentation see: https://celerite.readthedocs.io/en/stable/ + For `george` documentation see: https://george.readthedocs.io/en/latest/ + + Parameters + ========== + kernel: george.kernels.Kernel + `celerite` or `george` kernel. See the respective package documentation about the usage. + mean_model: george.modeling.Model + Mean model + t: array_like + The `times` or `x` values of the data set. + y: array_like + The `y` values of the data set. + yerr: float, int, array_like, optional + The error values on the y-values. If a single value is given, it is assumed that the value + applies for all y-values. Default is 1e-6, effectively assuming that no y-errors are present. + """ + import george + super().__init__(kernel=kernel, mean_model=mean_model, t=t, y=y, yerr=yerr, gp_class=george.GP) + + def log_likelihood(self): + """ + Calculate the log-likelihood for the Gaussian process given the current parameters. + + Returns + ======= + float: The log-likelihood value. + """ + for name, value in self.parameters.items(): + try: + self.gp.set_parameter(name=name, value=value) + except ValueError: + raise ValueError(f"Parameter {name} not a valid parameter for the GP.") + try: + return self.gp.log_likelihood(self.y) + except Exception: + return -np.inf + + class MarginalizedLikelihoodReconstructionError(Exception): pass diff --git a/examples/core_examples/gaussian_process_celerite_example.py b/examples/core_examples/gaussian_process_celerite_example.py new file mode 100644 index 0000000000000000000000000000000000000000..417493f57745ba211b333d70094d731bed0291cf --- /dev/null +++ b/examples/core_examples/gaussian_process_celerite_example.py @@ -0,0 +1,203 @@ +import matplotlib.pyplot as plt +import numpy as np +from pathlib import Path + +import celerite.terms + +import bilby +from bilby.core.prior import Uniform + + +# In this example we show how we can use the `celerite` package within `bilby`. +# We begin by synthesizing some data and then use a simple Gaussian Process +# model to fit and interpolate the data. +# The advantage of `celerite` is its high speed which allows the application of +# Gaussian Processes to large one-dimensional data sets. +# `bilby` implements a likelihood interface to the `celerite` and `george`, and +# adds some useful utility functions. + +# For specific use of `celerite`, see also the documentation at +# https://celerite.readthedocs.io/en/stable/, and the paper on the arxiv +# https://arxiv.org/abs/1703.09710. + +# For the use of Gaussian Processes in general, Rasmussen and Williams (2006) +# provides an in-depth introduction. The book is available for free at +# http://www.gaussianprocess.org/. + +label = "gaussian_process_celerite_example" +outdir = "outdir" +Path(outdir).mkdir(exist_ok=True) + +# Here, we set up the parameters for the data creation. +# The data will be a Gaussian Bell curve times a sine function. +# We also add a linear trend and some Gaussian white noise. + +period = 6 +amplitude = 10 +width = 20 +jitter = 1 +offset = 10 +slope = 0.1 + + +def linear_function(x, a, b): + return a * x + b + + +# For the data creation, we leave a gap in the middle of the time series +# to see how the Gaussian Process model can interpolate the data. +# We fix the seed to ensure reproducibility. + +np.random.seed(42) +times = np.linspace(0, 40, 100) +times = np.append(times, np.linspace(60, 100, 100)) +dt = times[1] - times[0] +duration = times[-1] - times[0] + +ys = ( + amplitude + * np.sin(2 * np.pi * times / period) + * np.exp(-((times - 50) ** 2) / 2 / width**2) + + np.random.normal(scale=jitter, size=len(times)) + + linear_function(x=times, a=slope, b=offset) +) + +plt.errorbar(times, ys, yerr=jitter, fmt=".k") +plt.xlabel("times") +plt.ylabel("y") +plt.savefig(f"{outdir}/{label}_data.pdf") +plt.show() + + +# We use a Gaussian Process kernel to model the Bell curve and sine aspects of +# the data. This specific kernel is a +# 'stochastically-driven harmonic oscillator'. +kernel = celerite.terms.SHOTerm(log_S0=0, log_Q=0, log_omega0=0) + +# If we wish to also infer the Gaussian white noise, we can add a `JitterTerm`. +# This is useful if the `yerr` values of the data are not known. +jitter_term = True +if jitter_term: + kernel += celerite.terms.JitterTerm(log_sigma=0) + +# `bilby.core.likelihood.function_to_celerite_mean_model` takes a python +# function and transforms it into the correct class for `celerite` to use as +# a mean function. The first argument of the python function to be passed +# has to be the 'time' or 'x' variable, followed by the parameters. The +# model needs to be initialized with some values, though these do not matter +# for inference. +LinearMeanModel = bilby.core.likelihood.function_to_celerite_mean_model(linear_function) +mean_model = LinearMeanModel(a=0, b=0) + +# Set up the likelihood. We set `yerr=1e-6` so that we can find the amount +# of white noise during the inference process. Smaller values of `yerr` +# cause the program to break. If you know the `yerr` in your problem, +# you can pass them in as an array. +likelihood = bilby.core.likelihood.CeleriteLikelihood( + kernel=kernel, mean_model=mean_model, t=times, y=ys, yerr=1e-6 +) + +# Print the parameter names. This is useful if we have trouble figuring out +# how `celerite` applies its naming scheme. +print(likelihood.gp.parameter_names) + +# Set up the priors. We know the name of the parameters from the print +# statement in the line before. +priors = bilby.core.prior.PriorDict() +priors["kernel:terms[0]:log_S0"] = Uniform( + minimum=-10, maximum=30, name="log_S0", latex_label=r"$\ln S_0$" +) +priors["kernel:terms[0]:log_Q"] = Uniform( + minimum=-10, maximum=30, name="log_Q", latex_label=r"$\ln Q$" +) +priors["kernel:terms[0]:log_omega0"] = Uniform( + minimum=-5, maximum=5, name="log_omega0", latex_label=r"$\ln \omega_0$" +) +priors["kernel:terms[1]:log_sigma"] = Uniform( + minimum=-5, maximum=5, name="log sigma", latex_label=r"$\ln \sigma$" +) +priors["mean:a"] = Uniform(minimum=-100, maximum=100, name="a", latex_label=r"$a$") +priors["mean:b"] = Uniform(minimum=-100, maximum=100, name="b", latex_label=r"$b$") + +# Run the sampling process as usual with `bilby`. The settings are such that +# the run should finish within a few minutes. +result = bilby.run_sampler( + likelihood=likelihood, + priors=priors, + outdir=outdir, + label=label, + sampler="dynesty", + sample="rslice", + nlive=500, + resume=True, +) +result.plot_corner() + +# Let's also plot the parameters with known values separately. This way we +# can see if the inference process yielded sensible results. +truths = { + "mean:a": slope, + "mean:b": offset, + "kernel:terms[0]:log_omega0": -np.log(period / 2 / np.pi), + "kernel:terms[1]:log_sigma": np.log(jitter), +} +result.label = f"{label}_truths" +result.plot_corner(truths=truths) + +# Now let's plot the data with some possible realizations of the mean model, +# and the interpolated prediction from the Gaussian Process using the +# maximum likelihood parameters. + +start_time = times[0] +end_time = times[-1] + + +# First, we extract the inferred Gaussian white noise jitter. +jitter = np.exp(result.posterior.iloc[-1]["kernel:terms[1]:log_sigma"]) + +# Next, we re-compute the Gaussian process with these y errors. +# `likelihood.gp` is an instance of the `celerite` Gaussian Process class. +# See the `celerite` documentation for a detailed explanation. We can then +# draw predicted means and variances from the `celerite` Gaussian process. +likelihood.gp.compute(times, jitter) +x = np.linspace(start_time, end_time, 5000) +pred_mean, pred_var = likelihood.gp.predict(ys, x, return_var=True) +pred_std = np.sqrt(pred_var) + +# Plot the data and prediction. +color = "#ff7f0e" +plt.errorbar(times, ys, yerr=jitter, fmt=".k", capsize=0, label="Data") +plt.plot(x, pred_mean, color=color, label="Prediction") +plt.fill_between( + x, + pred_mean + pred_std, + pred_mean - pred_std, + color=color, + alpha=0.3, + edgecolor="none", +) + +# Plot the mean model for the maximum likelihood parameters. +if isinstance(likelihood.mean_model, (float, int)): + trend = np.ones(len(x)) * likelihood.mean_model +else: + trend = likelihood.mean_model.get_value(x) +plt.plot(x, trend, color="green", label="Mean") + +# Plot the mean model for ten other posterior samples. +samples = [ + result.posterior.iloc[np.random.randint(len(result.posterior))] for _ in range(10) +] +for sample in samples: + likelihood.set_parameters(sample) + if not isinstance(likelihood.mean_model, (float, int)): + trend = likelihood.mean_model.get_value(x) + plt.plot(x, trend, color="green", alpha=0.3) + +plt.xlabel("times") +plt.ylabel("y") +plt.legend(ncol=2) +plt.tight_layout() +plt.savefig(f"{outdir}/{label}_max_like_fit.pdf") +plt.show() +plt.clf() diff --git a/examples/core_examples/gaussian_process_george_example.py b/examples/core_examples/gaussian_process_george_example.py new file mode 100644 index 0000000000000000000000000000000000000000..2c2946583cbe7ab32aa4ddd106e1b2f44559e76e --- /dev/null +++ b/examples/core_examples/gaussian_process_george_example.py @@ -0,0 +1,182 @@ +import matplotlib.pyplot as plt +import numpy as np +from pathlib import Path + +import george + +import bilby +from bilby.core.prior import Uniform + + +# In this example we show how we can use the `george` package within +# `bilby`. We begin by synthesizing some data and then use a simple Gaussian +# Process model to fit and interpolate the data. `bilby` implements a +# likelihood interface to the `celerite` and `george` and adds some useful +# utility functions. + +# For specific use of `george`, see also the documentation at +# https://george.readthedocs.io/en/stable/, and the paper on the arxiv +# https://arxiv.org/abs/1703.09710. + +# For the use of Gaussian Processes in general, Rasmussen and Williams ( +# 2006) provides an in-depth introduction. The book is available for free at +# http://www.gaussianprocess.org/. + +label = "gaussian_process_george_example" +outdir = "outdir" +Path(outdir).mkdir(exist_ok=True) + +# Here, we set up the parameters for the data creation. The data will be a +# Gaussian Bell curve times a sine function. We also add a linear trend and +# some Gaussian white noise. + +period = 6 +amplitude = 10 +width = 20 +jitter = 1 +offset = 10 +slope = 0.1 + + +def linear_function(x, a, b): + return a * x + b + + +# For the data creation, we leave a gap in the middle of the time series to +# see how the Gaussian Process model can interpolate the data. We fix the +# seed to ensure reproducibility. + +np.random.seed(42) +times = np.linspace(0, 40, 100) +times = np.append(times, np.linspace(60, 100, 100)) +dt = times[1] - times[0] +duration = times[-1] - times[0] + +ys = ( + amplitude + * np.sin(2 * np.pi * times / period) + * np.exp(-((times - 50) ** 2) / 2 / width**2) + + np.random.normal(scale=jitter, size=len(times)) + + linear_function(x=times, a=slope, b=offset) +) + +plt.errorbar(times, ys, yerr=jitter, fmt=".k") +plt.xlabel("times") +plt.ylabel("y") +plt.savefig(f"{outdir}/{label}_data.pdf") +plt.show() + + +# We use a Gaussian Process kernel to model the Bell curve and sine aspects +# of the data. This specific kernel is a Matern 3/2 kernel, which will be +# actually not be a good model! Try using the other available kernels in +# `george` to see if you can achieve a better interpolation! +kernel = 2.0 * george.kernels.Matern32Kernel(metric=5.0) + +# `bilby.core.likelihood.function_to_george_mean_model` takes a python +# function and transforms it into the correct class for `celerite` to use as +# a mean function. The first argument of the python function to be passed +# has to be the 'time' or 'x' variable, followed by the parameters. The +# model needs to be initialized with some values, though these do not matter +# for inference. +LinearMeanModel = bilby.core.likelihood.function_to_george_mean_model(linear_function) +mean_model = LinearMeanModel(a=0, b=0) + +# Set up the likelihood. We set `yerr=1e-6` so that we can find the amount +# of white noise during the inference process. # Smaller values of `yerr` +# cause the program to break. If you know the `yerr` in your problem, +# you can pass them in as # an array. +likelihood = bilby.core.likelihood.GeorgeLikelihood( + kernel=kernel, mean_model=mean_model, t=times, y=ys, yerr=1e-6 +) + +# Print the parameter names. This is useful if we have trouble figuring out +# how `celerite` applies its naming scheme. +print(likelihood.gp.parameter_names) +print(likelihood.gp.parameter_vector) + +# Set up the priors. We know the name of the parameters from the print +# statement in the line before. +priors = bilby.core.prior.PriorDict() +priors["kernel:k1:log_constant"] = Uniform( + minimum=-10, maximum=30, name="log_A", latex_label=r"$\ln A$" +) +priors["kernel:k2:metric:log_M_0_0"] = Uniform( + minimum=-10, maximum=30, name="log_M_0_0", latex_label=r"$\ln M_{00}$" +) +priors["white_noise:value"] = Uniform( + minimum=0, maximum=10, name="white noise", latex_label=r"$\sigma$" +) +priors["mean:a"] = Uniform(minimum=-100, maximum=100, name="a", latex_label=r"$a$") +priors["mean:b"] = Uniform(minimum=-100, maximum=100, name="b", latex_label=r"$b$") + +# Run the sampling process as usual with `bilby`. The settings are such that +# the run should finish within a few minutes. +result = bilby.run_sampler( + likelihood=likelihood, + priors=priors, + outdir=outdir, + label=label, + sampler="dynesty", + sample="rslice", + nlive=300, + resume=True, +) +result.plot_corner() + +# Now let's plot the data with some possible realizations of the mean model, +# and the interpolated prediction from the Gaussian Process using the +# maximum likelihood parameters. +start_time = times[0] +end_time = times[-1] + + +# First, we extract the inferred Gaussian white noise jitter +jitter = result.posterior.iloc[-1]["white_noise:value"] + +# Next, we re-compute the Gaussian process with these y errors. +# `likelihood.gp` is an instance of the `celerite` Gaussian Process class. +# See the `celerite` documentation for a detailed explanation. We can then +# draw predicted means and variances from the `celerite` Gaussian process. +likelihood.gp.compute(times, jitter) +x = np.linspace(start_time, end_time, 5000) +pred_mean, pred_var = likelihood.gp.predict(ys, x, return_var=True) +pred_std = np.sqrt(pred_var) + +# Plot the data and prediction. +color = "#ff7f0e" +plt.errorbar(times, ys, yerr=jitter, fmt=".k", capsize=0, label="Data") +plt.plot(x, pred_mean, color=color, label="Prediction") +plt.fill_between( + x, + pred_mean + pred_std, + pred_mean - pred_std, + color=color, + alpha=0.3, + edgecolor="none", +) + +# Plot the mean model for the maximum likelihood parameters. +if isinstance(likelihood.mean_model, (float, int)): + trend = np.ones(len(x)) * likelihood.mean_model +else: + trend = likelihood.mean_model.get_value(x) +plt.plot(x, trend, color="green", label="Mean") + +# Plot the mean model for ten other posterior samples. +samples = [ + result.posterior.iloc[np.random.randint(len(result.posterior))] for _ in range(10) +] +for sample in samples: + likelihood.set_parameters(sample) + if not isinstance(likelihood.mean_model, (float, int)): + trend = likelihood.mean_model.get_value(x) + plt.plot(x, trend, color="green", alpha=0.3) + +plt.xlabel("times") +plt.ylabel("y") +plt.legend(ncol=2) +plt.tight_layout() +plt.savefig(f"{outdir}/{label}_max_like_fit.pdf") +plt.show() +plt.clf() diff --git a/optional_requirements.txt b/optional_requirements.txt index 86acd396e5bba1759be86b07ade32ff1b3dfad60..d6ceb7188d00e64badeeb9ddf3d75e0ddcf87966 100644 --- a/optional_requirements.txt +++ b/optional_requirements.txt @@ -1,5 +1,7 @@ astropy +celerite lalsuite +george gwpy theano plotly diff --git a/test/core/likelihood_test.py b/test/core/likelihood_test.py index d5a0e296865bc66fc3b052bd8019d05f8a76f154..c34c1116e13ccb222a5eb9d9ab9112c12e87b6f0 100644 --- a/test/core/likelihood_test.py +++ b/test/core/likelihood_test.py @@ -1,6 +1,9 @@ import unittest from unittest import mock + import numpy as np + +import bilby.core.likelihood from bilby.core.likelihood import ( Likelihood, GaussianLikelihood, @@ -693,5 +696,158 @@ class TestJointLikelihood(unittest.TestCase): # self.assertDictEqual(self.joint_likelihood.parameters, joint_likelihood.parameters) +class TestGPLikelihood(unittest.TestCase): + + def setUp(self) -> None: + self.t = [1, 2, 3] + self.y = [4, 5, 6] + self.yerr = [0.4, 0.5, 0.6] + self.kernel = mock.MagicMock() + self.mean_model = mock.MagicMock() + self.gp_mock = mock.MagicMock() + self.gp_mock.compute = mock.MagicMock() + self.parameter_dict = dict(a=1, b=2) + self.gp_mock.get_parameter_dict = mock.MagicMock(return_value=dict(self.parameter_dict)) + self.gp_class = mock.MagicMock(return_value=self.gp_mock) + self.celerite_likelihood = bilby.core.likelihood._GPLikelihood( + kernel=self.kernel, mean_model=self.mean_model, t=self.t, y=self.y, yerr=self.yerr, gp_class=self.gp_class) + + def tearDown(self) -> None: + del self.t + del self.y + del self.yerr + del self.kernel + del self.mean_model + del self.parameter_dict + del self.gp_class + del self.gp_mock + del self.celerite_likelihood + + def test_t(self): + self.assertIsInstance(self.celerite_likelihood.t, np.ndarray) + self.assertTrue(np.array_equal(self.t, self.celerite_likelihood.t)) + + def test_y(self): + self.assertIsInstance(self.celerite_likelihood.y, np.ndarray) + self.assertTrue(np.array_equal(self.y, self.celerite_likelihood.y)) + + def test_yerr(self): + self.assertIsInstance(self.celerite_likelihood.yerr, np.ndarray) + self.assertTrue(np.array_equal(self.yerr, self.celerite_likelihood.yerr)) + + def test_gp_class(self): + self.assertEqual(self.gp_class, self.celerite_likelihood.GPClass) + + def test_gp_instantiation(self): + self.celerite_likelihood.GPClass.assert_called_once_with( + kernel=self.kernel, mean=self.mean_model, fit_mean=True, fit_white_noise=True) + + def test_gp_mock(self): + self.celerite_likelihood.gp.compute.assert_called_once_with( + self.celerite_likelihood.t, yerr=self.celerite_likelihood.yerr) + + def test_parameters(self): + self.assertDictEqual(self.parameter_dict, self.celerite_likelihood.parameters) + + def test_set_parameters_no_exceptions(self): + self.celerite_likelihood.gp.set_parameter = mock.MagicMock() + self.celerite_likelihood.mean_model.set_parameter = mock.MagicMock() + expected_a = 5 + self.celerite_likelihood.set_parameters(dict(a=expected_a)) + self.celerite_likelihood.gp.set_parameter.assert_called_once_with(name="a", value=5) + self.assertEqual(expected_a, self.celerite_likelihood.parameters["a"]) + + +class TestFunctionMeanModel(unittest.TestCase): + + def test_function_to_celerite_mean_model(self): + def func(x, a, b, c): + return a * x ** 2 + b * x + c + + mean_model = bilby.core.likelihood.function_to_celerite_mean_model(func=func) + self.assertListEqual(["a", "b", "c"], list(mean_model.parameter_names)) + + def test_function_to_george_mean_model(self): + def func(x, a, b, c): + return a * x ** 2 + b * x + c + + mean_model = bilby.core.likelihood.function_to_celerite_mean_model(func=func) + self.assertListEqual(["a", "b", "c"], list(mean_model.parameter_names)) + + +class TestCeleriteLikelihoodEvaluation(unittest.TestCase): + + def setUp(self) -> None: + import celerite + + def func(x, a): + return a * x + + self.t = [0, 1, 2] + self.y = [0, 1, 2] + self.yerr = [0.4, 0.5, 0.6] + self.parameters = {"kernel:log_S0": 0, "kernel:log_Q": 0, "kernel:log_omega0": 0, "mean:a": 1} + self.kernel = celerite.terms.SHOTerm(log_S0=0, log_Q=0, log_omega0=0) + + self.MeanModel = bilby.likelihood.function_to_celerite_mean_model(func=func) + self.mean_model = self.MeanModel(a=1) + self.celerite_likelihood = bilby.core.likelihood.CeleriteLikelihood( + kernel=self.kernel, mean_model=self.mean_model, t=self.t, y=self.y, yerr=self.yerr) + self.celerite_likelihood.parameters = self.parameters + + def tearDown(self) -> None: + del self.t + del self.y + del self.yerr + del self.parameters + del self.kernel + del self.MeanModel + del self.mean_model + del self.celerite_likelihood + + def test_log_l_evalutation(self): + log_l = self.celerite_likelihood.log_likelihood() + expected = -2.0390312696885102 + self.assertEqual(expected, log_l) + + def test_set_parameters(self): + combined_params = {"kernel:log_S0": 2, "kernel:log_Q": 2, "kernel:log_omega0": 2, "mean:a": 2} + self.celerite_likelihood.set_parameters(combined_params) + self.assertDictEqual(combined_params, self.celerite_likelihood.parameters) + + +class TestGeorgeLikelihoodEvaluation(unittest.TestCase): + + def setUp(self) -> None: + import george + + def func(x, a): + return a * x + + self.t = [0, 1, 2] + self.y = [0, 1, 2] + self.yerr = [0.4, 0.5, 0.6] + self.parameters = {} + self.kernel = 2.0 * george.kernels.Matern32Kernel(metric=5.0) + + self.MeanModel = bilby.likelihood.function_to_celerite_mean_model(func=func) + self.mean_model = self.MeanModel(a=1) + self.george_likelihood = bilby.core.likelihood.GeorgeLikelihood( + kernel=self.kernel, mean_model=self.mean_model, t=self.t, y=self.y, yerr=self.yerr) + + def tearDown(self) -> None: + pass + + def test_likelihood_value(self): + log_l = self.george_likelihood.log_likelihood() + expected = -3.2212751203208403 + self.assertEqual(expected, log_l) + + def test_set_parameters(self): + combined_params = {"kernel:k1:log_constant": 2, "kernel:k2:metric:log_M_0_0": 2, "mean:a": 2} + self.george_likelihood.set_parameters(combined_params) + self.assertDictEqual(combined_params, self.george_likelihood.parameters) + + if __name__ == "__main__": unittest.main()