pyFOCI 0.2.2__tar.gz → 0.3.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 (55) hide show
  1. {pyfoci-0.2.2 → pyfoci-0.3.0}/.github/workflows/deploy-gh-pages.yml +1 -1
  2. {pyfoci-0.2.2 → pyfoci-0.3.0}/.github/workflows/lint.yml +1 -1
  3. {pyfoci-0.2.2 → pyfoci-0.3.0}/.github/workflows/python-app.yml +1 -1
  4. {pyfoci-0.2.2 → pyfoci-0.3.0}/.github/workflows/release.yml +4 -4
  5. {pyfoci-0.2.2 → pyfoci-0.3.0}/CHANGELOG.md +17 -2
  6. {pyfoci-0.2.2/pyFOCI.egg-info → pyfoci-0.3.0}/PKG-INFO +1 -1
  7. {pyfoci-0.2.2 → pyfoci-0.3.0}/dev-readme.txt +12 -4
  8. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/_foci.py +91 -21
  9. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/_version.py +3 -3
  10. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/tests/test_foci.py +35 -0
  11. {pyfoci-0.2.2 → pyfoci-0.3.0/pyFOCI.egg-info}/PKG-INFO +1 -1
  12. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI.egg-info/SOURCES.txt +2 -0
  13. pyfoci-0.3.0/pyFOCI.egg-info/scm_file_list.json +49 -0
  14. pyfoci-0.3.0/pyFOCI.egg-info/scm_version.json +8 -0
  15. {pyfoci-0.2.2 → pyfoci-0.3.0}/.codecov.yml +0 -0
  16. {pyfoci-0.2.2 → pyfoci-0.3.0}/.coveragerc +0 -0
  17. {pyfoci-0.2.2 → pyfoci-0.3.0}/.github/dependabot.yml +0 -0
  18. {pyfoci-0.2.2 → pyfoci-0.3.0}/.gitignore +0 -0
  19. {pyfoci-0.2.2 → pyfoci-0.3.0}/.pre-commit-config.yaml +0 -0
  20. {pyfoci-0.2.2 → pyfoci-0.3.0}/CITATION.cff +0 -0
  21. {pyfoci-0.2.2 → pyfoci-0.3.0}/LICENSE +0 -0
  22. {pyfoci-0.2.2 → pyfoci-0.3.0}/README.md +0 -0
  23. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/Makefile +0 -0
  24. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/css/project-template.css +0 -0
  25. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/img/index_api.svg +0 -0
  26. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/img/index_examples.svg +0 -0
  27. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/img/index_getting_started.svg +0 -0
  28. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/img/index_user_guide.svg +0 -0
  29. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/img/logo.png +0 -0
  30. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_static/js/copybutton.js +0 -0
  31. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_templates/class.rst +0 -0
  32. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_templates/function.rst +0 -0
  33. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_templates/numpydoc_docstring.py +0 -0
  34. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/_templates/sidebar-search-bs.html +0 -0
  35. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/api.rst +0 -0
  36. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/conf.py +0 -0
  37. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/index.rst +0 -0
  38. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/make.bat +0 -0
  39. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/quick_start.rst +0 -0
  40. {pyfoci-0.2.2 → pyfoci-0.3.0}/doc/user_guide.rst +0 -0
  41. {pyfoci-0.2.2 → pyfoci-0.3.0}/examples/README.txt +0 -0
  42. {pyfoci-0.2.2 → pyfoci-0.3.0}/examples/plot_FOCISelector.py +0 -0
  43. {pyfoci-0.2.2 → pyfoci-0.3.0}/pixi.lock +0 -0
  44. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/__init__.py +0 -0
  45. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/tests/__init__.py +0 -0
  46. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/tests/test_common.py +0 -0
  47. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/utils/__init__.py +0 -0
  48. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/utils/discovery.py +0 -0
  49. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/utils/tests/__init__.py +0 -0
  50. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI/utils/tests/test_discovery.py +0 -0
  51. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI.egg-info/dependency_links.txt +0 -0
  52. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI.egg-info/requires.txt +0 -0
  53. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyFOCI.egg-info/top_level.txt +0 -0
  54. {pyfoci-0.2.2 → pyfoci-0.3.0}/pyproject.toml +0 -0
  55. {pyfoci-0.2.2 → pyfoci-0.3.0}/setup.cfg +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  pages: write
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v6
19
+ - uses: actions/checkout@v7
20
20
  with:
21
21
  fetch-depth: 0
22
22
  - uses: prefix-dev/setup-pixi@v0.9.6
@@ -12,7 +12,7 @@ jobs:
12
12
  build:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v6
15
+ - uses: actions/checkout@v7
16
16
  - uses: prefix-dev/setup-pixi@v0.9.6
17
17
  with:
18
18
  pixi-version: v0.68.1
@@ -15,7 +15,7 @@ jobs:
15
15
  os: [windows-latest, ubuntu-latest, macos-latest]
16
16
  runs-on: ${{ matrix.os }}
17
17
  steps:
18
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@v7
19
19
  - uses: prefix-dev/setup-pixi@v0.9.6
20
20
  with:
21
21
  pixi-version: v0.68.1
@@ -12,7 +12,7 @@ jobs:
12
12
 
13
13
  steps:
14
14
  - name: Checkout
15
- uses: actions/checkout@v6
15
+ uses: actions/checkout@v7
16
16
 
17
17
  - name: Install Pixi
18
18
  uses: prefix-dev/setup-pixi@v0.9.6
@@ -85,7 +85,7 @@ jobs:
85
85
  if: startsWith(github.ref, 'refs/tags/')
86
86
  steps:
87
87
  - name: Checkout
88
- uses: actions/checkout@v6 # needed so we can read CHANGELOG.md
88
+ uses: actions/checkout@v7 # needed so we can read CHANGELOG.md
89
89
 
90
90
  - name: Download artifacts
91
91
  uses: actions/download-artifact@v8
@@ -122,14 +122,14 @@ jobs:
122
122
 
123
123
  - name: Create release (from CHANGELOG)
124
124
  if: steps.changelog.outputs.body_path != ''
125
- uses: softprops/action-gh-release@v2
125
+ uses: softprops/action-gh-release@v3
126
126
  with:
127
127
  body_path: release_body.md
128
128
  files: dist/*
129
129
 
130
130
  - name: Create release (auto-generated notes fallback)
131
131
  if: steps.changelog.outputs.use_auto_notes == 'true'
132
- uses: softprops/action-gh-release@v2
132
+ uses: softprops/action-gh-release@v3
133
133
  with:
134
134
  generate_release_notes: true
135
135
  files: dist/*
@@ -5,16 +5,31 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.0] - 2026-06-26
9
+
10
+ ### Added
11
+ - Introduced an alternative nearest neighbors selection algorithm, similar to the R reference implementation.
12
+ It is exposed with the new keyword-only `FOCISelector` parameter `nn_strategy="grouping"`.
13
+ The original algorithm remains available with `nn_strategy="radius"`.
14
+
15
+ ## Changed
16
+ - Made `nn_strategy="grouping"` the new default, because it is faster.
17
+
18
+ ## [0.2.3] - 2026-06-23
19
+
20
+ ### Changed
21
+ - Updated Action for creating GitHub releases.
22
+
8
23
  ## [0.2.2] - 2026-06-17
9
24
 
10
25
  ### Added
11
- - There is now a changelog, which is also used for the Github releases.
26
+ - There is now a changelog, which is also used for the GitHub releases.
12
27
 
13
28
  ## [0.2.1] - 2026-06-17
14
29
 
15
30
  ### Changed
16
31
  - Input features are now N(0,1)-normalized by default.
17
- This can be switched off with the new parameter `standardize` by setting it to `Ǹone`.
32
+ This can be switched off with the new parameter `standardize` by setting it to `None`.
18
33
 
19
34
  ## [0.2.0] - 2026-06-16
20
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyFOCI
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A template for scikit-learn compatible packages.
5
5
  Author-email: Robert Pollak <robert.pollak@jku.at>
6
6
  License-Expression: BSD-3-Clause
@@ -9,6 +9,7 @@ pixi run -e test test
9
9
  # Also examine the resulting coverage.xml.
10
10
 
11
11
  pixi run -e doc build-doc
12
+ # Also examine the resulting example plot.
12
13
 
13
14
  Commit to a dev branch.
14
15
 
@@ -17,7 +18,7 @@ pixi run build
17
18
 
18
19
  2. CI:
19
20
 
20
- Push dev branch to Github.
21
+ Push dev branch to GitHub.
21
22
 
22
23
  Create pull request to trigger CI tests.
23
24
 
@@ -26,13 +27,20 @@ Create pull request to trigger CI tests.
26
27
  printf 'Fast-forwarding main to dev: '; git merge-base --is-ancestor main dev && git branch -f main dev && echo OK || echo ERROR
27
28
 
28
29
  git push origin main:main
29
- # By this, The Github repo setting "Automatically delete head branches"
30
+ # By this, The GitHub repo setting "Automatically delete head branches"
30
31
  # deletes the dev branch in the pull request, for easier creation of the next pull request from dev.
31
32
 
33
+ Update the [Unreleased] changelog section
34
+
32
35
 
33
36
  Releasing:
34
37
  ==========
35
38
 
36
- Following [Semantic Versioning](https://semver.org/), add a tag like "v0.1.2" to a commit and push it.
37
- A Github action will then publish this Release to PyPI.
39
+ Select a version like `0.1.2`, following [Semantic Versioning](https://semver.org/).
40
+
41
+ Update the changelog, commit with "Release 0.1.2".
42
+
43
+ Tag the commit as "v0.1.2" and push it, including the tag.
44
+
45
+ GitHub actions will then update the online docs and publish this release on GitHub and PyPI.
38
46
 
@@ -51,7 +51,78 @@ def _rank_max(y):
51
51
  return ranks
52
52
 
53
53
 
54
- def _Tn(X_sub, y_rank, random_state):
54
+ def _nn_radius_based(X_sub, random_state):
55
+ """Radius-based NN selection with random tie breaking."""
56
+ X_sub = np.asarray(X_sub)
57
+ n = X_sub.shape[0]
58
+
59
+ # Fit NN on X_sub
60
+ nbrs = sklearn.neighbors.NearestNeighbors(n_neighbors=2, algorithm="ball_tree")
61
+ nbrs.fit(X_sub)
62
+
63
+ # Get min distances
64
+ distances, _ = nbrs.kneighbors(n_neighbors=1)
65
+ min_distance = distances[:, 0]
66
+
67
+ eps = 1e-13 # to get all neighbors of min distance
68
+ # For each i, collect all min-dist neighbors, remove self, pick one at random
69
+ nbr_i = np.empty(n, dtype=int)
70
+ for i in range(n):
71
+ # Query neighbors in the tight radius around the nearest neighbor distance
72
+ neighbors = nbrs.radius_neighbors(
73
+ X_sub[i, :].reshape(1, -1), min_distance[i] + eps, return_distance=False
74
+ )[0]
75
+ # Remove self index if present
76
+ neighbors = neighbors[neighbors != i]
77
+ nbr_i[i] = random_state.choice(neighbors)
78
+
79
+ return nbr_i
80
+
81
+
82
+ def _nn_grouping_based(X_sub, random_state):
83
+ """Grouping-based NN selection with random tie-breaking.
84
+
85
+ Precondition: n_samples >= 2.
86
+ """
87
+ X_sub = np.asarray(X_sub)
88
+ n = X_sub.shape[0]
89
+
90
+ # 1) Group exactly identical rows
91
+ Xu, inv = np.unique(X_sub, axis=0, return_inverse=True) # Xu: (m, p)
92
+ m = Xu.shape[0]
93
+
94
+ groups = [[] for _ in range(m)]
95
+ for i, g in enumerate(inv):
96
+ groups[g].append(i)
97
+ groups = [np.asarray(g, dtype=int) for g in groups]
98
+
99
+ nbr_i = np.empty(n, dtype=int)
100
+
101
+ for i in range(n):
102
+ gi = inv[i]
103
+ members = groups[gi]
104
+
105
+ # repeated data: choose another member of same group at random
106
+ if members.size >= 2:
107
+ choices = members[members != i]
108
+ nbr_i[i] = int(random_state.choice(choices))
109
+ continue
110
+
111
+ # per-query brute-force distances to all unique rows
112
+ diff = Xu - Xu[gi] # (m, p)
113
+ d2 = (diff * diff).sum(axis=1) # rowwise dot products
114
+ d2[gi] = np.inf # exclude self
115
+
116
+ # Choose among all original indices whose (unique) row is at minimal distance
117
+ dmin = d2.min()
118
+ tied = np.flatnonzero(d2 == dmin) # tied unique rows
119
+ candidates = np.concatenate([groups[u] for u in tied])
120
+ nbr_i[i] = int(random_state.choice(candidates))
121
+
122
+ return nbr_i
123
+
124
+
125
+ def _Tn(X_sub, y_rank, random_state, *, nn_strategy="grouping"):
55
126
  """Compute :math:`T_n` following Fuchs (2024).
56
127
 
57
128
  The implementation uses the expression for :math:`T_n` given in
@@ -70,6 +141,8 @@ def _Tn(X_sub, y_rank, random_state):
70
141
  :func:`_rank_max`.
71
142
  random_state : numpy.random.RandomState
72
143
  Random number generator used to break nearest-neighbor ties.
144
+ nn_strategy : {"grouping", "radius"}, default="grouping"
145
+ Strategy used to select the nearest neighbor indices.
73
146
 
74
147
  Returns
75
148
  -------
@@ -78,25 +151,12 @@ def _Tn(X_sub, y_rank, random_state):
78
151
  """
79
152
  X_sub = np.asarray(X_sub)
80
153
  n = X_sub.shape[0]
81
- # Fit NN on X_sub
82
- nbrs = sklearn.neighbors.NearestNeighbors(n_neighbors=2, algorithm="ball_tree")
83
- nbrs.fit(X_sub)
84
-
85
- # Get min distances
86
- distances, _ = nbrs.kneighbors(n_neighbors=1)
87
- min_distance = distances[:, 0]
88
154
 
89
- eps = 1e-13 # to get all neighbors of min distance
90
- # For each i, collect all min-dist neighbors, remove self, pick one at random
91
- nbr_i = np.empty(n, dtype=int)
92
- for i in range(n):
93
- # Query neighbors in the tight radius around the nearest neighbor distance
94
- neighbors = nbrs.radius_neighbors(
95
- X_sub[i, :].reshape(1, -1), min_distance[i] + eps, return_distance=False
96
- )[0]
97
- # Remove self index if present
98
- neighbors = neighbors[neighbors != i]
99
- nbr_i[i] = random_state.choice(neighbors)
155
+ if nn_strategy == "grouping":
156
+ nbr_i = _nn_grouping_based(X_sub, random_state)
157
+ else:
158
+ assert nn_strategy == "radius"
159
+ nbr_i = _nn_radius_based(X_sub, random_state)
100
160
 
101
161
  # Apply the formula (indices are 0-based; y_rank is 1-based)
102
162
  term1 = np.sum(np.abs(y_rank - y_rank[nbr_i]))
@@ -146,6 +206,9 @@ class FOCISelector(SelectorMixin, BaseEstimator):
146
206
  variance before computing nearest neighbors. If None, X is used as-is.
147
207
  Columns with zero variance are left unchanged.
148
208
 
209
+ nn_strategy : {"grouping", "radius"}, default="grouping"
210
+ Strategy used to select nearest neighbors for computing :math:`T_n`.
211
+
149
212
  random_state : int, RandomState instance or None, default=None
150
213
  Controls the random tie-breaking among nearest neighbors. Pass an int
151
214
  for reproducible results across multiple calls. If None, the global
@@ -180,15 +243,22 @@ class FOCISelector(SelectorMixin, BaseEstimator):
180
243
  "max_features": [None, Interval(Integral, 1, None, closed="left")],
181
244
  "min_delta": [None, Interval(Real, None, None, closed="neither")],
182
245
  "standardize": [None, StrOptions({"normalize"})],
246
+ "nn_strategy": [StrOptions({"grouping", "radius"})],
183
247
  "random_state": ["random_state"],
184
248
  }
185
249
 
186
250
  def __init__(
187
- self, max_features=None, min_delta=0, standardize="normalize", random_state=None
251
+ self,
252
+ max_features=None,
253
+ min_delta=0,
254
+ standardize="normalize",
255
+ nn_strategy="grouping",
256
+ random_state=None,
188
257
  ):
189
258
  self.max_features = max_features
190
259
  self.min_delta = min_delta
191
260
  self.standardize = standardize
261
+ self.nn_strategy = nn_strategy
192
262
  self.random_state = random_state
193
263
 
194
264
  @_fit_context(prefer_skip_nested_validation=True)
@@ -249,7 +319,7 @@ class FOCISelector(SelectorMixin, BaseEstimator):
249
319
  for j in remaining:
250
320
  sel_candidate = selected + [j]
251
321
  X_sub = X[:, sel_candidate]
252
- Tn_val = _Tn(X_sub, y_rank, random_state)
322
+ Tn_val = _Tn(X_sub, y_rank, random_state, nn_strategy=self.nn_strategy)
253
323
  if Tn_val > best_Tn:
254
324
  best_Tn = Tn_val
255
325
  best_j = j
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.3.0'
22
+ __version_tuple__ = version_tuple = (0, 3, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g62b06f720'
24
+ __commit_id__ = commit_id = 'g3d446d680'
@@ -167,3 +167,38 @@ def test_random_state_int_reproducible():
167
167
  selector_1.Tn_path_,
168
168
  selector_2.Tn_path_,
169
169
  )
170
+
171
+
172
+ def test_nn_strategy_grouping_and_radius_are_accepted_and_reproducible():
173
+ X_df, y = make_demo_data(n=200, p=10, seed=0)
174
+
175
+ for strategy in ("grouping", "radius"):
176
+ selector_1 = FOCISelector(
177
+ random_state=0, nn_strategy=strategy, min_delta=None, max_features=3
178
+ ).fit(X_df, y)
179
+ selector_2 = FOCISelector(
180
+ random_state=0, nn_strategy=strategy, min_delta=None, max_features=3
181
+ ).fit(X_df, y)
182
+
183
+ # Non-trivial selection (avoid early stopping selecting none)
184
+ assert selector_1.support_mask_.sum() > 2
185
+
186
+ np.testing.assert_array_equal(
187
+ selector_1.selected_indices_,
188
+ selector_2.selected_indices_,
189
+ )
190
+ assert_allclose(
191
+ selector_1.Tn_path_,
192
+ selector_2.Tn_path_,
193
+ )
194
+
195
+
196
+ def test_nn_strategy_invalid_raises():
197
+ random_state = np.random.RandomState(0)
198
+ X = random_state.normal(size=(20, 3))
199
+ y = random_state.normal(size=20)
200
+
201
+ sel = FOCISelector(nn_strategy="invalid")
202
+ expected = "The 'nn_strategy' parameter of FOCISelector must be"
203
+ with pytest.raises(InvalidParameterError, match=re.escape(expected)):
204
+ sel.fit(X, y)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyFOCI
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A template for scikit-learn compatible packages.
5
5
  Author-email: Robert Pollak <robert.pollak@jku.at>
6
6
  License-Expression: BSD-3-Clause
@@ -41,6 +41,8 @@ pyFOCI.egg-info/PKG-INFO
41
41
  pyFOCI.egg-info/SOURCES.txt
42
42
  pyFOCI.egg-info/dependency_links.txt
43
43
  pyFOCI.egg-info/requires.txt
44
+ pyFOCI.egg-info/scm_file_list.json
45
+ pyFOCI.egg-info/scm_version.json
44
46
  pyFOCI.egg-info/top_level.txt
45
47
  pyFOCI/tests/__init__.py
46
48
  pyFOCI/tests/test_common.py
@@ -0,0 +1,49 @@
1
+ {
2
+ "files": [
3
+ ".coveragerc",
4
+ "CITATION.cff",
5
+ ".pre-commit-config.yaml",
6
+ "README.md",
7
+ "LICENSE",
8
+ ".codecov.yml",
9
+ "pyproject.toml",
10
+ "CHANGELOG.md",
11
+ "dev-readme.txt",
12
+ "pixi.lock",
13
+ ".gitignore",
14
+ "doc/Makefile",
15
+ "doc/quick_start.rst",
16
+ "doc/index.rst",
17
+ "doc/api.rst",
18
+ "doc/make.bat",
19
+ "doc/user_guide.rst",
20
+ "doc/conf.py",
21
+ "doc/_static/img/logo.png",
22
+ "doc/_static/img/index_getting_started.svg",
23
+ "doc/_static/img/index_examples.svg",
24
+ "doc/_static/img/index_user_guide.svg",
25
+ "doc/_static/img/index_api.svg",
26
+ "doc/_static/css/project-template.css",
27
+ "doc/_static/js/copybutton.js",
28
+ "doc/_templates/numpydoc_docstring.py",
29
+ "doc/_templates/function.rst",
30
+ "doc/_templates/sidebar-search-bs.html",
31
+ "doc/_templates/class.rst",
32
+ "pyFOCI/__init__.py",
33
+ "pyFOCI/_foci.py",
34
+ "pyFOCI/utils/__init__.py",
35
+ "pyFOCI/utils/discovery.py",
36
+ "pyFOCI/utils/tests/__init__.py",
37
+ "pyFOCI/utils/tests/test_discovery.py",
38
+ "pyFOCI/tests/__init__.py",
39
+ "pyFOCI/tests/test_foci.py",
40
+ "pyFOCI/tests/test_common.py",
41
+ "examples/plot_FOCISelector.py",
42
+ "examples/README.txt",
43
+ ".github/dependabot.yml",
44
+ ".github/workflows/release.yml",
45
+ ".github/workflows/python-app.yml",
46
+ ".github/workflows/lint.yml",
47
+ ".github/workflows/deploy-gh-pages.yml"
48
+ ]
49
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.3.0",
3
+ "distance": 0,
4
+ "node": "g3d446d6802cb189aeb6ca6c2e5623b56fb506933",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-06-26"
8
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes