TuiML is open source and designed to be extensible. Whether you want to add an algorithm, fix a bug, or improve the documentation, this guide covers everything you need.
Implement a classifier, regressor, or clusterer using the base classes, add tests, and submit a PR.
Found an issue? Open a GitHub issue with a reproducible example and we will investigate.
Improve docstrings, tutorials, or examples. Every contribution counts toward a better library.
TuiML ships its own implementations, with C++ hot paths for performance-critical code, rather than wrapping other libraries. That is what makes it a library rather than a facade, and it is the single most important thing to understand before you write code.
Bridges to external libraries are welcome, but they live in dedicated packages,
tuiml.sklearn, tuiml.capymoa and tuiml.weka, as
optional extras, never inside the core namespaces.
algorithms/, preprocessing/ or features/. See External libraries for the full rules.algorithms/gradient_boosting/ is the one exception, and a historical one: the XGBoost, LightGBM and CatBoost wrappers predate this rule and keep their bare names for compatibility. Do not treat it as a precedent.
Anything that needs a library outside the core install — PyTorch, a boosting
backend, a foundation model — has to stay invisible until it is actually used.
The rule is not "wrap the import in a try": it is that the algorithm
catalog must be byte-identical on every install, so an agent
browsing the hub sees the same thing whatever the user happens to have.
import torch or import xgboost, anywhere. Classes are still defined, exported and registered without the library, so list_algorithms() is unchanged. An AST test fails the build if you add one.YourModel() must succeed without the library — it only records hyperparameters. Checking availability in __init__ breaks parameter grids, pickling and the generic contract suite.fit calls the shared helper, which raises an ImportError naming your class and the exact install command.Use the existing helpers rather than writing your own check: tuiml.utils.torch_backend.require_torch for PyTorch, tuiml.algorithms.gradient_boosting._backend.require_backend for a boosting library.
Also declare the extra in pyproject.toml, add it to the installer prompts in website/static/install.sh and install.ps1, and gate your tests with pytest.mark.skipif so they skip cleanly rather than erroring on a core-only install.
Clone the repository and set up your local environment.
A plain uv sync gives you the core library only. Tests that need an optional backend skip rather than fail, so a green run does not by itself mean you covered your change — install what your work touches:
If you changed something in algorithms/tabular_deep/ or algorithms/timeseries/deep/, check the skip count before you open the PR — uv run pytest -rs lists exactly what was skipped and why.
uv run to execute Python scripts and commands. Never use python3 directly.
Every algorithm inherits from the Algorithm base class. Pick the subclass
that matches what your algorithm predicts.
| Base class | Use it for | Import |
|---|---|---|
| Classifier | Predicting categorical labels | from tuiml.base.algorithms import Classifier |
| Regressor | Predicting continuous values | from tuiml.base.algorithms import Regressor |
| Clusterer | Unsupervised grouping | from tuiml.base.algorithms import Clusterer |
| Associator | Association rule mining | from tuiml.base.algorithms import Associator |
Every algorithm must implement these abstract methods:
A complete nearest-centroid classifier, start to finish.
@classifier registers your algorithm with the hub and carries its metadata, tags and version.super().__init__(), then set every attribute that will be read later.get_parameter_schema(), get_capabilities() and get_complexity() for hub integration.Five steps from an empty file to a pull request.
Place your algorithm in the matching subdirectory under tuiml/algorithms/:
Lowercase with underscores, descriptive, no abbreviations unless they are standard:
gradient_boosting_classifier.py, but svm.py.
Follow the structure above. Your checklist:
@classifier, @regressor and so onfit() and predict()self._is_fitted = True at the end of fit()Update both the category and the main __init__.py so your class is importable:
Add a test file under tests/, confirm the whole suite passes with
uv run pytest, then open a pull request.
The @classifier, @regressor, @clusterer and
@associator decorators handle registration with the
TuiML hub automatically.
| Parameter | Type | Description |
|---|---|---|
| tags | List[str] | Categorization tags, for example "trees", "ensemble" |
| version | str | Semantic version string, for example "1.0.0" |
| author | str | Optional author name or organization |
TuiML has bridges to scikit-learn and CapyMOA, and we welcome contributions that extend them. But where that code lives is not a matter of taste, it is the rule that keeps the core library coherent.
Never put wrapper files inside the core namespaces. A file named
sklearn_rf.py does not belong in tuiml/algorithms/trees/.
The core algorithms/, preprocessing/ and
features/ packages contain native TuiML
implementations only. Wrappers go in tuiml/sklearn/,
tuiml/capymoa/ or tuiml/weka/. Mixing them makes the
library look like a thin shell
over scikit-learn and leaves users unable to tell what is actually ours.
| What you are adding | Where it goes | Dependency |
|---|---|---|
| An algorithm implemented from scratch | tuiml/algorithms/<family>/ |
core, no new deps |
| A scikit-learn estimator exposed to TuiML | tuiml/sklearn/ |
tuiml[sklearn] extra |
| A CapyMOA streaming learner | tuiml/capymoa/ |
tuiml[capymoa] extra, needs Java |
| A Weka learner | tuiml/weka/ |
tuiml[weka] extra, needs Java |
| A pretrained foundation model (weights downloaded, not shipped) | tuiml/foundation/ |
tuiml[foundation] extra, hub key foundation.<Name> |
| A neural architecture TuiML trains itself | algorithms/tabular_deep/ or timeseries/deep/ |
tuiml[torch] extra, bare name |
| A bridge to some other library (JAX, cuML) | a new tuiml/<library>/ package |
its own extra |
Name the package after the library it wraps (sklearn,
capymoa, weka), not after the capability it provides
(streaming).
Capability names are reserved for native implementations, so
tuiml/algorithms/streaming/ stays free for streaming algorithms we write
ourselves.
Each bridge registers into the hub under a namespaced key,
sklearn.<ClassName>, capymoa.<ClassName> or
weka.<ClassName>. That is
what lets a scikit-learn RandomForestClassifier and a native
TuiML RandomForestClassifier coexist, and it lets users
choose between them explicitly:
A wrapper declares its constructor parameters and builds the backing estimator lazily.
_build_estimator() is the only place the external library is ever imported,
that is what keeps the dependency optional:
You never write the fit / predict bridge yourself, the mixin
does it, and it raises a clear ImportError pointing at
pip install 'tuiml[sklearn]' if the library is missing. CapyMOA works the
same way via tuiml/capymoa/_base.py, adapting its instance-at-a-time API to
TuiML's batch interface.
tuiml/sklearn/, tuiml/capymoa/ or tuiml/weka/_build_estimator()pyproject.tomlsklearn_*.py files to algorithms/, preprocessing/ or features/evaluation.splitting and evaluation.metricsTests are what let the next contributor change code without breaking yours. TuiML uses pytest.
TuiML uses NumPy/SciPy docstring style. The gold
standard reference is algorithms/anomaly/isolation_forest.py.
Args: or Parameters:), the doc generator will not parse it. Section headers must use dash underlines.In this order, for every class under algorithms/:
For datasets/ and features/, keep it short: Summary,
Parameters and Returns only. Every Examples block must open with the import
that defines the names it uses, a reader copies the block straight out of the rendered
docs and runs it.
Use RST math, it renders through KaTeX. Inline: :math:`x^2`. Display block:
.. math::
Python reads \t \a \b \f \n \r \v as escape sequences inside docstrings, so
LaTeX commands starting with those letters get mangled. Double the backslash:
\\text \\alpha \\beta \\frac \\begin \\nabla \\right \\varepsilon\sigma \sum \sqrt \lambda \log \pi \mu \inftyuv run scripts/generate_docs.py after docstring changes to regenerate the HTML:class:`~tuiml.algorithms.svm.SVR` : Description. — the target class must actually exist[AuthorYear] format with a DOI linkRegressor base class, not Classifier_, the scikit-learn conventionself._is_fitted in fit()super().__init__()Once your algorithm is complete and tested, send it in.
uv run pytest before submitting.Need help? Open an issue on GitHub, we are happy to help you get your algorithm integrated.