Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 79 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Lint and format check
run: |
uv venv --python 3.12
source .venv/bin/activate
uv pip install ruff
ruff check .
ruff format --check .

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# TensorFlow wheels are the constraint on the supported range.
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Install package and test dependencies
run: |
uv venv --python ${{ matrix.python-version }}
source .venv/bin/activate
uv pip install -e ".[dev]"
- name: Run tests
env:
TF_CPP_MIN_LOG_LEVEL: "3"
run: |
source .venv/bin/activate
pytest tests -v

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Build distributions
run: |
uv venv --python 3.12
source .venv/bin/activate
uv build
- name: Check metadata
run: |
source .venv/bin/activate
uv pip install twine
twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: distributions
path: dist/
12 changes: 12 additions & 0 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,23 @@ jobs:
with:
enable-cache: true
- name: Install dependencies and test
env:
TF_CPP_MIN_LOG_LEVEL: "3"
run: |
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[dev]"
pytest tests
- name: Verify the tag matches the packaged version
if: github.event_name == 'release'
run: |
source .venv/bin/activate
package_version="$(python -c 'import infinite_training; print(infinite_training.__version__)')"
tag_version="${GITHUB_REF_NAME#v}"
if [ "$package_version" != "$tag_version" ]; then
echo "::error::Tag $tag_version does not match package version $package_version"
exit 1
fi
- name: Build package
run: |
source .venv/bin/activate
Expand Down
93 changes: 93 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0] - 2026-07-25

Backward compatible with 2.0.0: existing code keeps working and warns where an
API has been renamed.

### Fixed

- `Target(target_value=0)` is now honoured. The default was selected with
`target_value or <bound>`, so a falsy `0`/`0.0` was replaced by an unreachable
bound and the session could never stop on that target.
- `last_value` no longer raises `AttributeError` when read before training, or
after an interrupt during the first round. It is derived from the persisted
value history, so it also survives a restart.
- `Target()` is no longer used as a default argument value, which meant every
trainer built without an explicit target shared one mutable instance.
- Calling `train()` or an inference method before `compile()` now raises a clear
`RuntimeError` instead of `AttributeError: 'InfiniteTrainer' object has no
attribute 'best_model'`.
- A target naming a metric that is absent from the training history now raises a
`RuntimeError` listing the available keys, instead of a bare `KeyError`. When
the missing key starts with `val_`, the message points at `validation_data`.
- Checkpoint parent directories are created automatically; previously a path
such as `runs/exp1/best.npy` failed unless the directory already existed.
- Restored checkpoint values are converted back to Python floats and plain
weight lists rather than being left as zero-dimensional object arrays.
- `KeyboardInterrupt` is logged instead of silently swallowed.
- `requires-python` corrected from `>=3.9` to `>=3.10`. TensorFlow has required
3.10 or newer since 2.16, so the old floor advertised an uninstallable
combination.

### Added

- `InfiniteTrainer`, the new name for `InfinityTraining`.
- `rounds_completed` property.
- `save()` as a public method for checkpointing on demand.
- `Target.is_improvement()`, `Target.is_reached()` and
`Target.worst_possible_value`, so the comparison rules live with the target.
- `__version__` and `__all__` exports.
- A CI workflow running lint, tests on Python 3.10-3.12, and a packaging check
on every push and pull request. Previously tests ran only when a release was
published.
- The publish workflow verifies that the release tag matches the packaged
version.
- `CONTRIBUTING.md`, `CHANGELOG.md`, and a rewritten `README.md` with an API
reference, a resume guide and a security note.
- Test suite covering targets, stop conditions, persistence and resume,
error handling, and the deprecation shims (55 tests, up from 3).

### Changed

- The package is split into `target.py`, `trainer.py` and `_storage.py`. Public
imports are unchanged: `from infinite_training import InfiniteTrainer, Target`.
- Type hints use `float` rather than `np.double` for scalar values.
- Progress and stop reasons are reported through the `logging` module.
- `run.py` moved to `examples/mnist.py` and no longer ships inside the wheel.
- Packaging declares its packages explicitly, so `tests` is no longer picked up
by auto-discovery.

### Removed

- The unused `polars` runtime dependency.

### Deprecated

The following still work and will be removed in 3.0.0:

| Deprecated | Replacement |
| --- | --- |
| `InfinityTraining` | `InfiniteTrainer` |
| `optimize_weight` | `best_weights` |
| `last_weight` | `last_weights` |
| `optimize_value` | `best_value` |
| `list_value` | `value_history` |
| `optimize_model` | `best_model` |
| `predict_optimize()` | `predict_best()` |
| `optimize_weight_path=` | `best_weights_path=` |
| `last_weight_path=` | `last_weights_path=` |
| `optimize_value_path=` | `best_value_path=` |
| `list_value_path=` | `value_history_path=` |

## [2.0.0] - 2026-04-03

- Upgraded to the latest TensorFlow and switched the release workflow to `uv`.

[2.1.0]: https://github.com/vyncint/infinite-training/releases/tag/v2.1.0
[2.0.0]: https://github.com/vyncint/infinite-training/releases/tag/v2.0.0
80 changes: 80 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Contributing

Thanks for your interest in improving Infinite Training. Bug reports, questions
and pull requests are all welcome.

## Getting set up

TensorFlow wheels lag the newest Python release, so develop on Python 3.10-3.12.

```bash
git clone https://github.com/vyncint/infinite-training.git
cd infinite-training

python3.12 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
```

## Running the checks

The same three checks run in CI, and all of them should pass before you open a
pull request.

```bash
pytest tests -v # test suite
ruff check . # lint
ruff format --check . # formatting
```

TensorFlow prints a lot of start-up noise; `TF_CPP_MIN_LOG_LEVEL=3` silences it.
The test suite sets this for you.

To try a change end to end against a real dataset:

```bash
pip install -e ".[example]"
python examples/mnist.py
```

## Making a change

- **Keep the public API stable.** The 2.0.x names are kept as deprecated
aliases and are covered by `tests/test_deprecations.py`. If you rename
something, add an alias that emits a `DeprecationWarning`, note it in the
deprecation table in `CHANGELOG.md`, and leave the removal for the next major
version.
- **Add a test with every behaviour change.** Bug fixes should come with a test
that fails before the fix. Group tests by behaviour in a `Test*` class and
give each test a name that reads as a sentence.
- **Update the docs in the same change.** New or renamed public API belongs in
the README's API reference; user-visible changes belong in `CHANGELOG.md`
under *Unreleased*.
- **Explain the "why" in comments.** The code should say what it does; comments
are for constraints and reasons that are not obvious from reading it.

## Project layout

| Path | Purpose |
| --- | --- |
| `infinite_training/target.py` | `Target` — the stopping criterion and its comparison rules |
| `infinite_training/trainer.py` | `InfiniteTrainer` — the training loop, plus deprecated aliases |
| `infinite_training/_storage.py` | Checkpoint reading and writing (internal) |
| `tests/` | Test suite, one module per concern |
| `examples/` | Runnable examples, not shipped in the wheel |

## Releasing

Maintainers only:

1. Bump `version` in `pyproject.toml` and `__version__` in
`infinite_training/__init__.py`. They must match.
2. Move the *Unreleased* notes in `CHANGELOG.md` under the new version.
3. Publish a GitHub release tagged `vX.Y.Z`. The publish workflow runs the
tests, verifies that the tag matches the packaged version, builds and
uploads to PyPI.

## Code of conduct

Be respectful and constructive. Assume good faith, and keep discussion focused
on the work.
Loading
Loading