# pytorch-ignite -- a high-level training-loop library for PyTorch: an Engine # that drives a user-supplied step function over a data iterable, an event # system to hang handlers off it (checkpointing, early stopping, LR schedules), # and ~70 metrics. # # WHY IT IS HERE: it is the bottom of the chain that unblocks SimNIBS's `charm`. # python-pytorch-ignite -> brainsynth -> brainnet -> simnibs charm, # mesh_tools.meshing and optimization.opt_struct # Without it SimNIBS users cannot build a head model from their own MRI and are # limited to the pre-built ones. # # Class 1 under docs/PACKAGING-POLICY.md: Fedora ships no package by this source # name and nothing provides python3dist(pytorch-ignite). Verified 2026-09-27: # dnf repoquery --repo=fedora --repo=updates 'python*ignite*' -> empty # dnf repoquery --whatprovides 'python3dist(pytorch-ignite)' -> empty # Both runtime dependencies (torch>=2.2,<3 and packaging) come from Fedora # proper: python3-torch is 2.9.1-10.fc44. # The project name is pytorch-ignite; the sdist and its top directory use the # PEP 625 underscore spelling, so %%pypi_source needs the underscore form while # the dist metadata keeps the hyphen. %global pypi_name pytorch-ignite %global src_name pytorch_ignite # The import name is neither of those. %global import_name ignite Name: python-%{pypi_name} Version: 0.5.5 Release: 1%{?dist} Summary: High-level training loop, event system and metrics for PyTorch License: BSD-3-Clause URL: https://github.com/pytorch/ignite Source0: %{pypi_source %{src_name}} # Pure Python: the wheel is built by hatchling with only-include = ["ignite"] # and ships no compiled objects. torch itself is arch-specific, but that is a # dependency, not content of this package. BuildArch: noarch BuildRequires: python3-devel %global _description %{expand: PyTorch-Ignite is a library for training and evaluating neural networks in PyTorch. The Engine abstracts the training loop: it runs a user-supplied step function over a data iterable and emits events (started, epoch started, iteration completed, ...) that handlers attach to, so checkpointing, early stopping, learning-rate scheduling, progress reporting and metric computation are composed rather than written into the loop. It ships roughly seventy metrics, including classification, regression, clustering, segmentation, GAN and NLP metrics, and a distributed-computation layer that abstracts over torch.distributed, Horovod and XLA.} %description %_description %package -n python3-%{pypi_name} Summary: %{summary} # OPTIONAL INTEGRATIONS ARE NOT REQUIRES. # # ignite ships handler modules for tensorboard/tensorboardX, Weights & Biases, # MLflow, ClearML, Neptune, Polyaxon, Visdom and tqdm, and metrics that defer to # scikit-learn, scipy, pandas, matplotlib and pynvml. Every one of those imports # happens INSIDE a function or a class __init__ body, never at module scope -- # checked over the whole tree, e.g. # ignite/handlers/wandb_logger.py:148 import wandb # ignite/handlers/mlflow_logger.py:148 import mlflow # ignite/handlers/tensorboard_logger.py:209 from tensorboardX import SummaryWriter # ignite/metrics/roc_auc.py:12 from sklearn.metrics import roc_auc_score # ignite/metrics/gan/fid.py:30 import scipy.linalg # so nothing here is needed to import the package, and none of it may be a hard # Requires: a user who wants the Engine must not be made to install MLflow. # # Recommends, not Requires, and only for the ones Fedora actually ships. Checked # 2026-09-27 against f44: python3-tqdm, python3-scikit-learn, python3-matplotlib, # python3-pandas and python3-scipy exist; python3-tensorboardX, python3-visdom, # python3-wandb, python3-mlflow, python3-clearml, python3-neptune and # python3-polyaxon do NOT exist in Fedora at all, so listing them even as # Recommends would be dead text that no depsolver can ever act on. The handlers # for those raise a clear RuntimeError naming the pip package, which is the # right behaviour for content Fedora does not carry. # # tensorboard support needs no dependency of its own: ignite falls back to # torch.utils.tensorboard, which python3-torch already provides. # # numpy is the interesting one. It is genuinely optional -- every use is wrapped # in try/except ImportError (ignite/utils.py:390, ignite/engine/deterministic.py # :91 and :108) and manual_seed's own docstring says "optionally numpy (if can be # imported)". The %%check below passed in a build root that had no numpy at all, # which is the proof it is not required. But python3-torch does not require numpy # either, so a minimal install really does lack it, and the f44 build root # printed # UserWarning: Failed to initialize NumPy: No module named 'numpy' # (Triggered internally at .../torch/csrc/utils/tensor_numpy.cpp:84) # on the very first `import ignite`. Without numpy, manual_seed() silently does # not seed numpy's RNG and the deterministic engine's _get_rng_states() silently # returns a shorter list -- so reproducibility, which is the reason to use # ignite.engine.deterministic at all, degrades without saying so. Recommends, # because it is not needed to import or train, and because a depsolver default # of "present" is the right one here. Recommends: python3-numpy Recommends: python3-tqdm Recommends: python3-scikit-learn Recommends: python3-scipy Recommends: python3-matplotlib Recommends: python3-pandas %description -n python3-%{pypi_name} %_description %prep %autosetup -p1 -n %{src_name}-%{version} %generate_buildrequires # No extras are declared by upstream; requirements-dev.txt is test/lint/doc # tooling and pulls things Fedora does not have (clearml, neptune, visdom). %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files -l %{import_name} %check # GUARD FIRST: prove the import check is actually going to inspect the modules # whose optional imports are the risk in this package. %%pyproject_check_import # derives its list from %%pyproject_save_files; if that list were ever reduced to # the top-level package only (say by someone adding -t) the logger modules would # stop being covered and the check would still report success. Positive test -- # `grep -qx ... || exit 1` -- deliberately not `grep -q ... && exit 1`, which # returns 1 when grep does not match and so inverts the test it looks like. for m in %{import_name} \ %{import_name}.engine \ %{import_name}.handlers \ %{import_name}.handlers.tensorboard_logger \ %{import_name}.handlers.wandb_logger \ %{import_name}.handlers.mlflow_logger \ %{import_name}.handlers.clearml_logger \ %{import_name}.handlers.neptune_logger \ %{import_name}.handlers.polyaxon_logger \ %{import_name}.handlers.visdom_logger \ %{import_name}.handlers.tqdm_logger \ %{import_name}.metrics.roc_auc \ %{import_name}.distributed.comp_models.xla \ %{import_name}.distributed.comp_models.horovod \ %{import_name}.contrib.handlers; do grep -qxF "$m" "%{_pyproject_modules}" || { echo "ERROR: $m is not in the %%pyproject_check_import module list." echo "The import check would pass without ever looking at it." echo "--- list as generated ---" cat "%{_pyproject_modules}" exit 1 } done # Baseline: import every installed module. NOTHING is excluded. The xla and # horovod comp_models are in the list and import cleanly without torch_xla or # horovod because they wrap those imports in try/except ImportError and set # has_xla_support / has_hvd_support False # (ignite/distributed/comp_models/xla.py:11-17, horovod.py:13-24). %pyproject_check_import # FUNCTIONAL CHECK. ignite is a training-loop library; importing it proves # almost nothing about it, because the whole point of the package is an event # dispatch loop that either drives a step function or does not. So: drive one. cat > %{_builddir}/ignite-smoke.py <<'EOF' """Exercise the four things a consumer of ignite actually depends on: the Engine's event loop, the supervised-trainer factory (autograd + optimizer step through ignite's own update function), the metric attach/compute cycle, and a handler that produces an artefact on disk. Every assertion is exact or strictly directional. Nothing here is allowed to pass by finding nothing: the fired-events list, the parameter delta, the accuracy value and the checkpoint directory listing are all asserted non-empty or equal to a value computed by hand. """ import os import tempfile import torch from torch import nn import ignite from ignite.engine import ( Engine, Events, create_supervised_evaluator, create_supervised_trainer, ) from ignite.handlers import Checkpoint, DiskSaver from ignite.metrics import Accuracy torch.manual_seed(0) torch.set_num_threads(1) print("ignite", ignite.__version__, "on torch", torch.__version__) assert ignite.__version__ == "0.5.5", ignite.__version__ # --- 1. the raw Engine event loop ------------------------------------------- seen = [] fired = [] def step(engine, batch): seen.append(batch) return batch * 2 engine = Engine(step) for ev in (Events.STARTED, Events.EPOCH_STARTED, Events.EPOCH_COMPLETED, Events.COMPLETED): engine.add_event_handler(ev, lambda _e, ev=ev: fired.append(ev)) data = [1, 2, 3, 4, 5] engine.run(data, max_epochs=2) assert engine.state.epoch == 2, engine.state.epoch assert engine.state.iteration == 10, engine.state.iteration assert seen == data * 2, seen assert engine.state.output == 10, engine.state.output # Exact event accounting: STARTED and COMPLETED once, the epoch pair twice. assert fired == [ Events.STARTED, Events.EPOCH_STARTED, Events.EPOCH_COMPLETED, Events.EPOCH_STARTED, Events.EPOCH_COMPLETED, Events.COMPLETED, ], fired print("engine loop OK: 2 epochs, 10 iterations, 6 events in order") # --- 2. create_supervised_trainer really trains ------------------------------ # Linear regression the model can fit, so "the loss went down" is a statement # about ignite wiring autograd and the optimizer, not about luck. w_true = torch.tensor([[2.0, -3.0, 0.5, 1.0]]) x = torch.randn(64, 4) y = x @ w_true.T batches = [(x[i : i + 8], y[i : i + 8]) for i in range(0, 64, 8)] model = nn.Linear(4, 1, bias=False) before = model.weight.detach().clone() optimiser = torch.optim.SGD(model.parameters(), lr=0.05) trainer = create_supervised_trainer(model, optimiser, nn.MSELoss()) losses = [] trainer.add_event_handler(Events.ITERATION_COMPLETED, lambda e: losses.append(e.state.output)) tmpdir = tempfile.mkdtemp(prefix="ignite-ckpt-") checkpoint = Checkpoint({"model": model}, DiskSaver(tmpdir, require_empty=False), n_saved=1) trainer.add_event_handler(Events.EPOCH_COMPLETED, checkpoint) trainer.run(batches, max_epochs=30) assert len(losses) == 30 * len(batches), len(losses) assert all(isinstance(v, float) for v in losses), losses[:3] assert losses[-1] < losses[0] / 10.0, (losses[0], losses[-1]) after = model.weight.detach().clone() assert not torch.equal(before, after), "optimizer step never ran" # It should have essentially recovered w_true. assert torch.allclose(after, w_true, atol=5e-2), after print(f"trainer OK: loss {losses[0]:.4f} -> {losses[-1]:.6f}, weights recovered") # --- 3. the Checkpoint handler wrote something ------------------------------- saved = sorted(os.listdir(tmpdir)) assert saved, f"Checkpoint handler wrote nothing into {tmpdir}" assert checkpoint.last_checkpoint is not None assert os.path.getsize(os.path.join(tmpdir, saved[-1])) > 0, saved reloaded = torch.load(os.path.join(tmpdir, saved[-1]), weights_only=True) assert torch.equal(reloaded["weight"], after), "checkpoint does not match the model" print("checkpoint OK:", saved, "reloads and matches the trained weights") # --- 4. metric attach / iterate / compute, against a hand-computed value ----- # nn.Identity means the "logits" are the inputs, so the expected Accuracy is # arithmetic rather than a tolerance: argmax gives [0, 1, 0, 1] against # labels [0, 1, 0, 0], i.e. 3 of 4. eval_x = torch.tensor([[1.0, 0.0], [0.0, 1.0], [1.0, 0.0], [0.0, 1.0]]) eval_y = torch.tensor([0, 1, 0, 0]) evaluator = create_supervised_evaluator(nn.Identity(), metrics={"accuracy": Accuracy()}) evaluator.run([(eval_x, eval_y)]) assert "accuracy" in evaluator.state.metrics, evaluator.state.metrics acc = evaluator.state.metrics["accuracy"] assert abs(acc - 0.75) < 1e-12, acc print(f"metrics OK: Accuracy == {acc} exactly as computed by hand") print("ALL FUNCTIONAL CHECKS PASSED") EOF # Keep torch single-threaded and quiet in the build root; OMP thread pools and # CPU-affinity probing are not what is under test here. OMP_NUM_THREADS=1 \ MKL_NUM_THREADS=1 \ PYTHONPATH=%{buildroot}%{python3_sitelib} \ %{python3} %{_builddir}/ignite-smoke.py %files -n python3-%{pypi_name} -f %{pyproject_files} %doc README.md %doc CITATION %changelog * Sun Sep 27 2026 Morgan Hough - 0.5.5-1 - Initial package. Bottom of the chain that unblocks SimNIBS charm (pytorch-ignite -> brainsynth -> brainnet -> charm / mesh_tools.meshing / optimization.opt_struct); charm cannot run today, so SimNIBS users are limited to pre-built head models. - Class 1 (new to Fedora): nothing in Fedora provides python3dist(pytorch-ignite), and both runtime deps (torch>=2.2,<3 and packaging) come from Fedora proper. - Optional integrations (tensorboard, wandb, mlflow, clearml, neptune, polyaxon, visdom, tqdm, sklearn, scipy, pandas, matplotlib, numpy) are Recommends at most, and only where Fedora has the package; all of their imports are function-local upstream, so none is needed to import ignite. Verified on the artefact: rpm -q --requires lists only python3.14dist(torch) >= 2.2 with < 3~~, python3.14dist(packaging) and python(abi), and nothing else. - %%check does a real Engine run, not only %%pyproject_check_import: 2-epoch event-order accounting, a create_supervised_trainer fit whose weights are asserted against the generating coefficients, a Checkpoint reloaded and compared to the trained model, and an Accuracy computed by hand. - f44 only for now. Upstream declares requires-python ">=3.10,<3.15" and f45/rawhide ship python 3.15.0~rc2, so the f45+ chroots need that cap revisited with upstream first. See docs/upstream/.