import numpy as np
import pytest
import scipy.stats
import scirs2
class TestDistanceMetrics:
def test_cityblock_distance(self):
from scipy.spatial.distance import cityblock
u = np.ascontiguousarray(np.random.randn(10))
v = np.ascontiguousarray(np.random.randn(10))
scipy_dist = cityblock(u, v)
scirs2_dist = scirs2.cityblock_py(u, v)
assert np.allclose(scipy_dist, scirs2_dist, rtol=1e-10)
def test_minkowski_distance(self):
from scipy.spatial.distance import minkowski
u = np.ascontiguousarray(np.random.randn(10))
v = np.ascontiguousarray(np.random.randn(10))
for p in [1, 2, 3]:
scipy_dist = minkowski(u, v, p)
scirs2_dist = scirs2.minkowski_py(u, v, p)
assert np.allclose(scipy_dist, scirs2_dist, rtol=1e-10)
class TestMoreHypothesisTests:
def test_kruskal_wallis(self):
np.random.seed(42)
g1 = np.ascontiguousarray(np.random.randn(30))
g2 = np.ascontiguousarray(np.random.randn(30) + 0.5)
g3 = np.ascontiguousarray(np.random.randn(30) + 1.0)
scipy_result = scipy.stats.kruskal(g1, g2, g3)
result = scirs2.kruskal_py(g1, g2, g3)
if isinstance(result, dict):
stat = result.get('statistic', result.get('H', None))
else:
stat = result
if stat is not None:
assert np.isfinite(stat)
def test_anderson_darling(self):
np.random.seed(42)
data = np.ascontiguousarray(np.random.randn(100))
result = scirs2.anderson_darling_py(data)
if isinstance(result, dict):
stat = result.get('statistic', result.get('A2', None))
else:
stat = result
if stat is not None:
assert np.isfinite(stat)
assert stat >= 0
@pytest.mark.skip(reason="Known issue: dagostino_k2_py returns NaN")
def test_dagostino_k2(self):
np.random.seed(42)
data = np.ascontiguousarray(np.random.randn(100))
result = scirs2.dagostino_k2_py(data)
if isinstance(result, dict):
stat = result.get('statistic', result.get('k2', None))
else:
stat = result
if stat is not None:
assert np.isfinite(stat)
class TestConfidenceIntervals:
@pytest.mark.skip(reason="skewness_ci_py API incompatible (no 'alpha' parameter)")
def test_skewness_ci(self):
np.random.seed(42)
data = np.ascontiguousarray(np.random.randn(100))
result = scirs2.skewness_ci_py(data) if isinstance(result, dict):
lower = result.get('lower', result.get('ci_lower', None))
upper = result.get('upper', result.get('ci_upper', None))
elif hasattr(result, '__len__') and len(result) == 2:
lower, upper = result
else:
lower, upper = None, None
if lower is not None and upper is not None:
assert lower < upper
@pytest.mark.skip(reason="kurtosis_ci_py API incompatible (no 'alpha' parameter)")
def test_kurtosis_ci(self):
np.random.seed(42)
data = np.ascontiguousarray(np.random.randn(100))
result = scirs2.kurtosis_ci_py(data) if isinstance(result, dict):
lower = result.get('lower', result.get('ci_lower', None))
upper = result.get('upper', result.get('ci_upper', None))
elif hasattr(result, '__len__') and len(result) == 2:
lower, upper = result
else:
lower, upper = None, None
if lower is not None and upper is not None:
assert lower < upper
class TestInformationTheory:
def test_kl_divergence(self):
p = np.ascontiguousarray(np.array([0.1, 0.4, 0.5]))
q = np.ascontiguousarray(np.array([0.2, 0.3, 0.5]))
scipy_kl = scipy.stats.entropy(p, q)
scirs2_kl = scirs2.kl_divergence_py(p, q)
if scirs2_kl is not None:
assert np.allclose(scipy_kl, scirs2_kl, rtol=1e-6)
class TestCategoricalAnalysis:
@pytest.mark.skip(reason="Known issue: odds_ratio_py has array conversion problems")
def test_odds_ratio(self):
table = np.ascontiguousarray(np.array([[10, 5], [3, 12]]))
result = scirs2.odds_ratio_py(table)
if isinstance(result, dict):
or_val = result.get('odds_ratio', result.get('value', None))
else:
or_val = result
if or_val is not None:
expected = (table[0,0] * table[1,1]) / (table[0,1] * table[1,0])
assert np.allclose(or_val, expected, rtol=1e-10)
@pytest.mark.skip(reason="Known issue: relative_risk_py has array conversion problems")
def test_relative_risk(self):
table = np.ascontiguousarray(np.array([[20, 30], [10, 40]]))
result = scirs2.relative_risk_py(table)
if isinstance(result, dict):
rr_val = result.get('relative_risk', result.get('value', None))
else:
rr_val = result
if rr_val is not None:
assert np.isfinite(rr_val)
assert rr_val > 0
class TestMoreQuantiles:
def test_quintiles(self):
np.random.seed(42)
data = np.ascontiguousarray(np.random.randn(1000))
quintiles = scirs2.quintiles_py(data)
assert len(quintiles) == 4
assert np.all(quintiles[:-1] <= quintiles[1:])
p20 = scirs2.percentile_py(data, 20)
assert np.allclose(quintiles[0], p20, rtol=1e-10)
class TestAdditionalIntegration:
def test_trapezoid_integration(self):
x = np.ascontiguousarray(np.linspace(0, np.pi, 100))
y = np.ascontiguousarray(np.sin(x))
numpy_result = np.trapz(y, x)
scirs2_result = scirs2.trapezoid_array_py(y, x)
assert np.allclose(numpy_result, 2.0, rtol=1e-3)
assert np.allclose(scirs2_result, numpy_result, rtol=1e-10)
def test_cumulative_trapezoid(self):
x = np.ascontiguousarray(np.linspace(0, 10, 100))
y = np.ascontiguousarray(x**2)
result = scirs2.cumulative_trapezoid_py(y, x)
if hasattr(result, '__len__'):
assert np.all(result[1:] >= result[:-1])
class TestSquareform:
@pytest.mark.skip(reason="Known issue: squareform_py has array conversion problems")
def test_squareform_computable(self):
from scipy.spatial.distance import squareform
condensed = np.ascontiguousarray(np.array([1, 2, 3, 4, 5, 6]))
scipy_square = squareform(condensed)
scirs2_square = scirs2.squareform_py(condensed)
assert np.allclose(scipy_square, scirs2_square, rtol=1e-10)
if __name__ == "__main__":
pytest.main([__file__, "-v"])