Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
42 changes: 21 additions & 21 deletions src/semeio/fmudesign/design_distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,29 @@ def parse_and_validate_normal_params(
return tuple(params)


def _check_dist_params_lognormal(dist_params: Sequence[str]) -> tuple[bool, str]:
def parse_and_validate_lognormal_params(
dist_params: Sequence[int | str | float],
) -> tuple[float, float]:
if len(dist_params) != 2:
status = False
msg = (
raise ValueError(
"Lognormal distribution must have 2 parameters, "
"but had " + str(len(dist_params)) + " parameters. "
f"but had {len(dist_params)} parameters."
)
elif not (is_number(dist_params[0]) and is_number(dist_params[1])):
status = False
msg = "Parameters for lognormal distribution must be numbers. "
elif float(dist_params[1]) < 0:
status = False
msg = "Lognormal distribution must have stddev >= 0. "
else:
status = True
msg = ""
try:
mean, stddev = [float(p) for p in dist_params]
except (ValueError, TypeError) as e:
raise ValueError(
f"All parameters must be convertible to numbers. Got: {dist_params}"
) from e

return status, msg
if np.any(np.isnan([mean, stddev])):
raise ValueError(f"Parameters cannot be NaN. Got: {[mean, stddev]}")

if stddev < 0:
raise ValueError(
f"Stddev for lognormal distribution must be >= 0. Got: {stddev}"
)
return mean, stddev


def parse_and_validate_uniform_params(
Expand Down Expand Up @@ -227,7 +232,7 @@ def draw_values_normal(


def draw_values_lognormal(
dist_parameters: Sequence[str],
dist_parameters: Sequence[int | str | float],
numreals: int,
rng: np.random.Generator,
normalscoresamples: npt.NDArray[Any] | None = None,
Expand All @@ -241,12 +246,7 @@ def draw_values_lognormal(
Returns:
list of values
"""
status, msg = _check_dist_params_lognormal(dist_parameters)
if not status:
raise ValueError(msg)

mean = float(dist_parameters[0])
sigma = float(dist_parameters[1])
mean, sigma = parse_and_validate_lognormal_params(dist_parameters)
if normalscoresamples is not None:
values = scipy.stats.lognorm.ppf(
scipy.stats.norm.cdf(normalscoresamples),
Expand Down
58 changes: 48 additions & 10 deletions tests/fmudesign/test_design_distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,58 @@ def test_that_invalid_truncation_bounds_raise_ordering_error(self):
dists.parse_and_validate_normal_params([0, 1, 1, 0])


def test_check_dist_params_lognormal():
"""Test lognormal dist param checker"""
assert not dists._check_dist_params_lognormal([])[0]
assert not dists._check_dist_params_lognormal(())[0]
class TestLognormalDistribution:
def test_that_empty_list_raises_parameter_count_error(self):
with pytest.raises(
ValueError,
match="Lognormal distribution must have 2 parameters, but had 0 parameters.",
):
dists.parse_and_validate_lognormal_params([])

def test_that_empty_tuple_raises_parameter_count_error(self):
with pytest.raises(
ValueError,
match="Lognormal distribution must have 2 parameters, but had 0 parameters.",
):
dists.parse_and_validate_lognormal_params(())

def test_that_single_parameter_raises_count_error(self):
with pytest.raises(
ValueError,
match="Lognormal distribution must have 2 parameters, but had 1 parameters.",
):
dists.parse_and_validate_lognormal_params([0])

def test_that_three_parameters_raises_count_error(self):
with pytest.raises(
ValueError,
match="Lognormal distribution must have 2 parameters, but had 3 parameters.",
):
dists.parse_and_validate_lognormal_params([0, 0, 0])

assert not dists._check_dist_params_lognormal([0])[0]
assert not dists._check_dist_params_lognormal([0, 0, 0])[0]
def test_that_non_numeric_parameters_raise_conversion_error(self):
with pytest.raises(
ValueError,
match=r"All parameters must be convertible to numbers. Got: \['mean', 'mu'\]",
):
dists.parse_and_validate_lognormal_params(["mean", "mu"])

assert not dists._check_dist_params_lognormal(["mean", "mu"])[0]
def test_that_valid_lognormal_parameters_return_float_tuple(self):
assert dists.parse_and_validate_lognormal_params([0, 1]) == (0.0, 1.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"0" and "1" are also valid parameters in parser code, but untested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I've added a test.


assert dists._check_dist_params_lognormal([0, 1])[0]
assert not dists._check_dist_params_lognormal([0, -1])[0]
def test_that_negative_stddev_raises_validation_error(self):
with pytest.raises(
ValueError,
match="Stddev for lognormal distribution must be >= 0. Got: -1.0",
):
dists.parse_and_validate_lognormal_params([0, -1])

def test_that_zero_stddev_is_accepted(self):
assert dists.parse_and_validate_lognormal_params([0, 0]) == (0.0, 0.0)

assert dists._check_dist_params_lognormal([0, 0])[0] # edge case
def test_that_nan_parameter_raises_error(self):
with pytest.raises(ValueError, match="Parameters cannot be NaN"):
dists.parse_and_validate_lognormal_params([0, np.nan])


class TestUniformDistribution:
Expand Down
Loading