opfython 1.0.12__tar.gz → 2.0.0__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 (54) hide show
  1. opfython-2.0.0/PKG-INFO +128 -0
  2. opfython-2.0.0/README.md +97 -0
  3. {opfython-1.0.12 → opfython-2.0.0}/opfython/__init__.py +1 -2
  4. {opfython-1.0.12 → opfython-2.0.0}/opfython/core/__init__.py +2 -3
  5. opfython-2.0.0/opfython/core/heap.py +208 -0
  6. {opfython-1.0.12 → opfython-2.0.0}/opfython/core/node.py +19 -70
  7. opfython-2.0.0/opfython/core/opf.py +169 -0
  8. opfython-2.0.0/opfython/core/subgraph.py +167 -0
  9. opfython-2.0.0/opfython/math/__init__.py +1 -0
  10. {opfython-1.0.12 → opfython-2.0.0}/opfython/math/distance.py +1 -2
  11. {opfython-1.0.12 → opfython-2.0.0}/opfython/math/general.py +3 -34
  12. opfython-2.0.0/opfython/math/random.py +23 -0
  13. {opfython-1.0.12 → opfython-2.0.0}/opfython/models/__init__.py +3 -4
  14. {opfython-1.0.12 → opfython-2.0.0}/opfython/models/knn_supervised.py +17 -96
  15. {opfython-1.0.12 → opfython-2.0.0}/opfython/models/semi_supervised.py +11 -43
  16. {opfython-1.0.12 → opfython-2.0.0}/opfython/models/supervised.py +49 -162
  17. {opfython-1.0.12 → opfython-2.0.0}/opfython/models/unsupervised.py +22 -137
  18. opfython-2.0.0/opfython/stream/__init__.py +1 -0
  19. opfython-2.0.0/opfython/stream/loader.py +55 -0
  20. {opfython-1.0.12 → opfython-2.0.0}/opfython/stream/parser.py +2 -23
  21. opfython-2.0.0/opfython/stream/splitter.py +87 -0
  22. opfython-2.0.0/opfython/subgraphs/__init__.py +3 -0
  23. opfython-2.0.0/opfython/subgraphs/knn.py +227 -0
  24. opfython-2.0.0/opfython/utils/__init__.py +1 -0
  25. {opfython-1.0.12 → opfython-2.0.0}/opfython/utils/constants.py +1 -2
  26. opfython-2.0.0/opfython/utils/converter.py +66 -0
  27. {opfython-1.0.12 → opfython-2.0.0}/opfython/utils/decorator.py +1 -2
  28. opfython-2.0.0/opfython/utils/exception.py +48 -0
  29. {opfython-1.0.12 → opfython-2.0.0}/opfython/utils/logging.py +4 -35
  30. opfython-2.0.0/pyproject.toml +64 -0
  31. opfython-1.0.12/PKG-INFO +0 -200
  32. opfython-1.0.12/README.md +0 -174
  33. opfython-1.0.12/opfython/core/heap.py +0 -378
  34. opfython-1.0.12/opfython/core/opf.py +0 -288
  35. opfython-1.0.12/opfython/core/subgraph.py +0 -232
  36. opfython-1.0.12/opfython/math/__init__.py +0 -2
  37. opfython-1.0.12/opfython/math/random.py +0 -48
  38. opfython-1.0.12/opfython/stream/__init__.py +0 -2
  39. opfython-1.0.12/opfython/stream/loader.py +0 -112
  40. opfython-1.0.12/opfython/stream/splitter.py +0 -150
  41. opfython-1.0.12/opfython/subgraphs/__init__.py +0 -4
  42. opfython-1.0.12/opfython/subgraphs/knn.py +0 -317
  43. opfython-1.0.12/opfython/utils/__init__.py +0 -2
  44. opfython-1.0.12/opfython/utils/converter.py +0 -173
  45. opfython-1.0.12/opfython/utils/exception.py +0 -98
  46. opfython-1.0.12/opfython.egg-info/PKG-INFO +0 -200
  47. opfython-1.0.12/opfython.egg-info/SOURCES.txt +0 -37
  48. opfython-1.0.12/opfython.egg-info/dependency_links.txt +0 -1
  49. opfython-1.0.12/opfython.egg-info/requires.txt +0 -11
  50. opfython-1.0.12/opfython.egg-info/top_level.txt +0 -1
  51. opfython-1.0.12/pyproject.toml +0 -2
  52. opfython-1.0.12/setup.cfg +0 -14
  53. opfython-1.0.12/setup.py +0 -45
  54. {opfython-1.0.12 → opfython-2.0.0}/LICENSE +0 -0
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.5
2
+ Name: opfython
3
+ Version: 2.0.0
4
+ Summary: Python-Inspired Optimum-Path Forest Classifier
5
+ Author-email: Gustavo Rosa <gustavo.rosa@unesp.br>
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: Education
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ License-File: LICENSE
20
+ Requires-Dist: numba>=0.67.0
21
+ Requires-Dist: numpy>=2.4.4
22
+ Requires-Dist: coverage>=7.10.0 ; extra == "tests"
23
+ Requires-Dist: flake8>=7.3.0 ; extra == "tests"
24
+ Requires-Dist: pytest>=9.0.0 ; extra == "tests"
25
+ Project-URL: Documentation, https://opfython.readthedocs.io
26
+ Project-URL: Homepage, https://github.com/gugarosa/opfython
27
+ Project-URL: Issues, https://github.com/gugarosa/opfython/issues
28
+ Provides-Extra: tests
29
+ Import-Name: opfython
30
+
31
+ # OPFython
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/opfython.svg)](https://pypi.org/project/opfython/)
34
+ [![CI](https://github.com/gugarosa/opfython/actions/workflows/ci.yml/badge.svg)](https://github.com/gugarosa/opfython/actions/workflows/ci.yml)
35
+ [![Documentation](https://readthedocs.org/projects/opfython/badge/?version=latest)](https://opfython.readthedocs.io/)
36
+ [![DOI](https://img.shields.io/badge/DOI-10.1016/j.simpa.2021.100113-006DB9.svg)](https://doi.org/10.1016/j.simpa.2021.100113)
37
+ [![License](https://img.shields.io/github/license/gugarosa/opfython.svg)](LICENSE)
38
+
39
+ OPFython is a Python implementation of the Optimum-Path Forest family of
40
+ classifiers. It provides supervised, semi-supervised, unsupervised, and
41
+ KNN-supervised models backed by NumPy and Numba.
42
+
43
+ This implementation follows [LibOPF](https://github.com/jppbsi/LibOPF).
44
+ Please cite the original LibOPF authors as well as OPFython when using it in
45
+ research.
46
+
47
+ OPFython requires Python 3.11 or newer.
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ uv add opfython
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ ```python
58
+ import numpy as np
59
+
60
+ from opfython.models import SupervisedOPF
61
+
62
+ X_train = np.asarray([[0.0, 0.0], [0.1, 0.2], [1.0, 1.0], [1.1, 0.9]])
63
+ Y_train = np.asarray([0, 0, 1, 1])
64
+ X_test = np.asarray([[0.05, 0.1], [1.05, 1.0]])
65
+
66
+ classifier = SupervisedOPF()
67
+ classifier.fit(X_train, Y_train)
68
+ predictions = classifier.predict(X_test)
69
+ ```
70
+
71
+ Labels must be zero-based and sequential. Pre-computed distance matrices can
72
+ be supplied through each classifier's `pre_computed_distance` constructor
73
+ argument.
74
+
75
+ ## Classifiers
76
+
77
+ | Class | Purpose |
78
+ |---|---|
79
+ | `SupervisedOPF` | Complete-graph supervised classification |
80
+ | `KNNSupervisedOPF` | Supervised classification with learned KNN adjacency |
81
+ | `SemiSupervisedOPF` | Learning from labeled and unlabeled samples |
82
+ | `UnsupervisedOPF` | Density-based clustering and label propagation |
83
+
84
+ The package also includes 47 distance metrics, random generators, OPF
85
+ evaluation measures, dataset loaders and splitters, package logging and
86
+ exception helpers, and converters for LibOPF binary datasets.
87
+
88
+ See [the documentation](https://opfython.readthedocs.io/) and the
89
+ [`examples/applications`](examples/applications) directory for complete
90
+ workflows.
91
+
92
+ ## Development
93
+
94
+ ```bash
95
+ uv sync --all-groups
96
+ uv run pytest
97
+ uv run pre-commit run --all-files
98
+ uv run --group docs sphinx-build -W -b html docs docs/_build/html
99
+ uv build --no-sources
100
+ ```
101
+
102
+ ## Citation
103
+
104
+ ```bibtex
105
+ @article{rosa2021simpa,
106
+ title = {OPFython: A Python implementation for Optimum-Path Forest},
107
+ author = {Gustavo H. {de Rosa} and Joao P. Papa},
108
+ journal = {Software Impacts},
109
+ pages = {100113},
110
+ year = {2021},
111
+ issn = {2665-9638},
112
+ doi = {https://doi.org/10.1016/j.simpa.2021.100113}
113
+ }
114
+ ```
115
+
116
+ ```bibtex
117
+ @misc{rosa2021speedup,
118
+ title = {Speeding Up OPFython with Numba},
119
+ author = {Gustavo H. de Rosa and Joao Paulo Papa},
120
+ year = {2021},
121
+ eprint = {2106.11828},
122
+ archivePrefix = {arXiv},
123
+ primaryClass = {cs.LG}
124
+ }
125
+ ```
126
+
127
+ OPFython is licensed under the Apache License 2.0.
128
+
@@ -0,0 +1,97 @@
1
+ # OPFython
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/opfython.svg)](https://pypi.org/project/opfython/)
4
+ [![CI](https://github.com/gugarosa/opfython/actions/workflows/ci.yml/badge.svg)](https://github.com/gugarosa/opfython/actions/workflows/ci.yml)
5
+ [![Documentation](https://readthedocs.org/projects/opfython/badge/?version=latest)](https://opfython.readthedocs.io/)
6
+ [![DOI](https://img.shields.io/badge/DOI-10.1016/j.simpa.2021.100113-006DB9.svg)](https://doi.org/10.1016/j.simpa.2021.100113)
7
+ [![License](https://img.shields.io/github/license/gugarosa/opfython.svg)](LICENSE)
8
+
9
+ OPFython is a Python implementation of the Optimum-Path Forest family of
10
+ classifiers. It provides supervised, semi-supervised, unsupervised, and
11
+ KNN-supervised models backed by NumPy and Numba.
12
+
13
+ This implementation follows [LibOPF](https://github.com/jppbsi/LibOPF).
14
+ Please cite the original LibOPF authors as well as OPFython when using it in
15
+ research.
16
+
17
+ OPFython requires Python 3.11 or newer.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ uv add opfython
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ```python
28
+ import numpy as np
29
+
30
+ from opfython.models import SupervisedOPF
31
+
32
+ X_train = np.asarray([[0.0, 0.0], [0.1, 0.2], [1.0, 1.0], [1.1, 0.9]])
33
+ Y_train = np.asarray([0, 0, 1, 1])
34
+ X_test = np.asarray([[0.05, 0.1], [1.05, 1.0]])
35
+
36
+ classifier = SupervisedOPF()
37
+ classifier.fit(X_train, Y_train)
38
+ predictions = classifier.predict(X_test)
39
+ ```
40
+
41
+ Labels must be zero-based and sequential. Pre-computed distance matrices can
42
+ be supplied through each classifier's `pre_computed_distance` constructor
43
+ argument.
44
+
45
+ ## Classifiers
46
+
47
+ | Class | Purpose |
48
+ |---|---|
49
+ | `SupervisedOPF` | Complete-graph supervised classification |
50
+ | `KNNSupervisedOPF` | Supervised classification with learned KNN adjacency |
51
+ | `SemiSupervisedOPF` | Learning from labeled and unlabeled samples |
52
+ | `UnsupervisedOPF` | Density-based clustering and label propagation |
53
+
54
+ The package also includes 47 distance metrics, random generators, OPF
55
+ evaluation measures, dataset loaders and splitters, package logging and
56
+ exception helpers, and converters for LibOPF binary datasets.
57
+
58
+ See [the documentation](https://opfython.readthedocs.io/) and the
59
+ [`examples/applications`](examples/applications) directory for complete
60
+ workflows.
61
+
62
+ ## Development
63
+
64
+ ```bash
65
+ uv sync --all-groups
66
+ uv run pytest
67
+ uv run pre-commit run --all-files
68
+ uv run --group docs sphinx-build -W -b html docs docs/_build/html
69
+ uv build --no-sources
70
+ ```
71
+
72
+ ## Citation
73
+
74
+ ```bibtex
75
+ @article{rosa2021simpa,
76
+ title = {OPFython: A Python implementation for Optimum-Path Forest},
77
+ author = {Gustavo H. {de Rosa} and Joao P. Papa},
78
+ journal = {Software Impacts},
79
+ pages = {100113},
80
+ year = {2021},
81
+ issn = {2665-9638},
82
+ doi = {https://doi.org/10.1016/j.simpa.2021.100113}
83
+ }
84
+ ```
85
+
86
+ ```bibtex
87
+ @misc{rosa2021speedup,
88
+ title = {Speeding Up OPFython with Numba},
89
+ author = {Gustavo H. de Rosa and Joao Paulo Papa},
90
+ year = {2021},
91
+ eprint = {2106.11828},
92
+ archivePrefix = {arXiv},
93
+ primaryClass = {cs.LG}
94
+ }
95
+ ```
96
+
97
+ OPFython is licensed under the Apache License 2.0.
@@ -2,5 +2,4 @@
2
2
  of several modules and sub-modules.
3
3
  """
4
4
 
5
-
6
- __version__ = "1.0.12"
5
+ __version__ = "2.0.0"
@@ -1,7 +1,6 @@
1
- """A core package for all common opfython modules.
2
- """
1
+ """A core package for all common opfython modules."""
3
2
 
4
3
  from opfython.core.heap import Heap
5
4
  from opfython.core.node import Node
6
- from opfython.core.subgraph import Subgraph
7
5
  from opfython.core.opf import OPF
6
+ from opfython.core.subgraph import Subgraph
@@ -0,0 +1,208 @@
1
+ """Priority queue used by OPF algorithms."""
2
+
3
+ from typing import List
4
+
5
+ import opfython.utils.constants as c
6
+ import opfython.utils.exception as e
7
+
8
+
9
+ class Heap:
10
+ """Fixed-size mutable min or max heap."""
11
+
12
+ def __init__(self, size: int = 1, policy: str = "min") -> None:
13
+ self.size = size
14
+ self.policy = policy
15
+ self.cost = [c.FLOAT_MAX] * size
16
+ self.color = [c.WHITE] * size
17
+ self.p = [-1] * size
18
+ self.pos = [-1] * size
19
+ self.last = -1
20
+
21
+ @property
22
+ def size(self) -> int:
23
+ """Maximum heap size."""
24
+
25
+ return self._size
26
+
27
+ @size.setter
28
+ def size(self, size: int) -> None:
29
+ if not isinstance(size, int):
30
+ raise e.TypeError("`size` should be an integer")
31
+ if size < 1:
32
+ raise e.ValueError("`size` should be > 0")
33
+ self._size = size
34
+
35
+ @property
36
+ def policy(self) -> str:
37
+ """Heap ordering policy."""
38
+
39
+ return self._policy
40
+
41
+ @policy.setter
42
+ def policy(self, policy: str) -> None:
43
+ if policy not in ["min", "max"]:
44
+ raise e.ValueError("`policy` should be `min` or `max`")
45
+ self._policy = policy
46
+
47
+ @property
48
+ def cost(self) -> List[float]:
49
+ """Node costs."""
50
+
51
+ return self._cost
52
+
53
+ @cost.setter
54
+ def cost(self, cost: List[float]) -> None:
55
+ if not isinstance(cost, list):
56
+ raise e.TypeError("`cost` should be a list")
57
+ self._cost = cost
58
+
59
+ @property
60
+ def color(self) -> List[int]:
61
+ """Node colors."""
62
+
63
+ return self._color
64
+
65
+ @color.setter
66
+ def color(self, color: List[int]) -> None:
67
+ if not isinstance(color, list):
68
+ raise e.TypeError("`color` should be a list")
69
+ self._color = color
70
+
71
+ @property
72
+ def p(self) -> List[int]:
73
+ """Heap-position to node mapping."""
74
+
75
+ return self._p
76
+
77
+ @p.setter
78
+ def p(self, p: List[int]) -> None:
79
+ if not isinstance(p, list):
80
+ raise e.TypeError("`p` should be a list")
81
+ self._p = p
82
+
83
+ @property
84
+ def pos(self) -> List[int]:
85
+ """Node to heap-position mapping."""
86
+
87
+ return self._pos
88
+
89
+ @pos.setter
90
+ def pos(self, pos: List[int]) -> None:
91
+ if not isinstance(pos, list):
92
+ raise e.TypeError("`pos` should be a list")
93
+ self._pos = pos
94
+
95
+ @property
96
+ def last(self) -> int:
97
+ """Last occupied heap position."""
98
+
99
+ return self._last
100
+
101
+ @last.setter
102
+ def last(self, last: int) -> None:
103
+ if not isinstance(last, int):
104
+ raise e.TypeError("`last` should be an integer")
105
+ if last < -1:
106
+ raise e.ValueError("`last` should be > -1")
107
+ self._last = last
108
+
109
+ def is_full(self) -> bool:
110
+ """Return whether every heap slot is occupied."""
111
+
112
+ return self.last == self.size - 1
113
+
114
+ def is_empty(self) -> bool:
115
+ """Return whether the heap has no nodes."""
116
+
117
+ return self.last == -1
118
+
119
+ def dad(self, i: int) -> int:
120
+ """Return a node's parent position."""
121
+
122
+ return int((i - 1) / 2)
123
+
124
+ def left_son(self, i: int) -> int:
125
+ """Return a node's left-child position."""
126
+
127
+ return 2 * i + 1
128
+
129
+ def right_son(self, i: int) -> int:
130
+ """Return a node's right-child position."""
131
+
132
+ return 2 * i + 2
133
+
134
+ def _precedes(self, left: int, right: int) -> bool:
135
+ if self.policy == "min":
136
+ return self.cost[left] < self.cost[right]
137
+ return self.cost[left] > self.cost[right]
138
+
139
+ def _swap(self, left: int, right: int) -> None:
140
+ self.p[left], self.p[right] = self.p[right], self.p[left]
141
+ self.pos[self.p[left]] = left
142
+ self.pos[self.p[right]] = right
143
+
144
+ def go_up(self, i: int) -> None:
145
+ """Move a node toward the heap root."""
146
+
147
+ parent = self.dad(i)
148
+ while i > 0 and self._precedes(self.p[i], self.p[parent]):
149
+ self._swap(i, parent)
150
+ i = parent
151
+ parent = self.dad(i)
152
+
153
+ def go_down(self, i: int) -> None:
154
+ """Move a node toward the heap leaves."""
155
+
156
+ left = self.left_son(i)
157
+ right = self.right_son(i)
158
+ target = i
159
+
160
+ if left <= self.last and self._precedes(self.p[left], self.p[target]):
161
+ target = left
162
+ if right <= self.last and self._precedes(self.p[right], self.p[target]):
163
+ target = right
164
+
165
+ if target != i:
166
+ self._swap(i, target)
167
+ self.go_down(target)
168
+
169
+ def insert(self, p: int) -> bool:
170
+ """Insert a node if capacity is available."""
171
+
172
+ if self.is_full():
173
+ return False
174
+
175
+ self.last += 1
176
+ self.p[self.last] = p
177
+ self.color[p] = c.GRAY
178
+ self.pos[p] = self.last
179
+ self.go_up(self.last)
180
+ return True
181
+
182
+ def remove(self) -> int:
183
+ """Remove and return the next node, or ``False`` when empty."""
184
+
185
+ if self.is_empty():
186
+ return False
187
+
188
+ node = self.p[0]
189
+ self.pos[node] = -1
190
+ self.color[node] = c.BLACK
191
+ self.p[0] = self.p[self.last]
192
+ self.pos[self.p[0]] = 0
193
+ self.p[self.last] = -1
194
+ self.last -= 1
195
+ self.go_down(0)
196
+ return node
197
+
198
+ def update(self, p: int, cost: float) -> None:
199
+ """Update a node's cost, inserting it when unseen."""
200
+
201
+ self.cost[p] = cost
202
+ if self.color[p] == c.BLACK:
203
+ pass
204
+
205
+ if self.color[p] == c.WHITE:
206
+ self.insert(p)
207
+ else:
208
+ self.go_up(self.pos[p])