Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import Union

import matplotlib.pyplot as plt
import numpy as np

# pylint: disable=redefined-builtin,no-name-in-module,no-member
# pylint: disable=no-name-in-module,no-member
Expand Down Expand Up @@ -52,6 +53,21 @@ def _ensure_odd_n(n) -> None:
_validate_odd_n(n)


def _validate_scaling_flag(value, name: str) -> bool:
"""Return a real boolean scaling flag without accepting truthy values."""
if np.ma.is_masked(value):
raise TypeError(f"{name} must be a boolean.")
if isinstance(value, (bool, np.bool_)):
return bool(value)
try:
value_array = np.asarray(value)
except (TypeError, ValueError, RuntimeError, OverflowError) as exc:
raise TypeError(f"{name} must be a boolean.") from exc
if value_array.shape == () and value_array.dtype == np.bool_:
return bool(value_array.item())
raise TypeError(f"{name} must be a boolean.")


class CircularFourierDistribution(AbstractCircularDistribution):
"""Circular distribution represented by a Fourier series.

Expand Down Expand Up @@ -120,7 +136,9 @@ def __init__(
else:
raise ValueError("Need to provide either c or a and b.")

self.multiplied_by_n = multiplied_by_n
self.multiplied_by_n = _validate_scaling_flag(
multiplied_by_n, "multiplied_by_n"
)
self.transformation = transformation

def __sub__(
Expand Down Expand Up @@ -365,6 +383,9 @@ def from_distribution(
transformation: str = "sqrt",
store_values_multiplied_by_n: bool = True,
) -> "CircularFourierDistribution":
store_values_multiplied_by_n = _validate_scaling_flag(
store_values_multiplied_by_n, "store_values_multiplied_by_n"
)
n = _validate_odd_n(n)
if isinstance(distribution, CircularDiracDistribution):
if transformation != "identity":
Expand Down Expand Up @@ -412,6 +433,9 @@ def from_function_values(
transformation: str = "sqrt",
store_values_multiplied_by_n: bool = True,
) -> "CircularFourierDistribution":
store_values_multiplied_by_n = _validate_scaling_flag(
store_values_multiplied_by_n, "store_values_multiplied_by_n"
)
n_values = fvals.shape[0]
_ensure_odd_n(n_values)
c = fft.rfft(fvals)
Expand Down
59 changes: 59 additions & 0 deletions tests/distributions/test_circular_fourier_bool_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import numpy as np
import pytest

from pyrecest.backend import array
from pyrecest.distributions import CircularFourierDistribution, VonMisesDistribution


@pytest.mark.parametrize("invalid_flag", ["False", "True", 0, 1, None])
def test_constructor_rejects_non_boolean_multiplied_by_n(invalid_flag):
with pytest.raises(TypeError, match="multiplied_by_n must be a boolean"):
CircularFourierDistribution(
c=array([1.0]),
n=1,
transformation="identity",
multiplied_by_n=invalid_flag,
)


@pytest.mark.parametrize("invalid_flag", ["False", "True", 0, 1, None])
def test_function_value_factory_rejects_non_boolean_scaling_flag(invalid_flag):
with pytest.raises(
TypeError, match="store_values_multiplied_by_n must be a boolean"
):
CircularFourierDistribution.from_function_values(
array([1.0, 1.0, 1.0]),
transformation="identity",
store_values_multiplied_by_n=invalid_flag,
)


def test_distribution_factory_rejects_truthy_string_scaling_flag():
distribution = VonMisesDistribution(array(0.0), array(1.0))

with pytest.raises(
TypeError, match="store_values_multiplied_by_n must be a boolean"
):
CircularFourierDistribution.from_distribution(
distribution,
n=3,
transformation="identity",
store_values_multiplied_by_n="False",
)


def test_numpy_boolean_scaling_flags_are_accepted():
direct = CircularFourierDistribution(
c=array([1.0]),
n=1,
transformation="identity",
multiplied_by_n=np.bool_(False),
)
from_values = CircularFourierDistribution.from_function_values(
array([1.0, 1.0, 1.0]),
transformation="identity",
store_values_multiplied_by_n=np.bool_(False),
)

assert direct.multiplied_by_n is False
assert from_values.multiplied_by_n is False
Loading