docs: update documentation files and fix linting issues
This commit is contained in:
@@ -36,54 +36,49 @@ import pytest
|
||||
@pytest.mark.api
|
||||
def _test_component_is_callable(component, component_name="Component"):
|
||||
"""Component must be callable with array input."""
|
||||
assert callable(component), (
|
||||
f"{component_name}: component is not callable"
|
||||
)
|
||||
assert callable(component), f"{component_name}: component is not callable"
|
||||
|
||||
W = np.array([1.5, 2.0, 3.0])
|
||||
result = component(W)
|
||||
|
||||
assert isinstance(result, np.ndarray), (
|
||||
f"{component_name}: __call__ returned {type(result)}, "
|
||||
f"expected np.ndarray"
|
||||
)
|
||||
assert result.shape == W.shape, (
|
||||
f"{component_name}: shape mismatch {W.shape} → {result.shape}"
|
||||
f"{component_name}: __call__ returned {type(result)}, " f"expected np.ndarray"
|
||||
)
|
||||
assert (
|
||||
result.shape == W.shape
|
||||
), f"{component_name}: shape mismatch {W.shape} → {result.shape}"
|
||||
|
||||
|
||||
@pytest.mark.api
|
||||
def _test_component_repr(component, component_name="Component"):
|
||||
"""Component must have a non-empty repr."""
|
||||
repr_str = repr(component)
|
||||
assert isinstance(repr_str, str), (
|
||||
f"{component_name}: repr returned {type(repr_str)}"
|
||||
)
|
||||
assert len(repr_str) > 0, (
|
||||
f"{component_name}: repr is empty string"
|
||||
)
|
||||
assert isinstance(
|
||||
repr_str, str
|
||||
), f"{component_name}: repr returned {type(repr_str)}"
|
||||
assert len(repr_str) > 0, f"{component_name}: repr is empty string"
|
||||
# repr should contain the class name
|
||||
assert component_name in repr_str or component.__class__.__name__ in repr_str, (
|
||||
f"{component_name}: repr should contain class name. Got: {repr_str}"
|
||||
)
|
||||
assert (
|
||||
component_name in repr_str or component.__class__.__name__ in repr_str
|
||||
), f"{component_name}: repr should contain class name. Got: {repr_str}"
|
||||
|
||||
|
||||
@pytest.mark.api
|
||||
def _test_component_list_input(component, component_name="Component"):
|
||||
"""Component must handle Python list input (numpy converts it)."""
|
||||
result = component([1.5, 2.0, 3.0])
|
||||
assert isinstance(result, np.ndarray), (
|
||||
f"{component_name}: list input returned {type(result)}"
|
||||
)
|
||||
assert isinstance(
|
||||
result, np.ndarray
|
||||
), f"{component_name}: list input returned {type(result)}"
|
||||
|
||||
|
||||
@pytest.mark.api
|
||||
def _test_component_empty_input(component, component_name="Component"):
|
||||
"""Component must handle empty array input gracefully."""
|
||||
result = component(np.array([]))
|
||||
assert isinstance(result, np.ndarray), (
|
||||
f"{component_name}: empty input returned {type(result)}"
|
||||
)
|
||||
assert result.shape == (0,), (
|
||||
f"{component_name}: empty input should return empty array"
|
||||
)
|
||||
assert isinstance(
|
||||
result, np.ndarray
|
||||
), f"{component_name}: empty input returned {type(result)}"
|
||||
assert result.shape == (
|
||||
0,
|
||||
), f"{component_name}: empty input should return empty array"
|
||||
|
||||
@@ -10,20 +10,18 @@ seconds to complete. They are intended for CI/full test runs, not for
|
||||
fast feedback during development.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from hypothesis import given, settings, strategies as st
|
||||
|
||||
from beta_spectrum.components.fermi import FermiFunction
|
||||
from beta_spectrum.components.phase_space import PhaseSpace
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fermi function: random Z, A, W combinations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given(
|
||||
Z=st.integers(min_value=1, max_value=92),
|
||||
A=st.integers(min_value=1, max_value=300),
|
||||
@@ -56,8 +54,7 @@ def test_fermi_monotonic_for_fixed_Z(Z: int):
|
||||
|
||||
diffs = np.diff(result)
|
||||
assert np.all(diffs <= 0), (
|
||||
f"Fermi function not monotonically decreasing for Z={Z}: "
|
||||
f"diffs = {diffs}"
|
||||
f"Fermi function not monotonically decreasing for Z={Z}: " f"diffs = {diffs}"
|
||||
)
|
||||
|
||||
|
||||
@@ -65,6 +62,7 @@ def test_fermi_monotonic_for_fixed_Z(Z: int):
|
||||
# Phase space: random W0, W combinations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given(
|
||||
W0=st.floats(min_value=2.0, max_value=10.0, allow_infinity=False, allow_nan=False),
|
||||
W=st.floats(min_value=1.01, max_value=5.0, allow_infinity=False, allow_nan=False),
|
||||
@@ -91,7 +89,9 @@ def test_phase_space_threshold_at_W1(W0: float):
|
||||
ps = PhaseSpace(W0=W0)
|
||||
result = ps(np.array([1.0]))
|
||||
|
||||
assert result[0] == 0.0, f"Phase space should be 0 at W=1 for W0={W0:.2f}, got {result[0]}"
|
||||
assert (
|
||||
result[0] == 0.0
|
||||
), f"Phase space should be 0 at W=1 for W0={W0:.2f}, got {result[0]}"
|
||||
|
||||
|
||||
@given(
|
||||
@@ -103,4 +103,6 @@ def test_phase_space_endpoint_at_W0(W0: float):
|
||||
ps = PhaseSpace(W0=W0)
|
||||
result = ps(np.array([W0]))
|
||||
|
||||
assert result[0] == 0.0, f"Phase space should be 0 at W=W0={W0:.2f}, got {result[0]}"
|
||||
assert (
|
||||
result[0] == 0.0
|
||||
), f"Phase space should be 0 at W=W0={W0:.2f}, got {result[0]}"
|
||||
|
||||
@@ -52,11 +52,11 @@ or only quality tests (`pytest -m quality`) for fast feedback during development
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Property tests: positivity
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.property
|
||||
def _test_positive_values(component, W, component_name="Component"):
|
||||
"""Component must return positive values at all W in the physical range.
|
||||
@@ -82,9 +82,9 @@ def _test_positive_values(component, W, component_name="Component"):
|
||||
f"W range: [{W_arr.min():.3f}, {W_arr.max():.3f}], "
|
||||
f"result range: [{np.nanmin(result):.6f}, {np.nanmax(result):.6f}]"
|
||||
)
|
||||
assert not np.any(np.isinf(result)), (
|
||||
f"{component_name}: Inf values found in output."
|
||||
)
|
||||
assert not np.any(
|
||||
np.isinf(result)
|
||||
), f"{component_name}: Inf values found in output."
|
||||
assert np.all(result > 0), (
|
||||
f"{component_name}: non-positive values found. "
|
||||
f"Min = {np.min(result):.10f} at W = {W_arr[np.argmin(result)][0]:.4f}"
|
||||
@@ -92,7 +92,9 @@ def _test_positive_values(component, W, component_name="Component"):
|
||||
|
||||
|
||||
@pytest.mark.property
|
||||
def _test_non_negative_values(component, W, component_name="Component", allow_zero=True):
|
||||
def _test_non_negative_values(
|
||||
component, W, component_name="Component", allow_zero=True
|
||||
):
|
||||
"""Component must return non-negative values (allows zero at physical boundaries).
|
||||
|
||||
Use this for components that can legitimately be zero (e.g., phase_space
|
||||
@@ -106,17 +108,16 @@ def _test_non_negative_values(component, W, component_name="Component", allow_ze
|
||||
W_arr = np.asarray(W, dtype=float)
|
||||
result = component(W_arr)
|
||||
|
||||
assert not np.any(np.isnan(result)), (
|
||||
f"{component_name}: NaN values found in output."
|
||||
)
|
||||
assert not np.any(np.isinf(result)), (
|
||||
f"{component_name}: Inf values found in output."
|
||||
)
|
||||
assert not np.any(
|
||||
np.isnan(result)
|
||||
), f"{component_name}: NaN values found in output."
|
||||
assert not np.any(
|
||||
np.isinf(result)
|
||||
), f"{component_name}: Inf values found in output."
|
||||
|
||||
if allow_zero:
|
||||
assert np.all(result >= 0), (
|
||||
f"{component_name}: negative values found. "
|
||||
f"Min = {np.min(result):.10f}"
|
||||
f"{component_name}: negative values found. " f"Min = {np.min(result):.10f}"
|
||||
)
|
||||
else:
|
||||
assert np.all(result > 0), (
|
||||
@@ -129,15 +130,16 @@ def _test_non_negative_values(component, W, component_name="Component", allow_ze
|
||||
# Property tests: shape preservation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.quality
|
||||
def _test_output_shape_preserved(component, W, component_name="Component"):
|
||||
"""Output shape must match input shape — no flattening or reshaping."""
|
||||
W_arr = np.asarray(W, dtype=float)
|
||||
result = component(W_arr)
|
||||
|
||||
assert isinstance(result, np.ndarray), (
|
||||
f"{component_name}: output is {type(result)}, expected np.ndarray"
|
||||
)
|
||||
assert isinstance(
|
||||
result, np.ndarray
|
||||
), f"{component_name}: output is {type(result)}, expected np.ndarray"
|
||||
assert result.shape == W_arr.shape, (
|
||||
f"{component_name}: shape mismatch. "
|
||||
f"Input {W_arr.shape} → Output {result.shape}"
|
||||
@@ -152,15 +154,14 @@ def _test_scalar_input_returns_array(component, W_scalar, component_name="Compon
|
||||
f"{component_name}: single-element input returned {type(result)}, "
|
||||
f"expected np.ndarray"
|
||||
)
|
||||
assert result.ndim == 1, (
|
||||
f"{component_name}: expected 1D output, got {result.ndim}D"
|
||||
)
|
||||
assert result.ndim == 1, f"{component_name}: expected 1D output, got {result.ndim}D"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Property tests: numerical stability
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.quality
|
||||
def _test_no_nan_across_range(component, W, component_name="Component"):
|
||||
"""No NaN values across the entire energy range."""
|
||||
@@ -208,6 +209,7 @@ def _test_full_range_stability(component, W, component_name="Component"):
|
||||
# Property tests: type correctness
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.quality
|
||||
def _test_output_is_ndarray(component, W, component_name="Component"):
|
||||
"""Output must be numpy.ndarray for any valid input."""
|
||||
@@ -224,6 +226,7 @@ def _test_output_is_ndarray(component, W, component_name="Component"):
|
||||
# Parametrized test templates for quick component integration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def make_positive_test(component_class, test_params, component_name=None):
|
||||
"""Factory function to create a parametrized positivity test.
|
||||
|
||||
@@ -258,7 +261,7 @@ def make_positive_test(component_class, test_params, component_name=None):
|
||||
@pytest.mark.parametrize("W", W_values)
|
||||
def test_positive(component_params, W_arr):
|
||||
comp = component_class(**component_params)
|
||||
test_positive_values(comp, W_arr, component_name)
|
||||
_test_positive_values(comp, W_arr, component_name)
|
||||
|
||||
return test_positive
|
||||
|
||||
@@ -278,6 +281,6 @@ def make_stability_test(component_class, test_params, component_name=None):
|
||||
@pytest.mark.parametrize("W", W_values)
|
||||
def test_stability(component_params, W_arr):
|
||||
comp = component_class(**component_params)
|
||||
test_full_range_stability(comp, W_arr, component_name)
|
||||
_test_full_range_stability(comp, W_arr, component_name)
|
||||
|
||||
return test_stability
|
||||
|
||||
Reference in New Issue
Block a user