sklearn-morpho 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.SRCINFO +3 -3
  2. sklearn_morpho-0.2.2/.github/workflows/ci.yml +90 -0
  3. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.gitignore +2 -1
  4. sklearn_morpho-0.2.2/.qbuild/scripts/coverage +1 -0
  5. sklearn_morpho-0.2.2/.qbuild/scripts/main +1 -0
  6. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/l-DEP.ipynb +15 -15
  7. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/r-DEP.ipynb +15 -15
  8. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/simple_perceptron.ipynb +16 -9
  9. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/MREs/train_multiclass.ipynb +2 -1
  10. sklearn_morpho-0.2.2/PKG-INFO +10 -0
  11. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/PKGBUILD +2 -2
  12. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/README.md +6 -24
  13. sklearn_morpho-0.2.2/pyproject.toml +49 -0
  14. sklearn_morpho-0.2.2/src/sklearn_morpho/__init__.py +5 -0
  15. sklearn_morpho-0.2.2/src/sklearn_morpho/classifiers/__init__.py +3 -0
  16. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/ldep.py +51 -31
  17. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/rdep.py +45 -27
  18. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/classifiers/simple_perceptron.py +46 -27
  19. sklearn_morpho-0.2.2/src/sklearn_morpho/stopping/__init__.py +7 -0
  20. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_base.py +9 -2
  21. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_cost.py +8 -5
  22. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_early.py +8 -3
  23. sklearn_morpho-0.2.2/src/sklearn_morpho/stopping/stopping_epoch.py +24 -0
  24. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/stopping/stopping_train_stop.py +4 -2
  25. sklearn_morpho-0.2.2/src/sklearn_morpho/training/__init__.py +6 -0
  26. {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_ldep.py +81 -45
  27. {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_rdep.py +107 -55
  28. {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_simple_perceptron.py +48 -31
  29. {sklearn_morpho-0.2.1/src/sklearn_morpho/dccp → sklearn_morpho-0.2.2/src/sklearn_morpho/training}/dccp_wrapper.py +73 -41
  30. sklearn_morpho-0.2.2/src/sklearn_morpho/weighting/__init__.py +3 -0
  31. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_base.py +15 -8
  32. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_dist.py +11 -10
  33. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/src/sklearn_morpho/weighting/weighting_none.py +3 -0
  34. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/compare_estimators.py +75 -32
  35. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/compare_estimators_show.py +13 -7
  36. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/testing/display_boundary.py +32 -18
  37. sklearn_morpho-0.2.2/tests/integration/sklearn_test.py +23 -0
  38. sklearn_morpho-0.2.2/tests/stopping/cost_test.py +32 -0
  39. sklearn_morpho-0.2.2/tests/stopping/early_test.py +41 -0
  40. sklearn_morpho-0.2.2/tests/stopping/epoch_test.py +28 -0
  41. sklearn_morpho-0.2.2/tests/stopping/train_stop_test.py +20 -0
  42. sklearn_morpho-0.2.2/tests/training/friendly_dataset.py +31 -0
  43. sklearn_morpho-0.2.2/tests/training/test_dccp_wrapper.py +137 -0
  44. sklearn_morpho-0.2.2/tests/training/test_ldep.py +78 -0
  45. sklearn_morpho-0.2.2/tests/training/test_rdep.py +100 -0
  46. sklearn_morpho-0.2.2/tests/training/test_simple_perceptron.py +88 -0
  47. sklearn_morpho-0.2.2/tests/weighting/dist_test.py +36 -0
  48. sklearn_morpho-0.2.2/tests/weighting/none_test.py +50 -0
  49. sklearn_morpho-0.2.1/.github/workflows/ci.yml +0 -36
  50. sklearn_morpho-0.2.1/.qbuild/scripts/main +0 -1
  51. sklearn_morpho-0.2.1/PKG-INFO +0 -9
  52. sklearn_morpho-0.2.1/pyproject.toml +0 -32
  53. sklearn_morpho-0.2.1/src/sklearn_morpho/__init__.py +0 -5
  54. sklearn_morpho-0.2.1/src/sklearn_morpho/stopping/__init__.py +0 -5
  55. sklearn_morpho-0.2.1/src/sklearn_morpho/stopping/stopping_epoch.py +0 -21
  56. sklearn_morpho-0.2.1/src/sklearn_morpho/tests/moons_test.py +0 -16
  57. sklearn_morpho-0.2.1/src/sklearn_morpho/tests/sklearn_test.py +0 -19
  58. sklearn_morpho-0.2.1/src/sklearn_morpho/tests/train_test.py +0 -19
  59. sklearn_morpho-0.2.1/src/sklearn_morpho/weighting/__init__.py +0 -3
  60. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.github/workflows/pypi-publish.yml +0 -0
  61. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.gitlab-ci.yml +0 -0
  62. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/.qbuild/scripts/tests +0 -0
  63. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/LICENSE +0 -0
  64. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/check_todos.sh +0 -0
  65. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/pkgbuild_helper.sh +0 -0
  66. {sklearn_morpho-0.2.1 → sklearn_morpho-0.2.2}/pyrightconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  pkgbase = python-sklearn-morpho
2
2
  pkgdesc = Scikit-learn estimator toolbox for morphological perceptrons
3
- pkgver = 0.2.0
3
+ pkgver = 0.2.1
4
4
  pkgrel = 1
5
5
  url = https://github.com/d-002/sklearn-morpho
6
6
  arch = any
@@ -13,7 +13,7 @@ pkgbase = python-sklearn-morpho
13
13
  depends = python-cvxpy
14
14
  depends = python-pytest
15
15
  depends = python-scikit-learn
16
- source = https://files.pythonhosted.org/packages/source/s/sklearn_morpho/sklearn_morpho-0.2.0.tar.gz
17
- sha256sums = 0bba96bdf9e0d0c982276da7b7503c1812c337071bca4e813b2eb6eb0ab8e6bd
16
+ source = https://files.pythonhosted.org/packages/source/s/sklearn_morpho/sklearn_morpho-0.2.1.tar.gz
17
+ sha256sums = aaf94e85e2f546928ace464bf776c08681cd5349a48012076c3c66a9e801961f
18
18
 
19
19
  pkgname = python-sklearn-morpho
@@ -0,0 +1,90 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ "master" ]
6
+ pull_request:
7
+ branches: [ "**" ]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test on ${{ matrix.os }}
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ matrix:
15
+ os: [ ubuntu-latest, windows-latest, macos-latest ]
16
+
17
+ steps:
18
+ - name: Checkout repository
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.13"
25
+
26
+ - name: Install Hatch
27
+ run: python -m pip install hatch
28
+
29
+ - name: Run tests
30
+ run: hatch run pytest
31
+
32
+ coverage:
33
+ name: Coverage test, ignore failed tests
34
+ runs-on: ubuntu-latest
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v4
39
+
40
+ - name: Set up Python
41
+ uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.13"
44
+
45
+ - name: Install Hatch
46
+ run: python -m pip install hatch
47
+
48
+ - name: Install coverage utilities
49
+ run: python -m pip install coverage
50
+
51
+ - name: Check coverage
52
+ run: |
53
+ hatch run pytest --cov --cov-fail-under=90
54
+
55
+ todo:
56
+ name: Check for TODOs in code
57
+ runs-on: ubuntu-latest
58
+
59
+ steps:
60
+ - name: Checkout repository
61
+ uses: actions/checkout@v4
62
+
63
+ - name: Scan for TODOs in the code
64
+ run: ./check_todos.sh
65
+
66
+ linter:
67
+ name: Check for ${{ matrix.name }} errors
68
+ runs-on: ubuntu-latest
69
+ strategy:
70
+ matrix:
71
+ include:
72
+ - name: "linting"
73
+ command: "python -m ruff check"
74
+ - name: "coding style"
75
+ command: "[ -z \"$(python -m ruff format | grep reformatted)\" ]"
76
+
77
+ steps:
78
+ - name: Checkout repository
79
+ uses: actions/checkout@v4
80
+
81
+ - name: Set up Python
82
+ uses: actions/setup-python@v5
83
+ with:
84
+ python-version: "3.13"
85
+
86
+ - name: Install linter
87
+ run: python -m pip install ruff
88
+
89
+ - name: Run linter for coding style errors
90
+ run: ${{ matrix.command }}
@@ -1,7 +1,8 @@
1
1
  # python
2
2
  __pycache__
3
- venv
4
3
  *.pyc
4
+ .coverage
5
+ htmlcov
5
6
 
6
7
  # vim
7
8
  *.swp
@@ -0,0 +1 @@
1
+ hatch run pytest --cov --cov-report=html && xdg-open htmlcov/index.html
@@ -0,0 +1 @@
1
+ python testing/display_boundary.py
@@ -66,7 +66,7 @@
66
66
  "source": [
67
67
  "random_state = np.random.RandomState(42)\n",
68
68
  "\n",
69
- "X, y = make_moons(n_samples=1000, noise=.2, random_state=random_state)\n",
69
+ "X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
70
70
  "y = np.array(['red', 'blue'])[y]"
71
71
  ]
72
72
  },
@@ -890,7 +890,8 @@
890
890
  "ldep = LDEP(verbose=1, random_state=random_state)\n",
891
891
  "\n",
892
892
  "X_train, X_test, y_train, y_test = train_test_split(\n",
893
- " X, y, test_size=.3, random_state=random_state)\n",
893
+ " X, y, test_size=0.3, random_state=random_state\n",
894
+ ")\n",
894
895
  "\n",
895
896
  "ldep.fit(X_train, y_train)"
896
897
  ]
@@ -926,12 +927,7 @@
926
927
  ],
927
928
  "source": [
928
929
  "ldep_dccp = LDEP(use_dccp_library=True, verbose=1, random_state=random_state)\n",
929
- "\n",
930
- "# ignore warnings coming from the DCCP library\n",
931
- "import warnings\n",
932
- "with warnings.catch_warnings(action=\"ignore\"):\n",
933
- " ldep_dccp.fit(X_train, y_train)\n",
934
- "del warnings"
930
+ "ldep_dccp.fit(X_train, y_train)"
935
931
  ]
936
932
  },
937
933
  {
@@ -991,7 +987,9 @@
991
987
  "outputs": [],
992
988
  "source": [
993
989
  "score_test = f1_score(y_test, ldep.predict(X_test), pos_label=np.unique(y)[1])\n",
994
- "score_test_dccp = f1_score(y_test, ldep_dccp.predict(X_test), pos_label=np.unique(y)[1])"
990
+ "score_test_dccp = f1_score(\n",
991
+ " y_test, ldep_dccp.predict(X_test), pos_label=np.unique(y)[1]\n",
992
+ ")"
995
993
  ]
996
994
  },
997
995
  {
@@ -1031,18 +1029,20 @@
1031
1029
  ],
1032
1030
  "source": [
1033
1031
  "for name, estimator, score in (\n",
1034
- " ('l-DEP with manual linearization', ldep, score_test),\n",
1035
- " ('l-DEP with dccp library', ldep_dccp, score_test_dccp)\n",
1032
+ " ('l-DEP with manual linearization', ldep, score_test),\n",
1033
+ " ('l-DEP with dccp library', ldep_dccp, score_test_dccp),\n",
1036
1034
  "):\n",
1037
1035
  " # draw the decision boundary\n",
1038
1036
  " disp = DecisionBoundaryDisplay.from_estimator(\n",
1039
- " estimator, X_test, response_method='decision_function',\n",
1037
+ " estimator,\n",
1038
+ " X_test,\n",
1039
+ " response_method='decision_function',\n",
1040
1040
  " grid_resolution=200,\n",
1041
1041
  " plot_method='contour',\n",
1042
1042
  " levels=[0],\n",
1043
- " colors='black'\n",
1043
+ " colors='black',\n",
1044
1044
  " )\n",
1045
- " \n",
1045
+ "\n",
1046
1046
  " # add the points\n",
1047
1047
  " ax = disp.ax_\n",
1048
1048
  " ax.scatter(*X_test.T, color=y_test)\n",
@@ -1067,7 +1067,7 @@
1067
1067
  "name": "python",
1068
1068
  "nbconvert_exporter": "python",
1069
1069
  "pygments_lexer": "ipython3",
1070
- "version": "3.14.5"
1070
+ "version": "3.13.9"
1071
1071
  }
1072
1072
  },
1073
1073
  "nbformat": 4,
@@ -62,7 +62,7 @@
62
62
  "source": [
63
63
  "random_state = np.random.RandomState(42)\n",
64
64
  "\n",
65
- "X, y = make_moons(n_samples=1000, noise=.2, random_state=random_state)\n",
65
+ "X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
66
66
  "y = np.array(['red', 'blue'])[y]"
67
67
  ]
68
68
  },
@@ -889,7 +889,8 @@
889
889
  "rdep = RDEP(verbose=1, random_state=random_state)\n",
890
890
  "\n",
891
891
  "X_train, X_test, y_train, y_test = train_test_split(\n",
892
- " X, y, test_size=.3, random_state=random_state)\n",
892
+ " X, y, test_size=0.3, random_state=random_state\n",
893
+ ")\n",
893
894
  "\n",
894
895
  "rdep.fit(X_train, y_train)"
895
896
  ]
@@ -925,12 +926,7 @@
925
926
  ],
926
927
  "source": [
927
928
  "rdep_dccp = RDEP(use_dccp_library=True, verbose=1, random_state=random_state)\n",
928
- "\n",
929
- "# ignore warnings coming from the DCCP library\n",
930
- "import warnings\n",
931
- "with warnings.catch_warnings(action=\"ignore\"):\n",
932
- " rdep_dccp.fit(X_train, y_train)\n",
933
- "del warnings"
929
+ "rdep_dccp.fit(X_train, y_train)"
934
930
  ]
935
931
  },
936
932
  {
@@ -990,7 +986,9 @@
990
986
  "outputs": [],
991
987
  "source": [
992
988
  "score_test = f1_score(y_test, rdep.predict(X_test), pos_label=np.unique(y)[1])\n",
993
- "score_test_dccp = f1_score(y_test, rdep_dccp.predict(X_test), pos_label=np.unique(y)[1])"
989
+ "score_test_dccp = f1_score(\n",
990
+ " y_test, rdep_dccp.predict(X_test), pos_label=np.unique(y)[1]\n",
991
+ ")"
994
992
  ]
995
993
  },
996
994
  {
@@ -1030,18 +1028,20 @@
1030
1028
  ],
1031
1029
  "source": [
1032
1030
  "for name, estimator, score in (\n",
1033
- " ('r-DEP with manual linearization', rdep, score_test),\n",
1034
- " ('r-DEP with dccp library', rdep_dccp, score_test_dccp)\n",
1031
+ " ('r-DEP with manual linearization', rdep, score_test),\n",
1032
+ " ('r-DEP with dccp library', rdep_dccp, score_test_dccp),\n",
1035
1033
  "):\n",
1036
1034
  " # draw the decision boundary\n",
1037
1035
  " disp = DecisionBoundaryDisplay.from_estimator(\n",
1038
- " estimator, X_test, response_method='decision_function',\n",
1036
+ " estimator,\n",
1037
+ " X_test,\n",
1038
+ " response_method='decision_function',\n",
1039
1039
  " grid_resolution=200,\n",
1040
1040
  " plot_method='contour',\n",
1041
1041
  " levels=[0],\n",
1042
- " colors='black'\n",
1042
+ " colors='black',\n",
1043
1043
  " )\n",
1044
- " \n",
1044
+ "\n",
1045
1045
  " # add the points\n",
1046
1046
  " ax = disp.ax_\n",
1047
1047
  " ax.scatter(*X_test.T, color=y_test)\n",
@@ -1066,7 +1066,7 @@
1066
1066
  "name": "python",
1067
1067
  "nbconvert_exporter": "python",
1068
1068
  "pygments_lexer": "ipython3",
1069
- "version": "3.14.5"
1069
+ "version": "3.13.9"
1070
1070
  }
1071
1071
  },
1072
1072
  "nbformat": 4,
@@ -64,7 +64,7 @@
64
64
  "source": [
65
65
  "random_state = np.random.RandomState(42)\n",
66
66
  "\n",
67
- "X, y = make_moons(n_samples=1000, noise=.2, random_state=random_state)\n",
67
+ "X, y = make_moons(n_samples=1000, noise=0.2, random_state=random_state)\n",
68
68
  "y = np.array(['red', 'blue'])[y]"
69
69
  ]
70
70
  },
@@ -907,7 +907,8 @@
907
907
  "perceptron_min = MP(kind='min', verbose=1, random_state=random_state)\n",
908
908
  "\n",
909
909
  "X_train, X_test, y_train, y_test = train_test_split(\n",
910
- " X, y, test_size=.3, random_state=random_state)\n",
910
+ " X, y, test_size=0.3, random_state=random_state\n",
911
+ ")\n",
911
912
  "\n",
912
913
  "perceptron_max.fit(X_train, y_train)\n",
913
914
  "perceptron_min.fit(X_train, y_train)"
@@ -969,8 +970,12 @@
969
970
  "metadata": {},
970
971
  "outputs": [],
971
972
  "source": [
972
- "score_test_max = f1_score(y_test, perceptron_max.predict(X_test), pos_label=np.unique(y)[1])\n",
973
- "score_test_min = f1_score(y_test, perceptron_min.predict(X_test), pos_label=np.unique(y)[1])"
973
+ "score_test_max = f1_score(\n",
974
+ " y_test, perceptron_max.predict(X_test), pos_label=np.unique(y)[1]\n",
975
+ ")\n",
976
+ "score_test_min = f1_score(\n",
977
+ " y_test, perceptron_min.predict(X_test), pos_label=np.unique(y)[1]\n",
978
+ ")"
974
979
  ]
975
980
  },
976
981
  {
@@ -1010,18 +1015,20 @@
1010
1015
  ],
1011
1016
  "source": [
1012
1017
  "for name, estimator, score in (\n",
1013
- " ('dilation perceptron', perceptron_max, score_test_max),\n",
1014
- " ('erosion perceptron', perceptron_min, score_test_min)\n",
1018
+ " ('dilation perceptron', perceptron_max, score_test_max),\n",
1019
+ " ('erosion perceptron', perceptron_min, score_test_min),\n",
1015
1020
  "):\n",
1016
1021
  " # draw the decision boundary\n",
1017
1022
  " disp = DecisionBoundaryDisplay.from_estimator(\n",
1018
- " estimator, X_test, response_method='decision_function',\n",
1023
+ " estimator,\n",
1024
+ " X_test,\n",
1025
+ " response_method='decision_function',\n",
1019
1026
  " grid_resolution=200,\n",
1020
1027
  " plot_method='contour',\n",
1021
1028
  " levels=[0],\n",
1022
- " colors='black'\n",
1029
+ " colors='black',\n",
1023
1030
  " )\n",
1024
- " \n",
1031
+ "\n",
1025
1032
  " # add the points\n",
1026
1033
  " ax = disp.ax_\n",
1027
1034
  " ax.scatter(*X_test.T, color=y_test)\n",
@@ -60,7 +60,8 @@
60
60
  "X, y = fetch_openml('diabetes', version=1, return_X_y=True, as_frame=False)\n",
61
61
  "\n",
62
62
  "X_train, X_test, y_train, y_test = train_test_split(\n",
63
- " X, y, test_size=.3, random_state=random_state)"
63
+ " X, y, test_size=0.3, random_state=random_state\n",
64
+ ")"
64
65
  ]
65
66
  },
66
67
  {
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: sklearn-morpho
3
+ Version: 0.2.2
4
+ License-File: LICENSE
5
+ Requires-Dist: cvxpy<2.0.0,>=1.9.0
6
+ Requires-Dist: numpy>=2.0.0
7
+ Requires-Dist: scikit-learn<2.0.0,>=1.8.0
8
+ Provides-Extra: dccp
9
+ Requires-Dist: dccp<2.0.0,>=1.1.1; extra == 'dccp'
10
+ Requires-Dist: pytest<10.0.0,>=9.0.0; extra == 'dccp'
@@ -1,6 +1,6 @@
1
1
  pkgname=python-sklearn-morpho
2
2
  _name=sklearn_morpho
3
- pkgver=0.2.1
3
+ pkgver=0.2.2
4
4
  pkgrel=1
5
5
  pkgdesc="Scikit-learn estimator toolbox for morphological perceptrons"
6
6
  arch=("any")
@@ -9,7 +9,7 @@ license=("MIT")
9
9
  depends=("python" "python-cvxpy" "python-pytest" "python-scikit-learn")
10
10
  makedepends=("python-build" "python-installer" "python-wheel" "python-hatchling")
11
11
  source=("https://files.pythonhosted.org/packages/source/${_name::1}/${_name}/${_name}-${pkgver}.tar.gz")
12
- sha256sums=('0bba96bdf9e0d0c982276da7b7503c1812c337071bca4e813b2eb6eb0ab8e6bd')
12
+ sha256sums=('aaf94e85e2f546928ace464bf776c08681cd5349a48012076c3c66a9e801961f')
13
13
 
14
14
  build() {
15
15
  cd "${_name}-${pkgver}"
@@ -6,37 +6,18 @@ scikit-learn estimator toolbox for morphological perceptrons.
6
6
  ![](https://img.shields.io/aur/version/python-sklearn-morpho?style=flat-square)
7
7
  ![](https://img.shields.io/github/actions/workflow/status/d-002/sklearn-morpho/ci.yml?style=flat-square)
8
8
 
9
- > [!WARNING]
10
- > Some features of this repository use the `dccp` module.
11
- >
12
- > As of writing this (June 2026) the PyPI package has not been updated since
13
- > 2025 ([this commit](https://github.com/cvxgrp/dccp/commit/4322809)).
14
- > This means that currently the `dccp` module uses `cvxpy.reshape` with soon to
15
- > be outdated parameters.
16
- >
17
- > As of writing this, when using this solver warnings from cvxpy are displayed,
18
- > however these warnings are projected to be removed in future `cvxpy` versions,
19
- > which may create silent errors during training.
20
- >
21
- > I will leave this notice here until the `dccp` library's maintainer team
22
- > decides to update the package.
23
- > Please let me know if that is the case and I did not remove the notice.
24
- > See also: [issue](https://github.com/cvxgrp/dccp/issues/127)
25
- >
26
- > If you wish to use the solver in its current state without the warnings, you
27
- > can manually clone the dccp repo into the hatch venv.
28
-
29
9
  Current features:
30
10
 
31
11
  - Scikit-learn estimators:
32
12
  - Linear Dilation-Erosion Perceptron (l-DEP)
33
13
  - Reduced Dilation-Erosion Perceptron (r-DEP)
34
- - Simple dilation and erosion morphological perceptrons
14
+ - Simple Dilation and Erosion Morphological Perceptrons
35
15
  - Modular wrapper for DCCP optimization tasks with `cvxpy`
36
16
 
37
17
  File tree:
38
18
 
39
- - `src/sklearn_morpho`: contains the source code and a testsuite in `.../tests`
19
+ - `src/sklearn_morpho`: contains the source code and a testsuite in its `tests`
20
+ subdirectory.
40
21
  - `testing`: standalone files that use this library, may contain tests but they
41
22
  are not designed to be run as a CI testsuite for example.
42
23
  - `MREs`: standalone jupyter notebooks to showcase some of this library's
@@ -45,7 +26,7 @@ File tree:
45
26
  ## Getting started
46
27
 
47
28
  Take a look at the Jupyter code examples in the
48
- [`MREs`](https://github.com/d-002/sklearn-morpho/tree/master/MREs) directory.
29
+ [MREs](https://github.com/d-002/sklearn-morpho/tree/master/MREs) directory.
49
30
 
50
31
  ## Running the project
51
32
 
@@ -64,7 +45,8 @@ files to avoid training the estimators every time one wants to view the results.
64
45
 
65
46
  The package is available in the
66
47
  [Arch User Repository](https://aur.archlinux.org) as `python-sklearn-morpho`, as
67
- are all its dependencies except `dccp`.
48
+ are all its dependencies not already in the official packages, except for
49
+ `dccp`.
68
50
 
69
51
  Since the latter is an optional dependency, you can either:
70
52
 
@@ -0,0 +1,49 @@
1
+ # General
2
+
3
+ [build-system]
4
+ requires = ["hatchling"]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "sklearn-morpho"
9
+ version = "0.2.2"
10
+ dependencies = [
11
+ "cvxpy>=1.9.0,<2.0.0",
12
+ "numpy>=2.0.0",
13
+ "scikit-learn>=1.8.0,<2.0.0",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dccp = [
18
+ "dccp>=1.1.1,<2.0.0",
19
+ "pytest>=9.0.0,<10.0.0",
20
+ ]
21
+
22
+ [tool.pytest.ini_options]
23
+ testpaths = ["tests"]
24
+
25
+ [tool.hatch.envs.default]
26
+ python = "3.13"
27
+ path = ".venv" # for LSPs
28
+ dependencies = [
29
+ "dccp>=1.1.1,<2.0.0",
30
+ "jupyterlab>=4.5.0,<5.0.0", # for coding the MREs
31
+ "matplotlib>=3.10.0,<4.0.0", # for files in testing
32
+ "pandas>=3.0.0", # for scikit-learn's check_estimator and openml datasets
33
+ "pyqt6>=6.11.0,<7.0.0", # for matplotlib display on some devices
34
+ "pytest>=9.0.0,<10.0.0",
35
+ "pytest-cov>=7.1.0,<8.0.0",
36
+ "ruff>=0.15.0,<1.0.0", # linter
37
+ ]
38
+
39
+ # Ruff
40
+ [tool.ruff]
41
+ indent-width = 4
42
+ line-length = 80
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E"]
46
+
47
+ [tool.ruff.format]
48
+ quote-style = "single"
49
+ indent-style = "space"
@@ -0,0 +1,5 @@
1
+ from .classifiers import (
2
+ LDEP as LDEP,
3
+ RDEP as RDEP,
4
+ MorphoPerceptron as MorphoPerceptron,
5
+ )
@@ -0,0 +1,3 @@
1
+ from .ldep import LDEP as LDEP
2
+ from .rdep import RDEP as RDEP
3
+ from .simple_perceptron import MorphoPerceptron as MorphoPerceptron
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import numpy as np
2
4
  from typing import Literal, cast
3
5
 
@@ -7,16 +9,17 @@ from sklearn.utils import check_random_state
7
9
  from sklearn.utils.validation import validate_data, check_is_fitted
8
10
  from sklearn.utils.multiclass import unique_labels
9
11
 
10
- from ..dccp.dccp_ldep import LDEPDccpTrainer
12
+ from ..training.dccp_ldep import LDEPDccpTrainer
11
13
  from ..stopping import (
12
- StoppingMethod,
13
- CostStoppingMethod,
14
- EarlyStoppingMethod,
15
- EpochStoppingMethod,
16
- TrainStopStoppingMethod,
14
+ StoppingMethod,
15
+ CostStoppingMethod,
16
+ EarlyStoppingMethod,
17
+ EpochStoppingMethod,
18
+ TrainStopStoppingMethod,
17
19
  )
18
20
  from ..weighting import SampleWeighting, NoneSampleWeighting
19
21
 
22
+
20
23
  class LDEP(ClassifierMixin, BaseEstimator):
21
24
  """
22
25
  Scikit-learn estimator wrapper around a l-DEP (linear Dilation-Erosion
@@ -35,12 +38,18 @@ class LDEP(ClassifierMixin, BaseEstimator):
35
38
  times, but will allow the decision boundary to be more complex.
36
39
  """
37
40
 
38
- def __init__(self, latent_dims: tuple[int, int] = (10, 10), margin = 1.,
39
- penalty = 0., validation_ratio = .3,
40
- weighting_method: SampleWeighting | None = None,
41
- stopping_methods: list[StoppingMethod] | None = None,
42
- use_dccp_library: bool = False, verbose: Literal[0, 1, 2] = 0,
43
- random_state: np.random.RandomState | None = None) -> None:
41
+ def __init__(
42
+ self,
43
+ latent_dims: tuple[int, int] = (10, 10),
44
+ margin=1.0,
45
+ penalty=0.0,
46
+ validation_ratio=0.3,
47
+ weighting_method: SampleWeighting | None = None,
48
+ stopping_methods: list[StoppingMethod] | None = None,
49
+ use_dccp_library: bool = False,
50
+ verbose: Literal[0, 1, 2] = 0,
51
+ random_state: np.random.RandomState | None = None,
52
+ ) -> None:
44
53
  """
45
54
  Initialize the classifier, see class help for more.
46
55
 
@@ -70,9 +79,9 @@ class LDEP(ClassifierMixin, BaseEstimator):
70
79
  to the epoch with the best validation cost.
71
80
  If left to None, will use:
72
81
  [
73
- CostStoppingMethod(1e-6),
74
- EarlyStoppingMethod(5),
75
- EpochStoppingMethod(20),
82
+ CostStoppingMethod(),
83
+ EarlyStoppingMethod(),
84
+ EpochStoppingMethod(),
76
85
  TrainStopStoppingMethod(),
77
86
  ]
78
87
  Ignored when using the dccp library solver.
@@ -110,7 +119,7 @@ class LDEP(ClassifierMixin, BaseEstimator):
110
119
 
111
120
  # input data validation
112
121
  random_state = check_random_state(self.random_state)
113
- X, y = validate_data(self, X, y) # type: ignore
122
+ X, y = validate_data(self, X, y) # type: ignore
114
123
  self.scaler_ = StandardScaler()
115
124
  X_scaled = self.scaler_.fit_transform(X)
116
125
 
@@ -121,9 +130,9 @@ class LDEP(ClassifierMixin, BaseEstimator):
121
130
  weighting_method = self.weighting_method
122
131
  if self.stopping_methods is None:
123
132
  stopping_methods = [
124
- CostStoppingMethod(1e-6),
125
- EarlyStoppingMethod(5),
126
- EpochStoppingMethod(20),
133
+ CostStoppingMethod(),
134
+ EarlyStoppingMethod(),
135
+ EpochStoppingMethod(),
127
136
  TrainStopStoppingMethod(),
128
137
  ]
129
138
  else:
@@ -133,18 +142,27 @@ class LDEP(ClassifierMixin, BaseEstimator):
133
142
  # the classes are persisted inside the object for use in predict
134
143
  self.classes_ = unique_labels(y)
135
144
  classes_list = list(self.classes_)
136
- y_integers = np.array([classes_list.index(c) for c in y],
137
- dtype=np.int32)
145
+ y_integers = np.array(
146
+ [classes_list.index(c) for c in y], dtype=np.int32
147
+ )
138
148
 
139
149
  if len(classes_list) != 2:
140
- raise ValueError('Only binary classification is supported but '
141
- f'got {len(classes_list)} class(es).')
150
+ raise ValueError(
151
+ 'Only binary classification is supported but '
152
+ f'got {len(classes_list)} class(es).'
153
+ )
142
154
 
143
155
  # create and train perceptrons
144
156
  trainer = LDEPDccpTrainer(
145
- self.latent_dims, self.margin, self.penalty, self.validation_ratio,
146
- weighting_method, stopping_methods, self.use_dccp_library,
147
- self.verbose, random_state
157
+ self.latent_dims,
158
+ self.margin,
159
+ self.penalty,
160
+ self.validation_ratio,
161
+ weighting_method,
162
+ stopping_methods,
163
+ self.use_dccp_library,
164
+ self.verbose,
165
+ random_state,
148
166
  )
149
167
 
150
168
  trainer.train(X_scaled, y_integers)
@@ -158,13 +176,15 @@ class LDEP(ClassifierMixin, BaseEstimator):
158
176
 
159
177
  def decision_function(self, X: np.ndarray) -> np.ndarray:
160
178
  check_is_fitted(self)
161
- X = validate_data(self, X, reset=False) # type: ignore
179
+ X = validate_data(self, X, reset=False) # type: ignore
162
180
  X_scaled = cast(np.ndarray, self.scaler_.transform(X))
163
181
 
164
- expr_max = np.max(self.max_perceptron_ + X_scaled @ self.max_matrix_.T,
165
- axis=1)
166
- expr_min = np.min(self.min_perceptron_ + X_scaled @ self.min_matrix_.T,
167
- axis=1)
182
+ expr_max = np.max(
183
+ self.max_perceptron_ + X_scaled @ self.max_matrix_.T, axis=1
184
+ )
185
+ expr_min = np.min(
186
+ self.min_perceptron_ + X_scaled @ self.min_matrix_.T, axis=1
187
+ )
168
188
  return expr_max * self.lambda_ + expr_min * (1 - self.lambda_)
169
189
 
170
190
  def predict(self, X: np.ndarray) -> np.ndarray: