Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
juanitorduz committed Aug 12, 2024
1 parent 605f2d5 commit 782584d
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 8 deletions.
8 changes: 6 additions & 2 deletions pymc_marketing/mmm/transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,8 +899,12 @@ def michaelis_menten(
return alpha * x / (lam + x)


def hill_function(x, slope, kappa) -> pt.TensorVariable:
return 1 - (kappa**slope) / (kappa**slope + x**slope)
def hill_function(
x: pt.TensorLike, slope: pt.TensorLike, kappa: pt.TensorLike
) -> pt.TensorVariable:
return pt.as_tensor_variable(
1 - pt.power(kappa, slope) / (pt.power(kappa, slope) + pt.power(x, slope))
)


def hill_saturation_sigmoid(
Expand Down
109 changes: 103 additions & 6 deletions tests/mmm/test_transformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
batched_convolution,
delayed_adstock,
geometric_adstock,
hill_function,
hill_saturation_sigmoid,
inverse_scaled_logistic_saturation,
logistic_saturation,
Expand Down Expand Up @@ -466,7 +467,7 @@ def test_michaelis_menten(self, x, alpha, lam, expected):
(3, 2, -1),
],
)
def test_hill_monotonicity(self, sigma, beta, lam):
def test_hill_sigmoid_monotonicity(self, sigma, beta, lam):
x = np.linspace(-10, 10, 100)
y = hill_saturation_sigmoid(x, sigma, beta, lam).eval()
assert np.all(np.diff(y) >= 0), "The function is not monotonic."
Expand All @@ -479,7 +480,7 @@ def test_hill_monotonicity(self, sigma, beta, lam):
(3, 2, -1),
],
)
def test_hill_zero(self, sigma, beta, lam):
def test_hill_sigmoid_zero(self, sigma, beta, lam):
y = hill_saturation_sigmoid(0, sigma, beta, lam).eval()
assert y == pytest.approx(0.0)

Expand All @@ -491,7 +492,7 @@ def test_hill_zero(self, sigma, beta, lam):
(-3, 3, 2, -1),
],
)
def test_hill_sigma_upper_bound(self, x, sigma, beta, lam):
def test_hill_sigmoid_sigma_upper_bound(self, x, sigma, beta, lam):
y = hill_saturation_sigmoid(x, sigma, beta, lam).eval()
assert y <= sigma, f"The output {y} exceeds the upper bound sigma {sigma}."

Expand All @@ -503,7 +504,7 @@ def test_hill_sigma_upper_bound(self, x, sigma, beta, lam):
(-1, 3, 2, -1, 1.5),
],
)
def test_hill_behavior_at_lambda(self, x, sigma, beta, lam, expected):
def test_hill_sigmoid_behavior_at_lambda(self, x, sigma, beta, lam, expected):
y = hill_saturation_sigmoid(x, sigma, beta, lam).eval()
offset = sigma / (1 + np.exp(beta * lam))
expected_with_offset = expected - offset
Expand All @@ -522,7 +523,7 @@ def test_hill_behavior_at_lambda(self, x, sigma, beta, lam, expected):
(np.array([1, 2, 3]), 3, 2, 2),
],
)
def test_hill_vectorized_input(self, x, sigma, beta, lam):
def test_hill_sigmoid_vectorized_input(self, x, sigma, beta, lam):
y = hill_saturation_sigmoid(x, sigma, beta, lam).eval()
assert (
y.shape == x.shape
Expand All @@ -536,7 +537,7 @@ def test_hill_vectorized_input(self, x, sigma, beta, lam):
(3, 2, -1),
],
)
def test_hill_asymptotic_behavior(self, sigma, beta, lam):
def test_hill_sigmoid_asymptotic_behavior(self, sigma, beta, lam):
x = 1e6 # A very large value to approximate infinity
y = hill_saturation_sigmoid(x, sigma, beta, lam).eval()
offset = sigma / (1 + np.exp(beta * lam))
Expand All @@ -548,6 +549,102 @@ def test_hill_asymptotic_behavior(self, sigma, beta, lam):
err_msg="The function does not approach sigma as x approaches infinity.",
)

@pytest.mark.parametrize(
argnames=["slope", "kappa"],
argvalues=[
(1, 1),
(2, 0.5),
(3, 2),
],
ids=["slope=1, kappa=1", "slope=2, kappa=0.5", "slope=3, kappa=2"],
)
def test_hill_monotonicity(self, slope, kappa):
x = np.linspace(0, 10, 100)
y = hill_function(x, slope, kappa).eval()
assert np.all(np.diff(y) >= 0), "The function is not monotonic."

@pytest.mark.parametrize(
argnames=["slope", "kappa"],
argvalues=[
(1, 1),
(2, 0.5),
(3, 2),
],
ids=["slope=1, kappa=1", "slope=2, kappa=0.5", "slope=3, kappa=2"],
)
def test_hill_zero(self, slope, kappa):
y = hill_function(0, slope, kappa).eval()
assert y == pytest.approx(0.0)

@pytest.mark.parametrize(
argnames=["x", "slope", "kappa"],
argvalues=[
(1, 1, 1),
(2, 0.5, 0.5),
(3, 2, 2),
],
ids=[
"x=1, slope=1, kappa=1",
"x=2, slope=0.5, kappa=0.5",
"x=3, slope=2, kappa=2",
],
)
def test_hill_upper_bound(self, x, slope, kappa):
y = hill_function(x, slope, kappa).eval()
assert y <= 1, f"The output {y} exceeds the upper bound 1."

@pytest.mark.parametrize(
argnames=["slope", "kappa"],
argvalues=[
(1, 1),
(2, 0.5),
(3, 2),
],
ids=["slope=1, kappa=1", "slope=2, kappa=0.5", "slope=3, kappa=2"],
)
def test_hill_behavior_at_midpoint(self, slope, kappa):
y = hill_function(kappa, slope, kappa).eval()
expected = 0.5
np.testing.assert_almost_equal(
y,
expected,
decimal=5,
err_msg="The function does not behave as expected at the midpoint.",
)

@pytest.mark.parametrize(
"x, slope, kappa",
[
(np.array([0, 1, 2]), 1, 1),
(np.array([-1, 0, 1]), 2, 0.5),
(np.array([1, 2, 3]), 3, 2),
],
)
def test_hill_vectorized_input(self, x, slope, kappa):
y = hill_function(x, slope, kappa).eval()
assert (
y.shape == x.shape
), "The function did not return the correct shape for vectorized input."

@pytest.mark.parametrize(
"slope, kappa",
[
(1, 1),
(2, 0.5),
(3, 2),
],
)
def test_hill_asymptotic_behavior(self, slope, kappa):
x = 1e6 # A very large value to approximate infinity
y = hill_function(x, slope, kappa).eval()
expected = 1
np.testing.assert_almost_equal(
y,
expected,
decimal=5,
err_msg="The function does not approach sigma as x approaches infinity.",
)


class TestTransformersComposition:
@pytest.mark.parametrize(
Expand Down

0 comments on commit 782584d

Please sign in to comment.