Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a3a5d6f
refactor: remove tqdm dependency
Joao-Paulo-Silva Jun 13, 2026
1d0c632
feat: add a class to replace the old progress bar
Joao-Paulo-Silva Jun 13, 2026
f89a304
feat: add initial progress bar
Joao-Paulo-Silva Jun 13, 2026
e26af1c
fix: use stdout to prevent duplicate output prints
Joao-Paulo-Silva Jun 13, 2026
a6eb45b
fix: updates the character to use special slash.
Joao-Paulo-Silva Jun 13, 2026
94bf22e
docs: improve documentation in docstrings
Joao-Paulo-Silva Jun 14, 2026
131f34e
refactor: migrate tqdm usage to the custom class
Joao-Paulo-Silva Jun 14, 2026
5a57bd1
refactor: migrate tqdm usage to the custom class
Joao-Paulo-Silva Jun 14, 2026
3acc069
fix: fix lint
Joao-Paulo-Silva Jun 14, 2026
fcb71f5
fix: prevent division by zero when total is zero.
Joao-Paulo-Silva Jun 14, 2026
45a17aa
fix: resolve mypy type checking error.
Joao-Paulo-Silva Jun 14, 2026
7e55a71
feat: add missing types.
Joao-Paulo-Silva Jun 14, 2026
5a3f0c8
feat: throw errors to improve ProgressBar class validation
Joao-Paulo-Silva Jun 14, 2026
ff15fe1
refactor: improves the readability of the class
Joao-Paulo-Silva Jun 20, 2026
a8a126e
docs: adiciona a documentação inicial da nova classe
Joao-Paulo-Silva Jun 20, 2026
497aee4
docs: adiciona documentação dos métodos
Joao-Paulo-Silva Jun 20, 2026
f7f1e9b
docs: atualiza os cabeçalhos das tabelas.
Joao-Paulo-Silva Jun 20, 2026
b5eddb2
docs: adiciona documentação em inglês
Joao-Paulo-Silva Jun 20, 2026
d9af8c9
config: remove tqdm e atualiza a versão
Joao-Paulo-Silva Jun 20, 2026
eea0a32
chore: reexecuta notebooks para exibir a nova barra de progresso
Joao-Paulo-Silva Jun 20, 2026
820e5d8
fix: markdown
Joao-Paulo-Silva Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ The module requires installation of [python 3.10](https://www.python.org/downloa
|:--------:|:---------:|
| numpy | ≥ 1.22.4 |
| scipy | ≥ 1.8.1 |
| tqdm | ≥ 4.64.1 |
| numba | ≥ 0.59.0 |

</div>
Expand Down
2 changes: 1 addition & 1 deletion aisp/base/core/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@ def get_params(self, deep: bool = True) -> dict: # pylint: disable=W0613
"""
return {
key: getattr(self, key)
for key, _ in signature(self.__init__).parameters.items()
for key, _ in signature(type(self).__init__).parameters.items()
if key != "self" and not key.startswith("_") and hasattr(self, key)
}
17 changes: 8 additions & 9 deletions aisp/csa/_ai_recognition_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import numpy as np
import numpy.typing as npt
from scipy.spatial.distance import pdist
from tqdm import tqdm

from ._artificial_recognition_ball import _ARB
from ..base import BaseClassifier
from ..base.immune.cell import BCell
from ..exceptions import ModelNotFittedError
from ..utils.display import ProgressBar
from ..utils.distance import hamming, compute_metric_distance, get_metric_code
from ..utils.multiclass import predict_knn_affinity
from ..utils.random import set_seed_numba
Expand Down Expand Up @@ -200,15 +200,14 @@ def fit(

self.classes = np.unique(y)
sample_index = self._slice_index_list_by_class(y)
progress = tqdm(
total=len(y),
postfix="\n",
disable=not verbose,
bar_format="{desc} ┇{bar}┇ {n}/{total} memory cells for each aᵢ",
progress = ProgressBar(
len(y),
"memory cells for each aᵢ",
verbose=verbose,
)
pool_cells_classes = {}
for _class_ in self.classes:
progress.set_description_str(
progress.set_description(
f"Generating the memory cells for the {_class_} class:"
)

Expand Down Expand Up @@ -238,9 +237,9 @@ def fit(

progress.set_description(
f"\033[92m✔ Set of memory cells for classes ({', '.join(map(str, self.classes))}) "
f"successfully generated\033[0m"
f"successfully generated\033[0m",
)
progress.close()
progress.finish()
self._cells_memory = pool_cells_classes
self._all_class_cell_vectors = [
(class_name, cell.vector)
Expand Down
13 changes: 6 additions & 7 deletions aisp/ina/_ai_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import numpy.typing as npt
from scipy.sparse.csgraph import minimum_spanning_tree, connected_components
from scipy.spatial.distance import squareform, pdist, cdist
from tqdm import tqdm

from ..base import BaseClusterer
from ..base.immune.cell import Cell
Expand All @@ -19,6 +18,7 @@
)
from ..base.immune.populations import generate_random_antibodies
from ..exceptions import ModelNotFittedError
from ..utils.display import ProgressBar
from ..utils.distance import hamming, compute_metric_distance, get_metric_code
from ..utils.multiclass import predict_knn_affinity
from ..utils.random import set_seed_numba
Expand Down Expand Up @@ -235,11 +235,10 @@ def fit(self, X: Union[npt.NDArray, list], verbose: bool = True) -> AiNet:
"""
X = self._prepare_features(X)

progress = tqdm(
total=self.max_iterations,
postfix="\n",
disable=not verbose,
bar_format="{desc} ┇{bar}┇ {n}/{total} total training iterations",
progress = ProgressBar(
self.max_iterations,
"total training iterations",
verbose=verbose
)

population_p = self._init_population_antibodies()
Expand Down Expand Up @@ -273,7 +272,7 @@ def fit(self, X: Union[npt.NDArray, list], verbose: bool = True) -> AiNet:

parts.append(f"Antibody population size: {len(self._population_antibodies)}")
progress.set_description(f"\033[92m{' | '.join(parts)}\033[0m")
progress.close()
progress.finish()

return self

Expand Down
15 changes: 7 additions & 8 deletions aisp/nsa/_binary_negative_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

import numpy as np
import numpy.typing as npt
from tqdm import tqdm

from ._base import check_detector_bnsa_validity, bnsa_class_prediction
from ..base import BaseClassifier
from ..exceptions import MaxDiscardsReachedError, ModelNotFittedError
from ..utils.display import ProgressBar
from ..utils.sanitizers import sanitize_seed, sanitize_param
from ..utils.validation import (
check_array_type,
Expand Down Expand Up @@ -174,17 +174,16 @@ def fit(
list_detectors_by_class: dict = {}
sample_index: dict = self._slice_index_list_by_class(y)

progress = tqdm(
total=int(self.N * (len(self.classes))),
bar_format="{desc} ┇{bar}┇ {n}/{total} detectors",
postfix="\n",
disable=not verbose,
progress = ProgressBar(
int(self.N * (len(self.classes))),
"detectors",
verbose=verbose,
)

for _class_ in self.classes:
valid_detectors_set: list = []
discard_count: int = 0
progress.set_description_str(
progress.set_description(
f"Generating the detectors for the {_class_} class:"
)
x_class = X[sample_index[_class_]]
Expand All @@ -208,7 +207,7 @@ def fit(
f"\033[92m✔ Non-self detectors for classes ({', '.join(map(str, self.classes))}) "
f"successfully generated\033[0m"
)
progress.close()
progress.finish()
self._detectors = list_detectors_by_class
self._detectors_stack = np.array(
[np.stack(self._detectors[class_name]) for class_name in self.classes]
Expand Down
15 changes: 7 additions & 8 deletions aisp/nsa/_negative_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

import numpy as np
import numpy.typing as npt
from tqdm import tqdm

from ._base import check_detector_rnsa_validity
from ..base import BaseClassifier
from ..base.immune.cell import Detector
from ..exceptions import MaxDiscardsReachedError, ModelNotFittedError
from ..utils.display import ProgressBar
from ..utils.distance import (
min_distance_to_class_vectors,
get_metric_code,
Expand Down Expand Up @@ -215,18 +215,17 @@ def fit(
list_detectors_by_class = {}
sample_index = self._slice_index_list_by_class(y)

progress = tqdm(
total=int(self.N * (len(self.classes))),
bar_format="{desc} ┇{bar}┇ {n}/{total} detectors",
postfix="\n",
disable=not verbose,
progress = ProgressBar(
int(self.N * (len(self.classes))),
"detectors",
verbose=verbose
)
for _class_ in self.classes:
valid_detectors_set: List[Detector] = []
discard_count = 0
x_class = X[sample_index[_class_]]

progress.set_description_str(
progress.set_description(
f"Generating the detectors for the {_class_} class:"
)

Expand Down Expand Up @@ -255,7 +254,7 @@ def fit(
f"\033[92m✔ Non-self detectors for classes ({', '.join(map(str, self.classes))}) "
f"successfully generated\033[0m"
)
progress.close()
progress.finish()

self._detectors = list_detectors_by_class
return self
Expand Down
118 changes: 114 additions & 4 deletions aisp/utils/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _border(
f"{nl}{left}{middle.join(line * w for w in self.headers.values())}{right}"
)

def get_header(self):
def get_header(self) -> str:
"""
Generate the table header, including the top border, column headings, and separator line.

Expand All @@ -100,7 +100,7 @@ def get_header(self):

return top + titles + sep

def get_row(self, values: Mapping[str, Union[str, int, float]]):
def get_row(self, values: Mapping[str, Union[str, int, float]]) -> str:
"""
Generate a formatted row for the table data.

Expand All @@ -113,7 +113,7 @@ def get_row(self, values: Mapping[str, Union[str, int, float]]):
Returns
-------
str
Formatted string of the table row.
Formatted string of the table row.
"""
border = "|" if self._ascii_only else "│"
row = (
Expand All @@ -126,7 +126,7 @@ def get_row(self, values: Mapping[str, Union[str, int, float]]):

return row

def get_bottom(self, new_line: bool = False):
def get_bottom(self, new_line: bool = False) -> str:
"""
Generate the table's bottom border.

Expand Down Expand Up @@ -192,3 +192,113 @@ def finish(self) -> None:

print(self.get_bottom())
print(f"Total time: {time.perf_counter() - self._start:.6f} seconds")


class ProgressBar:
"""Display a console progress bar to track an algorithm's progress.

This class builds a progress bar in the format `{description}{bar*slots}{suffix}` and,
when calling the `finish` method, print the final progress bar along with the total time
elapsed since the class was instance.

Parameters
----------
total : int
Total number of iterations.
suffix : str, default=''
Optional text displayed after the progress bar.
description : str, default=''
Optional text displayed before the progress bar.
slots : int, default=10
Number of character slots used to represent the bar.
verbose : bool, default=True
If False, prints nothing to the terminal.

Raises
------
ValueError
If the total or slots is less than or equal to zero.
"""

def __init__(
self,
total: int,
suffix: str = '',
description: str = '',
slots: int = 10,
verbose: bool = True
) -> None:
if slots <= 0:
raise ValueError("'slots' must be greater than zero.")
if total <= 0:
raise ValueError("'total' must be greater than zero.")

self.verbose: bool = verbose
self.suffix: str = suffix
self.slots: int = slots

self._fill_char, self._edge_char = (
('█', '┇') if _supports_box_drawing() else ('#', '|')
)

self._total: int = total
self._current: int = 0
self._description: str = description

if self.verbose:
self._start: float = time.perf_counter()

def _print_bar(self) -> None:
"""Print the current progress bar."""
sys.stdout.write(
f"\r\033[K{self._description} {self._get_bar()} {self.suffix}"
)
sys.stdout.flush()

def _get_bar(self) -> str:
"""Build string representation of the progress bar."""
slot_quant = self.slots
edge = self._edge_char
filled = int((self._current / self._total) * slot_quant)
progress_bar = self._fill_char * filled + ' ' * (slot_quant - filled)
return f'{edge}{progress_bar}{edge} {self._current} / {self._total}'

def set_description(self, description: str) -> None:
"""Update the description before the progress bar.

Parameters
----------
description : str
New description.
"""
self._description = description

def update(self, increment: int = 1) -> None:
"""Increment the progress bar.

Parameters
----------
increment : int, default=1
Number of completed iterations to add to the current progress.

Raises
------
ValueError
If the increment is negative.
"""
if increment < 0:
raise ValueError("'increment' must be non-negative.")

self._current = min(self._current + increment, self._total)
if not self.verbose:
return

self._print_bar()

def finish(self) -> None:
"""End the progress display and print the total elapsed time."""
if not self.verbose:
return

self._print_bar()
print(f"\nTotal time: {time.perf_counter() - self._start:.6f} seconds")
1 change: 1 addition & 0 deletions docs/en/api/utils/display/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Utility functions for displaying algorithm information.
|------------------------------------------|-----------------------------------------------------------------------------|
| [`TableFormatter`](./table-formatter.md) | Format tabular data into strings for display in the console. |
| [`ProgressTable`](./progress-table.md) | Display a formatted table in the console to track the algorithm's progress. |
| [`ProgressTable`](./progress-table.md) | Display a console progress bar to track an algorithm's progress. |
Loading
Loading