From d5639c76b613d5aae2f92e61551ad8d1fd96d177 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 17 Jan 2026 16:50:34 -0700 Subject: [PATCH 1/4] WIP, MAINT: remove dependence on graforvfl * `graforvfl` is still listed with a copyleft license, and its dependency stack is fairly problematic to install as noted in the code changes at gh-2. While it is only a test time dependency rather than a hard library runtime dependency, I suspect we won't be able to carry it (and its transitive deps) over to sklearn. Even independent of that, it is a bit of a pain for us to deal with long-term, so this patch attempts to remove our testing dependence on it (and its transitive deps) completely. --- .github/constraints/deps.txt | 4 -- .github/workflows/ci.yml | 1 - src/rvfl/tests/test_model.py | 54 +++++++++++------------ src/rvfl/tests/test_regression.py | 71 ++++++++++++------------------- 4 files changed, 53 insertions(+), 77 deletions(-) diff --git a/.github/constraints/deps.txt b/.github/constraints/deps.txt index d2f080a..4d2eeec 100644 --- a/.github/constraints/deps.txt +++ b/.github/constraints/deps.txt @@ -1,8 +1,4 @@ numpy scipy>=1.7.1 scikit-learn>=1.2.1 -pandas -mealpy>=3.0.2 -permetrics>=2.0.0 matplotlib>=3.7.5 - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64721d5..9ceacc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,6 @@ jobs: python -m pip install -v ".[test]" python -m pip install ruff==0.13.1 python -m pip install -r .github/constraints/deps.txt - python -m pip install --no-deps graforvfl - name: lint run: | ruff check diff --git a/src/rvfl/tests/test_model.py b/src/rvfl/tests/test_model.py index 48324db..45e5061 100644 --- a/src/rvfl/tests/test_model.py +++ b/src/rvfl/tests/test_model.py @@ -1,6 +1,5 @@ # tests/test_model.py -import graforvfl import numpy as np import pytest from numpy.testing import assert_allclose @@ -365,18 +364,29 @@ def test_invalid_alpha(Classifier): bad_est.fit(X, y) -@pytest.mark.parametrize("hidden_layer_sizes", [(10,), (100,)]) -@pytest.mark.parametrize("n_classes", [2, 5]) -@pytest.mark.parametrize("activation", activations) -@pytest.mark.parametrize("weight_scheme", weights[2:]) -@pytest.mark.parametrize("alpha", [None, 0.5, 1]) +@pytest.mark.parametrize("""hidden_layer_sizes, + n_classes, + activation, + weight_scheme, + alpha, + exp_proba_shape, + exp_proba_median, + exp_proba_min""", [ + + # expected values are from graforvfl library + ([10,], 2, "relu", "uniform", None, (20, 2), 0.5, 0.0444571694), + ([100,], 2, "tanh", "normal", None, (20, 2), 0.5, 0.02538905725), + ([10,], 5, "softmax", "lecun_uniform", None, (20, 5), + 0.186506112, 0.08469873), +]) def test_classification_against_grafo(hidden_layer_sizes, n_classes, activation, - weight_scheme, alpha): - # test binary and multi-class classification against - # the open source graforvfl library on some synthetic + weight_scheme, alpha, exp_proba_shape, + exp_proba_median, exp_proba_min): + # test binary and multi-class classification against expected values + # from the open source graforvfl library on some synthetic # datasets X, y = make_classification(n_classes=n_classes, - n_informative=8) + n_informative=8, random_state=0) X_train, X_test, y_train, _ = train_test_split(X, y, test_size=0.2, random_state=0) model = RVFLClassifier(hidden_layer_sizes=hidden_layer_sizes, @@ -387,26 +397,14 @@ def test_classification_against_grafo(hidden_layer_sizes, n_classes, activation, reg_alpha=alpha) model.fit(X_train, y_train) - grafo_act = "none" if activation == "identity" else activation - if weight_scheme == "uniform": - grafo_wts = "random_uniform" - elif weight_scheme == "normal": - grafo_wts = "random_normal" - else: - grafo_wts = weight_scheme - - grafo_rvfl = graforvfl.RvflClassifier(size_hidden=hidden_layer_sizes[0], - act_name=grafo_act, - weight_initializer=grafo_wts, - reg_alpha=alpha, - seed=0) - - grafo_rvfl.fit(X_train, y_train) - actual_proba = model.predict_proba(X_test) - expected_proba = grafo_rvfl.predict_proba(X_test) + actual_proba_shape = actual_proba.shape + actual_proba_median = np.median(actual_proba) + actual_proba_min = np.min(actual_proba) - np.testing.assert_allclose(actual_proba, expected_proba) + np.testing.assert_allclose(actual_proba_shape, exp_proba_shape) + np.testing.assert_allclose(actual_proba_median, exp_proba_median) + np.testing.assert_allclose(actual_proba_min, exp_proba_min) @parametrize_with_checks([RVFLClassifier(), EnsembleRVFLClassifier()]) diff --git a/src/rvfl/tests/test_regression.py b/src/rvfl/tests/test_regression.py index d5f522c..042c7d8 100644 --- a/src/rvfl/tests/test_regression.py +++ b/src/rvfl/tests/test_regression.py @@ -1,7 +1,5 @@ - import numpy as np import pytest -from graforvfl import RvflRegressor from sklearn.datasets import fetch_openml, make_regression from sklearn.metrics import r2_score from sklearn.model_selection import train_test_split @@ -16,14 +14,25 @@ "glorot_uniform", "he_normal", "lecun_normal", "glorot_normal"] -@pytest.mark.parametrize("n_samples", [100, 1000]) -@pytest.mark.parametrize("n_targets", [10, 100]) -@pytest.mark.parametrize("hidden_layer_sizes", [(100,), (1000,)]) -@pytest.mark.parametrize("activation", activations) -@pytest.mark.parametrize("weight_scheme", weights) -@pytest.mark.parametrize("reg_alpha", [1, 10]) +@pytest.mark.parametrize("""n_samples, + n_targets, + hidden_layer_sizes, + activation, + weight_scheme, + reg_alpha, + exp_preds_shape, + exp_preds_median, + exp_preds_min""", [ + # expected values are from the graforvfl library + (100, 10, (100,), "relu", "glorot_normal", 10, (25, 10), + -29.31478018, -490.5751822), + (100, 10, (100,), "tanh", "uniform", 100, (25, 10), + -34.613165002, -327.82362807), +]) def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes, - activation, weight_scheme, reg_alpha): + activation, weight_scheme, reg_alpha, + exp_preds_shape, exp_preds_median, + exp_preds_min): N, d = n_samples, n_targets RNG = 42 X, y = make_regression(n_samples=N, @@ -43,24 +52,7 @@ def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes, X_train_s = scaler.transform(X_train) X_test_s = scaler.transform(X_test) - grafo_act = "none" if activation == "identity" else activation - if weight_scheme == "uniform": - grafo_wts = "random_uniform" - elif weight_scheme == "normal": - grafo_wts = "random_normal" - else: - grafo_wts = weight_scheme - - # Define models - models = { - "GrafoRVFL": RvflRegressor( - size_hidden=hidden_layer_sizes[0], - act_name=grafo_act, - weight_initializer=grafo_wts, - reg_alpha=reg_alpha, - seed=RNG - ), - "RVFL": RVFLRegressor( + model = RVFLRegressor( hidden_layer_sizes=hidden_layer_sizes, activation=activation, weight_scheme=weight_scheme, @@ -68,23 +60,14 @@ def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes, seed=RNG, reg_alpha=reg_alpha ) - } - - # Fit + predict - preds = {} - - for name, model in models.items(): - Xtr, Xte = (X_train_s, X_test_s) - model.fit(Xtr, y_train) - yhat = model.predict(Xte) - preds[name] = yhat - - # Compare GrafoRVFL and RVFL results - grf_results = preds["GrafoRVFL"] - cur_results = preds["RVFL"] - - # Test results - np.testing.assert_allclose(cur_results, grf_results, atol=1e-07) + model.fit(X_train_s, y_train) + actual_preds = model.predict(X_test_s) + actual_preds_shape = actual_preds.shape + actual_preds_median = np.median(actual_preds) + actual_preds_min = actual_preds.min() + np.testing.assert_allclose(actual_preds_shape, exp_preds_shape) + np.testing.assert_allclose(actual_preds_median, exp_preds_median) + np.testing.assert_allclose(actual_preds_min, exp_preds_min) @parametrize_with_checks([RVFLRegressor()]) From 0711902024c7d3cc50b96e2cb4e50a50efbdefab Mon Sep 17 00:00:00 2001 From: Navamita Ray Date: Thu, 22 Jan 2026 21:51:08 -0700 Subject: [PATCH 2/4] TST: More test cases for grafo rvfl classifier. --- src/rvfl/tests/test_model.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/rvfl/tests/test_model.py b/src/rvfl/tests/test_model.py index 45e5061..500d141 100644 --- a/src/rvfl/tests/test_model.py +++ b/src/rvfl/tests/test_model.py @@ -378,6 +378,36 @@ def test_invalid_alpha(Classifier): ([100,], 2, "tanh", "normal", None, (20, 2), 0.5, 0.02538905725), ([10,], 5, "softmax", "lecun_uniform", None, (20, 5), 0.186506112, 0.08469873), + ([10,], 2, "relu", "uniform", 0.5, (20, 2), 0.49999999999999994, + 0.04676933232591643), + ([10,], 2, "relu", "normal", 0.5, (20, 2), 0.5, + 0.13832596541020634), + ([10,], 2, "relu", "he_uniform", 0.5, (20, 2), 0.5, + 0.09354846081377409), + ([10,], 2, "relu", "lecun_uniform", 0.5, (20, 2), 0.5, + 0.09387932375067173), + ([10,], 2, "relu", "glorot_uniform", 0.5, (20, 2), + 0.49999999999999994, 0.09474642560519067), + ([10,], 2, "relu", "he_normal", 0.5, (20, 2), 0.5, + 0.13756805074436051), + ([10,], 2, "relu", "lecun_normal", 0.5, (20, 2), 0.5, + 0.1366715193146648), + ([10,], 2, "relu", "glorot_normal", 0.5, (20, 2), 0.5, + 0.147434110768701), + ([100,], 5, "relu", "normal", 1, (20, 5), 0.15697278777061396, + 0.014480242978774488), + ([100,], 5, "tanh", "normal", 1, (20, 5), 0.18173657135483476, + 0.04755723146401269), + ([100,], 5, "sigmoid", "normal", 1, (20, 5), 0.1831653950464296, + 0.05378741996708733), + ([100,], 5, "softmax", "normal", 1, (20, 5), 0.19357646668265396, + 0.10898717209741866), + ([100,], 5, "softmin", "normal", 1, (20, 5), 0.18746771358297387, + 0.09186562406164228), + ([100,], 5, "log_sigmoid", "normal", 1, (20, 5), + 0.16722029352468032, 0.012690348255702557), + ([100,], 5, "log_softmax", "normal", 1, (20, 5), + 0.1853363666712296, 0.10846041127337658), ]) def test_classification_against_grafo(hidden_layer_sizes, n_classes, activation, weight_scheme, alpha, exp_proba_shape, From 3b4204b740199262f757e499c8e279c54597869f Mon Sep 17 00:00:00 2001 From: Navamita Ray Date: Wed, 4 Feb 2026 17:56:11 -0700 Subject: [PATCH 3/4] BLD: Remove grafo from wheel --- .github/workflows/wheel.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index b51f1cb..a473242 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -30,11 +30,6 @@ jobs: shell: bash run: | python -m pip install dist/gfdl*.whl - # test deps need to be manually installed - # because of annoying version conflicts - # with grafo? - python -m pip install --no-deps "permetrics>=2.0.0" "mealpy>=3.0.2" - python -m pip install --no-deps graforvfl python -m pip install pandas ucimlrepo cd $RUNNER_TEMP python -m pytest --pyargs gfdl --cov=gfdl --cov-report=term-missing From 3d39af12073bc3dee35875f1e58d85c73c14f74b Mon Sep 17 00:00:00 2001 From: Navamita Ray Date: Wed, 4 Feb 2026 20:06:43 -0700 Subject: [PATCH 4/4] TST: More tests for comparing our regressor with grafo --- src/gfdl/tests/test_regression.py | 50 ++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/gfdl/tests/test_regression.py b/src/gfdl/tests/test_regression.py index 8c237b9..7e31e84 100644 --- a/src/gfdl/tests/test_regression.py +++ b/src/gfdl/tests/test_regression.py @@ -8,11 +8,6 @@ from gfdl.model import GFDLRegressor -activations = ["relu", "tanh", "sigmoid", "identity", "softmax", "softmin", - "log_sigmoid", "log_softmax"] -weights = ["uniform", "normal", "he_uniform", "lecun_uniform", - "glorot_uniform", "he_normal", "lecun_normal", "glorot_normal"] - @pytest.mark.parametrize("""n_samples, n_targets, @@ -22,17 +17,50 @@ reg_alpha, exp_preds_shape, exp_preds_median, - exp_preds_min""", [ + exp_preds_min, + exp_preds_r2""", [ # expected values are from the graforvfl library (100, 10, (100,), "relu", "glorot_normal", 10, (25, 10), - -29.31478018, -490.5751822), - (100, 10, (100,), "tanh", "uniform", 100, (25, 10), - -34.613165002, -327.82362807), + -29.31478018, -490.57518221, 0.97537085), + (100, 10, (100,), "tanh", "uniform", 1, (25, 10), + -43.03897314, -504.32794352, 0.98411997), + (100, 10, (100,), "log_softmax", "uniform", 1, (25, 10), + -30.56871963218171, -558.1388909597706, 0.9999532782125536), + (100, 10, (100,), "log_sigmoid", "normal", 10, (25, 10), + -19.5976250350991, -574.1699708675857, 0.9853855947182326), + (100, 10, (1000,), "softmin", "he_uniform", 1, (25, 10), + -57.91870287977487, -589.6707200160679, 0.9656730623177637), + (100, 10, (1000,), "softmax", "lecun_uniform", 10, (25, 10), + -51.938696542946786, -513.4094105001416, 0.9589931777194366), + (100, 100, (100,), "sigmoid", "glorot_uniform", 1, (25, 100), + -46.92889730988215, -1585.2331437646524, 0.6496204322668526), + (100, 100, (100,), "tanh", "he_normal", 10, (25, 100), + -5.531248709518545, -1131.5021652659007, 0.6018381457540279), + (100, 100, (1000,), "relu", "lecun_normal", 1, (25, 100), + -24.857674257413233, -1241.941403822942, 0.5954067650339964), + (100, 100, (1000,), "identity", "glorot_normal", 10, (25, 100), + -49.66037744636776, -1418.0996396366454, 0.6387637880009253), + (1000, 10, (100,), "log_softmax", "glorot_normal", 1, (250, 10), + -2.157983014856103, -821.8910528092026, 0.999999671320564), + (1000, 10, (100,), "log_sigmoid", "lecun_normal", 10, (250, 10), + -2.25281191108881, -813.3197346939389, 0.9998208055604957), + (1000, 10, (1000,), "softmin", "he_normal", 1, (250, 10), + -2.932635323616438, -819.9889270165279, 0.9999535335431835), + (1000, 10, (1000,), "softmax", "glorot_uniform", 10, (250, 10), + -3.27895924524588, -809.0526184106433, 0.9996980844468629), + (1000, 100, (100,), "sigmoid", "lecun_uniform", 1, (250, 100), + 40.193814730616296, -2003.2760146757932, 0.9999864051131802), + (1000, 100, (100,), "tanh", "he_normal", 10, (250, 100), + 38.349789631939906, -1968.7361166078529, 0.9984649082549426), + (1000, 100, (1000,), "relu", "normal", 1, (250, 100), + 47.91240910167704, -2194.259205351918, 0.8620693547752554), + (1000, 100, (1000,), "identity", "uniform", 10, (250, 100), + 39.788475103832646, -2004.3219743138504, 0.9999999882159872) ]) def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes, activation, weight_scheme, reg_alpha, exp_preds_shape, exp_preds_median, - exp_preds_min): + exp_preds_min, exp_preds_r2): N, d = n_samples, n_targets RNG = 42 X, y = make_regression(n_samples=N, @@ -65,9 +93,11 @@ def test_regression_against_grafo(n_samples, n_targets, hidden_layer_sizes, actual_preds_shape = actual_preds.shape actual_preds_median = np.median(actual_preds) actual_preds_min = actual_preds.min() + actual_preds_r2 = r2_score(y_test, actual_preds) np.testing.assert_allclose(actual_preds_shape, exp_preds_shape) np.testing.assert_allclose(actual_preds_median, exp_preds_median) np.testing.assert_allclose(actual_preds_min, exp_preds_min) + np.testing.assert_allclose(actual_preds_r2, exp_preds_r2) @parametrize_with_checks([GFDLRegressor()])