Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions kernel_embedding_dictionary/embeddings/embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
matern72_lebesgue_mean_func_1d,
matern_lebesgue_mean_func_1d,
)
from .var_funcs_1d import (
expquad_lebesgue_var_func_1d,
)


class KernelEmbedding:
Expand All @@ -31,7 +34,8 @@ def __init__(self, kernel: ProductKernel, measure: ProductMeasure):
self._measure = measure

# kernel and measure must be set first
self._mean_func_1d = self._get_1d_funcs()
self._mean_func_1d = self._get_1d_mean_funcs()
self._var_func_1d = self._get_1d_var_funcs()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue regarding the tests is here. By passing the function handle, the function is run. This would be fine if we new that for each kernel mean embedding, we also know the integrated kernel mean. But I don't believe that's the case. I would therefore propose to delete these lines, and rename the _get_1d_funcs directly to _mean_func_1d and _var_func_1d.


def __str__(self) -> str:
return f"Kernel embedding for\n\n{self._kernel.__str__()}\n\nand\n\n{self._measure.__str__()}"
Expand All @@ -52,8 +56,15 @@ def mean(self, x: np.ndarray) -> np.ndarray:
params_dim = {**self._kernel.get_param_dict_from_dim(dim), **self._measure.get_param_dict_from_dim(dim)}
kernel_mean *= self._mean_func_1d(x[:, dim], **params_dim)
return kernel_mean

def variance(self) -> float:
kernel_var = 1
for dim in range(self.ndim):
params_dim = {**self._kernel.get_param_dict_from_dim(dim), **self._measure.get_param_dict_from_dim(dim)}
kernel_var *= self._var_func_1d(**params_dim)
return kernel_var

def _get_1d_funcs(self) -> Callable:
def _get_1d_mean_funcs(self) -> Callable:

mean_func_1d_dict = {
"expquad-lebesgue": expquad_lebesgue_mean_func_1d,
Expand All @@ -72,3 +83,16 @@ def _get_1d_funcs(self) -> Callable:
raise ValueError(f"kernel embedding unknown.")

return mean_func_1d

def _get_1d_var_funcs(self) -> Callable:

var_func_1d_dict = {
"expquad-lebesgue": expquad_lebesgue_var_func_1d,
}

var_func_1d = var_func_1d_dict.get(self._kernel.name + "-" + self._measure.name, None)
if not var_func_1d:
pass
# raise ValueError(f"integrated kernel mean unknown.")

return var_func_1d
23 changes: 23 additions & 0 deletions kernel_embedding_dictionary/embeddings/var_funcs_1d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2025 The KED Authors. All Rights Reserved.
# SPDX-License-Identifier: MIT


import numpy as np
from scipy.special import erf


def expquad_lebesgue_var_func_1d(ell: float, lb: float, ub: float, density: float) -> np.ndarray:
"""Compute the expected mean function for the exponential quadratic kernel with respect to the Lebesgue measure in 1D.

Args:
ell: The length scale parameter.
lb: The lower bound of the integration interval.
ub: The upper bound of the integration interval.
density: The density of the Lebesgue measure.

Returns:
The expected mean function value.
"""
exp_term = ell * np.sqrt(2/np.pi) * (np.exp(-0.5 * ((ub - lb) / ell) ** 2) - 1)
erf_term = (ub - lb) * (erf((ub - lb) / (ell * np.sqrt(2))))
return np.sqrt(2 * np.pi) * ell * density**2 * (exp_term + erf_term)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Copyright 2025 The KED Authors. All Rights Reserved.
# SPDX-License-Identifier: MIT

import pytest
import numpy as np

from kernel_embedding_dictionary._get_embedding import get_embedding

from scipy.integrate import quad


def get_config_expquad_lebesgue_1d_standard():
ck = {"ndim": 1}
cm = {"ndim": 1}
return "expquad", "lebesgue", ck, cm


def get_config_expquad_lebesgue_1d_values():
ck = {"ndim": 1, "lengthscales": [0.3]}
cm = {"ndim": 1, "bounds": [(-0.5, 2.5)], "normalize": True} # test only works for normalized measures
return "expquad", "lebesgue", ck, cm


@pytest.fixture()
def config_expquad_lebesgue_1d_standard():
kn, mn, ck, cm = get_config_expquad_lebesgue_1d_standard()
ke = get_embedding(kernel_name=kn, measure_name=mn, kernel_config=ck, measure_config=cm)

def ke_mean_scalar(x):
return ke.mean(np.asarray(x).reshape(1, -1))

ekm, int_err = quad(ke_mean_scalar, 0, 1)
return kn, mn, ck, cm, ekm, int_err


@pytest.fixture()
def config_expquad_lebesgue_1d_values():
kn, mn, ck, cm = get_config_expquad_lebesgue_1d_values()
ke = get_embedding(kernel_name=kn, measure_name=mn, kernel_config=ck, measure_config=cm)

bounds = (cm["bounds"][0][0], cm["bounds"][0][1])

def ke_mean_scalar(x):
return ke.mean(np.asarray(x).reshape(1, -1)) / (bounds[1] - bounds[0])

ekm, int_err = quad(ke_mean_scalar, *bounds)
return kn, mn, ck, cm, ekm, int_err

fixture_list = [
"config_expquad_lebesgue_1d_standard",
"config_expquad_lebesgue_1d_values",
]

@pytest.mark.parametrize("fixture_name", fixture_list)
def test_expquad_lebesgue_mean_func_1d(fixture_name, request):
# Test cases for the expected mean function
kn, mn, ck, cm, num_ekm, int_err = request.getfixturevalue(fixture_name)
ke = get_embedding(kernel_name=kn, measure_name=mn, kernel_config=ck, measure_config=cm)
assert num_ekm == pytest.approx(ke.variance(), rel=int_err)
Loading