Открыть на GitHub

Введение в численное моделирование

Разработка научной Python-библиотеки: git, uv, pytest, ruff, MkDocs

Краткая идея

В этой лабораторной работе требуется превратить учебный код из “LU-разложение, блочные и ленточные матрицы” в небольшой устанавливаемый Python-пакет для работы с матрицами специальной структуры. Главный акцент работы — полный цикл разработки научного программного обеспечения: организация репозитория, командная работа через git, управление зависимостями, тестирование, оформление документации, сборка пакета, публикация на TestPyPI и использование опубликованного пакета в отдельном проекте.

Исходная точка: ноутбук MatLib.ipynb. Его можно использовать как источник идей, алгоритмов и тестовых примеров. Итоговая работа не должна оставаться набором ячеек ноутбука: результатом должен быть пакет, который можно установить, импортировать, протестировать, собрать и использовать как внешнюю зависимость.

Цели работы

После выполнения работы студент должен уметь:

Формат работы

Работа выполняется в группах. Рекомендуемый размер группы: от 2 до 5 человек. Работа ведется в общем репозитории группы над общим кодом, однако каждый участник реализует свою часть функциональности.

Задания разделены на обязательную и факультативную части. Необязательные задания позволяют освоить подходы к разработке и инструменты, которые используются в современной инженерной практике, однако трудоемкость их освоения выходит за пределы одной лабораторной работы.

Что нужно сдать

К сдаче предоставляется:

  1. Ссылка на групповой git-репозиторий.
  2. Ссылка на опубликованный пакет на TestPyPI.
  3. Отдельный клиентский проект или отдельная директория client-example/, где опубликованный пакет установлен как зависимость и используется в коде.
  4. Краткий отчет в REPORT.md или в конце README.md:
    • состав группы;
    • вклад каждого участника;
    • какие классы и функции реализованы;
    • какие тесты написаны;
    • какие команды использовались для проверки проекта;
    • какие факультативные задания выполнены, если они выполнялись.

Обязательные критерии приемки

Проект считается готовым к проверке, если выполняются все пункты:

Рекомендуемая структура репозитория

Имена директорий и модулей можно менять, но итоговая структура должна ясно отделять код библиотеки, тесты, документацию и примеры.

matlib-team-01/
├── .gitignore
├── .python-version
├── README.md
├── REPORT.md
├── mkdocs.yml
├── pyproject.toml
├── uv.lock
├── src/
│   └── matlib_team_01/
│       ├── __init__.py
│       ├── py.typed
│       ├── base.py
│       ├── dense.py
│       ├── band.py
│       ├── symmetric.py
│       ├── block.py
│       ├── solvers.py
│       └── errors.py
├── tests/
│   ├── test_dense.py
│   ├── test_structured.py
│   ├── test_block.py
│   ├── test_solvers.py
│   └── test_integration.py
├── docs/
│   ├── index.md
│   ├── tutorial.md
│   ├── api.md
│   └── development.md
└── examples/
    ├── poisson_1d.py
    └── block_matrix_demo.py

Название matlib-team-01 приведено как пример. Для публикации на TestPyPI имя проекта должно быть уникальным. Используйте имя вида matlib-<course>-<team>-<suffix> или другое согласованное с преподавателем имя.

Важно различать имя проекта и имя Python-модуля:

имя проекта на TestPyPI: matlib-team-01
имя модуля для import:   matlib_team_01

В названиях проектов часто используются дефисы, а в названиях Python-пакетов — подчеркивания.

Инструменты

В обязательной части используются:

Рекомендуемая версия Python: >=3.11.

Этап 1. Создание проекта и репозитория

Задание 1.1. Создайте библиотечный проект

Создайте новый проект через uv:

uv init --lib matlib-team-01
cd matlib-team-01

Замените matlib-team-01 на уникальное имя вашей команды.

Добавьте зависимости:

uv add numpy
uv add --dev pytest ruff mkdocs

Проверьте, что проект импортируется:

uv run python -c "import matlib_team_01; print(matlib_team_01.__name__)"

Если имя вашего модуля отличается, используйте его вместо matlib_team_01.

Задание 1.2. Настройте git-репозиторий

Если uv init не создал git-репозиторий автоматически, инициализируйте его вручную:

git init
git add .
git commit -m "Initial project structure"

Создайте удаленный репозиторий на GitHub или другой согласованной платформе и отправьте начальное состояние проекта.

git branch -M main
git remote add origin <URL-вашего-репозитория>
git push -u origin main

Задание 1.3. Распределите ответственность

Команда должна заранее договориться, кто отвечает за какие части проекта. Пример распределения:

Участник Возможная зона ответственности
A базовый интерфейс, FullMatrix, экспорт публичного API
B BlockMatrix, проверка совместимости блоков
C BandMatrix или SymmetricMatrix
D solvers.py, численный пример, интеграционный тест
E документация, README, TestPyPI, клиентский проект

Если группа меньше, один участник может объединять несколько ролей. Если группа больше, можно разделить тестирование, документацию, упаковку и дополнительные матричные типы.

Задание 1.4. Работайте через ветки

Каждая содержательная часть должна выполняться в отдельной ветке:

git switch -c feature/dense-matrix

После завершения части работы ветка должна быть объединена с main. Если используется GitHub, рекомендуется делать это через Pull Request. Если Pull Request не используется, слияние должно быть видно в истории git.

Минимальное требование: у каждого участника должен быть хотя бы один содержательный commit, попавший в итоговую ветку main.

Issues в этой работе необязательны. Их можно использовать как факультативное расширение.

Этап 2. Проектирование публичного API

Задание 2.1. Определите минимальный интерфейс матрицы

В файле base.py определите общий интерфейс, который будет поддерживаться всеми основными матричными классами. Это может быть абстрактный базовый класс, Protocol или просто явно документированный набор методов.

Минимально требуется поддержать:

@property
def shape(self) -> tuple[int, int]:
    ...

def to_dense(self) -> np.ndarray:
    ...

def matvec(self, x: np.ndarray) -> np.ndarray:
    ...

Дополнительно можно реализовать:

def __getitem__(self, key: tuple[int, int]) -> float:
    ...

def __matmul__(self, other: np.ndarray) -> np.ndarray:
    ...

Публичный API должен быть простым. Лучше реализовать меньше методов, но проверить и задокументировать их поведение.

Задание 2.2. Определите публичные исключения

В файле errors.py определите исключения для предсказуемых ошибок пользователя:

class ShapeMismatchError(ValueError):
    """Raised when matrix or vector dimensions are incompatible."""


class SingularMatrixError(ValueError):
    """Raised when a linear system cannot be solved due to singularity."""

Используйте эти исключения там, где это уместно. Неправильные размеры матриц и векторов не должны приводить к случайным IndexError или неинформативным ошибкам NumPy.

Задание 2.3. Зафиксируйте публичный API в __init__.py

Пользователь библиотеки должен иметь возможность импортировать основные классы из верхнего уровня пакета:

from matlib_team_01 import FullMatrix, BlockMatrix, BandMatrix, solve

Пример __init__.py:

from .band import BandMatrix
from .block import BlockMatrix
from .dense import FullMatrix
from .errors import ShapeMismatchError, SingularMatrixError
from .solvers import solve

__all__ = [
    "BandMatrix",
    "BlockMatrix",
    "FullMatrix",
    "ShapeMismatchError",
    "SingularMatrixError",
    "solve",
]

Если вы реализуете SymmetricMatrix вместо BandMatrix, скорректируйте пример.

Этап 3. Реализация матричных классов

Задание 3.1. Реализуйте FullMatrix

FullMatrix представляет обычную плотную матрицу.

Минимальные требования:

Пример ожидаемого использования:

import numpy as np
from matlib_team_01 import FullMatrix

A = FullMatrix([[1.0, 2.0], [3.0, 4.0]])
x = np.array([1.0, 1.0])

assert A.shape == (2, 2)
assert np.allclose(A.matvec(x), np.array([3.0, 7.0]))

Задание 3.2. Реализуйте BlockMatrix

BlockMatrix представляет матрицу, составленную из прямоугольной таблицы блоков. Блоками могут быть объекты ваших матричных классов или объекты, приводимые к FullMatrix.

Минимальные требования:

Пример ожидаемого использования:

import numpy as np
from matlib_team_01 import BlockMatrix, FullMatrix

A = FullMatrix([[1.0, 0.0], [0.0, 1.0]])
B = FullMatrix([[2.0], [3.0]])
C = FullMatrix([[4.0, 5.0]])
D = FullMatrix([[6.0]])

M = BlockMatrix([[A, B], [C, D]])

assert M.shape == (3, 3)
assert np.allclose(
    M.to_dense(),
    np.array([
        [1.0, 0.0, 2.0],
        [0.0, 1.0, 3.0],
        [4.0, 5.0, 6.0],
    ]),
)

Подсказка: BlockMatrix.to_dense() удобно реализовать через np.block, но перед этим нужно привести каждый блок к плотному виду. BlockMatrix.matvec() можно сначала реализовать через to_dense() @ x. Более сильная реализация может умножать блоки без сборки полной плотной матрицы.

Задание 3.3. Реализуйте один специальный тип матрицы

Выберите один из вариантов:

  1. BandMatrix — ленточная матрица;
  2. SymmetricMatrix — симметричная матрица;
  3. другой специальный тип, согласованный с преподавателем.

Вариант A. BandMatrix

Минимальные требования:

Пример тридиагональной матрицы:

[ 2 -1  0  0 ]
[-1  2 -1  0 ]
[ 0 -1  2 -1 ]
[ 0  0 -1  2 ]

Вариант B. SymmetricMatrix

Минимальные требования:

Задание 3.4. Реализуйте решение линейной системы

Реализуйте функцию solve(A, b) в solvers.py.

Минимальное требование:

def solve(A: MatrixLike, b: np.ndarray) -> np.ndarray:
    ...

Функция должна:

Для обязательной части допустимо использовать np.linalg.solve(A.to_dense(), b), но это решение должно быть явно оформлено как часть вашего API и покрыто тестами. Если команда переносит LU/LUP из исходной MatLib и использует его в solve, это считается более сильным решением.

Важно: NumPy можно использовать как численную опору и как эталонную реализацию в тестах. Однако классы библиотеки не должны быть пустыми обертками, в которых все операции сразу сводятся к плотной матрице. Хотя бы один матричный тип или одна операция должны содержательно использовать специальную структуру данных.

Этап 4. Тестирование

Задание 4.1. Настройте pytest

Создайте директорию tests/. В pyproject.toml можно добавить:

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

Проверка тестов:

uv run pytest

Задание 4.2. Напишите unit-тесты

Unit-тесты проверяют небольшие изолированные части поведения.

Минимальный набор:

Пример теста:

import numpy as np
import pytest

from matlib_team_01 import FullMatrix, ShapeMismatchError


def test_full_matrix_matvec():
    A = FullMatrix([[1.0, 2.0], [3.0, 4.0]])
    x = np.array([1.0, 1.0])

    assert np.allclose(A.matvec(x), np.array([3.0, 7.0]))


def test_full_matrix_matvec_rejects_wrong_shape():
    A = FullMatrix([[1.0, 2.0], [3.0, 4.0]])
    x = np.array([1.0, 1.0, 1.0])

    with pytest.raises(ShapeMismatchError):
        A.matvec(x)

Задание 4.3. Напишите тесты для solve

Минимальный набор:

Пример:

import numpy as np

from matlib_team_01 import FullMatrix, solve


def test_solve_small_system():
    A = FullMatrix([[3.0, 1.0], [1.0, 2.0]])
    b = np.array([9.0, 8.0])

    x = solve(A, b)

    assert np.linalg.norm(A.to_dense() @ x - b) < 1e-10

Задание 4.4. Напишите интеграционный тест

Интеграционный тест должен проверять не отдельный метод, а совместную работу нескольких частей библиотеки.

Рекомендуемый вариант: одномерная дискретизация уравнения Пуассона или похожая тридиагональная система.

Пример постановки:

A x = b,
A — тридиагональная матрица с 2 на диагонали и -1 на соседних диагоналях.

В тесте нужно:

Пример проверки через невязку:

import numpy as np

from matlib_team_01 import BandMatrix, solve


def test_poisson_like_system_residual_is_small():
    A = BandMatrix.tridiagonal(
        lower=-np.ones(4),
        diagonal=2 * np.ones(5),
        upper=-np.ones(4),
    )
    b = np.ones(5)

    x = solve(A, b)

    assert np.linalg.norm(A.to_dense() @ x - b) < 1e-10

Если у вас нет BandMatrix.tridiagonal, создайте аналогичный объект тем способом, который поддерживает ваша реализация.

Подсказки по численным тестам

Используйте np.allclose, np.isclose и нормы невязки вместо точного сравнения чисел с плавающей точкой.

Избегайте случайных матриц в обязательных тестах. Если случайность все же используется, фиксируйте seed генератора случайных чисел.

Тестируйте публичное поведение, а не внутреннее устройство. Например, тест должен проверять, что BandMatrix.matvec(x) дает правильный результат, а не то, как именно внутри названы массивы диагоналей.

Этап 5. Ruff и локальная проверка качества

Задание 5.1. Настройте Ruff

Добавьте в pyproject.toml:

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I"]

Можно использовать более строгий набор правил:

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]

Более строгий вариант полезен, но может требовать больше времени на исправления. Для обязательной части достаточно первого варианта.

Задание 5.2. Отформатируйте и проверьте код

Во время разработки:

uv run ruff format .
uv run ruff check .

Перед сдачей:

uv run ruff format --check .
uv run ruff check .
uv run pytest

Это локальный набор контрольных проверок. Он заменяет обязательный CI в данной лабораторной работе.

Этап 6. Документация через Markdown и MkDocs

Задание 6.1. Создайте mkdocs.yml

Минимальная конфигурация:

site_name: MatLib Team 01

nav:
  - Home: index.md
  - Tutorial: tutorial.md
  - API: api.md
  - Development: development.md

Если установлен только базовый MkDocs, можно не указывать тему. При желании можно добавить стандартную тему:

theme:
  name: mkdocs

Задание 6.2. Напишите docs/index.md

Файл docs/index.md должен кратко объяснять:

Задание 6.3. Напишите docs/tutorial.md

Файл docs/tutorial.md должен показывать воспроизводимый сценарий использования библиотеки.

Минимально включите:

  1. импорт библиотеки;
  2. создание матрицы;
  3. умножение на вектор;
  4. решение линейной системы;
  5. пример с блочной матрицей или специальной матрицей.

Пример фрагмента:

## Решение небольшой системы

```python
import numpy as np
from matlib_team_01 import FullMatrix, solve

A = FullMatrix([[3.0, 1.0], [1.0, 2.0]])
b = np.array([9.0, 8.0])

x = solve(A, b)
print(x)
```

В документации должны быть команды, которые пользователь действительно может скопировать и выполнить.

Задание 6.4. Напишите docs/api.md

В обязательной части API-документацию можно вести вручную. Для каждого публичного класса или функции укажите:

Минимальный список объектов:

Задание 6.5. Напишите docs/development.md

Этот файл предназначен для будущих разработчиков проекта. Он должен содержать команды:

uv sync
uv run pytest
uv run ruff format .
uv run ruff check .
uv run mkdocs serve
uv run mkdocs build
uv build

Также опишите:

Задание 6.6. Проверьте сборку документации

Для локального просмотра:

uv run mkdocs serve

Для проверки перед сдачей:

uv run mkdocs build

Команда mkdocs build должна завершаться без ошибок.

Этап 7. Сборка пакета

Задание 7.1. Проверьте метаданные в pyproject.toml

В pyproject.toml должны быть заполнены как минимум:

[project]
name = "matlib-team-01"
version = "0.1.0"
description = "Educational matrix library for scientific Python development practice"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "numpy>=1.26",
]

Скорректируйте name, description, авторов и зависимости под ваш проект.

Не публикуйте пакет с именем matlib, если оно не согласовано с преподавателем. Такое имя почти наверняка будет конфликтовать с чужими пакетами или будет слишком общим.

Задание 7.2. Соберите пакет

Выполните:

uv build

После успешной сборки должна появиться директория dist/ с файлами примерно такого вида:

dist/
├── matlib_team_01-0.1.0-py3-none-any.whl
└── matlib_team_01-0.1.0.tar.gz

Имена файлов могут отличаться в зависимости от имени проекта.

Задание 7.3. Проверьте локальную установку wheel

Перед публикацией полезно проверить, что wheel устанавливается в чистом окружении.

Один из вариантов:

cd ..
uv venv matlib-wheel-test
source matlib-wheel-test/bin/activate
uv pip install ./matlib-team-01/dist/*.whl
python -c "from matlib_team_01 import FullMatrix; print(FullMatrix([[1.0]]).shape)"
deactivate

Для Windows команда активации окружения будет отличаться.

Этап 8. Публикация на TestPyPI

Задание 8.1. Подготовьте уникальное имя пакета

TestPyPI, как и настоящий PyPI, использует глобальное пространство имен. Если имя уже занято, публикация не пройдет. Используйте уникальный суффикс.

Пример:

matlib-ns-2026-team-01

После изменения имени в pyproject.toml пересоберите пакет:

uv build

Если вы уже публиковали версию 0.1.0, повторно загрузить файлы с той же версией обычно нельзя. Увеличьте версию:

uv version --bump patch
uv build

Задание 8.2. Настройте публикацию на TestPyPI

Вариант с явным URL публикации:

uv publish --publish-url https://test.pypi.org/legacy/ --token "$UV_PUBLISH_TOKEN"

Токен нужно создать в TestPyPI и передать через переменную окружения. Не записывайте токен в репозиторий, README, ноутбуки, публично доступную историю команд или сообщения Pull Request.

Альтернативный вариант — добавить индекс в pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

Тогда публикация выполняется так:

uv publish --index testpypi --token "$UV_PUBLISH_TOKEN"

После публикации проверьте страницу проекта на TestPyPI.

Задание 8.3. Опубликуйте исправленную версию

В ходе работы обычно обнаруживается небольшая ошибка в документации, экспорте API или метаданных. Исправьте ее и выпустите patch-версию.

uv version --bump patch
uv build
uv publish --index testpypi --token "$UV_PUBLISH_TOKEN"

Если вы не настраивали индекс testpypi в pyproject.toml, используйте вариант с --publish-url.

В отчете укажите, какие версии были опубликованы.

Этап 9. Использование опубликованного пакета в отдельном проекте

Задание 9.1. Создайте клиентский проект

Создайте отдельный проект вне директории библиотеки:

cd ..
uv init matlib-client
cd matlib-client

Цель этого проекта — доказать, что ваша библиотека действительно может быть установлена и использована как внешняя зависимость.

Задание 9.2. Добавьте зависимость из TestPyPI

Рекомендуемый надежный способ — явно указать, что ваш пакет берется из TestPyPI, а остальные зависимости продолжают браться из обычного PyPI.

Добавьте в pyproject.toml клиентского проекта:

[project]
dependencies = [
    "matlib-team-01",
]

[tool.uv.sources]
matlib-team-01 = { index = "testpypi" }

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Замените matlib-team-01 на имя вашего опубликованного проекта.

Затем выполните:

uv sync

Если пакет зависит от numpy, он должен устанавливаться из обычного PyPI. Ваш учебный пакет должен устанавливаться из TestPyPI.

Задание 9.3. Напишите пример использования

Создайте файл example.py:

import numpy as np

from matlib_team_01 import FullMatrix, solve

A = FullMatrix([[3.0, 1.0], [1.0, 2.0]])
b = np.array([9.0, 8.0])

x = solve(A, b)
print("x =", x)
print("residual =", np.linalg.norm(A.to_dense() @ x - b))

Запустите:

uv run python example.py

В репозитории основного проекта или в отчете приведите:

Этап 10. Финальная проверка перед сдачей

Перед сдачей из корня основного проекта должны успешно выполняться команды:

uv sync
uv run ruff format --check .
uv run ruff check .
uv run pytest
uv run mkdocs build
uv build

Также должна успешно выполняться проверка клиентского проекта:

cd ../matlib-client
uv sync
uv run python example.py

Финальный commit рекомендуется пометить тегом:

git tag v0.1.0
git push origin v0.1.0

Если вы публиковали patch-версию, используйте соответствующий тег, например v0.1.1.

Требования к README.md

README.md должен быть коротким, но достаточным для первого знакомства с проектом.

Рекомендуемая структура:

# MatLib Team 01

Краткое описание проекта.

## Installation

```bash
uv add ...
```

## Quick example

```python
...
```

## Development

```bash
uv sync
uv run pytest
uv run ruff check .
```

## Documentation

```bash
uv run mkdocs serve
```

## Team

Список участников и вклад.

README не должен заменять всю документацию. Его задача — дать минимальный быстрый вход в проект.

Требования к коду

Публичный код

Публичные классы и функции должны иметь docstring. Минимально docstring должен объяснять:

Пример:

class FullMatrix:
    """Dense matrix backed by a two-dimensional NumPy array.

    Parameters
    ----------
    data:
        Matrix data. Must be convertible to a two-dimensional NumPy array.
    """

Внутренний код

Внутренние вспомогательные функции можно не документировать так подробно, но их назначение должно быть понятно из имени или короткого комментария.

Не следует делать весь код публичным. Если функция является внутренней, можно назвать ее с начального подчеркивания:

def _validate_vector_shape(...):
    ...

Проверка входных данных

Научный код часто используется в длинных вычислительных сценариях. Ошибка должна возникать как можно ближе к источнику проблемы и быть диагностируемой.

Проверяйте:

Численная корректность

Для операций с плавающей точкой используйте допуски. Не сравнивайте массивы через ==, если речь идет о результате вычислений.

Хороший стиль проверки:

assert np.linalg.norm(A.to_dense() @ x - b) < 1e-10

или:

assert np.allclose(actual, expected, rtol=1e-12, atol=1e-12)

Git workflow

Обязательный минимум:

Примеры хороших имен веток:

feature/full-matrix
feature/block-matrix
feature/band-matrix
feature/solver
feature/tests
feature/docs

Примеры commit-сообщений:

Add FullMatrix implementation
Add block matrix shape validation
Add solver residual tests
Document TestPyPI publishing workflow

Нежелательные commit-сообщения:

fix
update
final
asdf

Критерии оценивания

Возможная схема оценивания:

Раздел Вес
Структура пакета, uv, корректные метаданные 15%
Реализация матричных классов и публичного API 25%
Тесты и численная проверка 20%
Документация MkDocs + README 15%
Git workflow и вклад участников 10%
Сборка, публикация на TestPyPI, клиентский проект 15%

Преподаватель может использовать другую шкалу, но эти веса отражают основную идею работы: важен весь цикл разработки, а не только написание классов.

Частые ошибки

Ошибка 1. Весь код остается в ноутбуке

Ноутбук можно оставить как черновик или пример, но библиотечный код должен находиться в src/<package_name>/.

Ошибка 2. Классы есть, но общего API нет

Если у каждого класса свои имена методов и свое поведение, библиотекой трудно пользоваться. Минимальный общий интерфейс должен быть одинаковым для всех основных матриц.

Ошибка 3. Тесты проверяют реализацию, а не поведение

Плохой тест проверяет, как называется внутренний массив. Хороший тест проверяет, что матрица правильно умножается на вектор, правильно преобразуется к плотному виду и корректно сообщает об ошибках.

Ошибка 4. Все операции сразу делают to_dense()

Для обязательной части это допустимо в некоторых местах, особенно в solve. Но хотя бы один специальный тип матрицы или одна операция должны реально использовать структуру данных. Иначе теряется смысл специальных матриц.

Ошибка 5. Пакет опубликован, но его нельзя установить

Публикация считается завершенной только после проверки в отдельном клиентском проекте.

Ошибка 6. Секреты попали в репозиторий

Токены TestPyPI нельзя коммитить. Если токен случайно попал в репозиторий, его нужно немедленно отозвать в TestPyPI и создать новый.

Ошибка 7. Повторная публикация той же версии

Индекс пакетов обычно не позволяет перезаписать уже опубликованный файл той же версии. Для исправлений увеличивайте версию, например с 0.1.0 до 0.1.1.

Факультативные задания

Факультативные задания необязательны. Их цель — показать, как этот проект может быть приближен к реальной практике разработки научного ПО.

F1. Issues

Создайте issue на каждую крупную задачу:

Свяжите Pull Request с issue так, чтобы соответствующая задача закрывалась после merge.

F2. Продвинутое тестирование pytest

Используйте:

Пример идеи:

import numpy as np
import pytest

from matlib_team_01 import FullMatrix


@pytest.mark.parametrize(
    "data,x,expected",
    [
        ([[1.0]], [2.0], [2.0]),
        ([[1.0, 0.0], [0.0, 1.0]], [3.0, 4.0], [3.0, 4.0]),
    ],
)
def test_matvec_parametrized(data, x, expected):
    A = FullMatrix(data)
    assert np.allclose(A.matvec(np.array(x)), np.array(expected))

F3. Coverage

Добавьте pytest-cov и сформируйте отчет покрытия:

uv add --dev pytest-cov
uv run pytest --cov=matlib_team_01 --cov-report=term-missing

Не следует гнаться за 100% покрытия. Важнее объяснить, какие части кода покрыты, какие не покрыты и почему.

F4. Проверка типов

Добавьте mypy или pyright и проверьте публичный API. Это особенно полезно, если вы явно используете Protocol или абстрактный базовый класс для матриц.

Пример:

uv add --dev mypy
uv run mypy src

Для этой лабораторной проверка типов является факультативной, потому что типизация рекурсивных блочных структур может оказаться сложнее основной темы работы.

F5. CI

Настройте GitHub Actions или GitLab CI так, чтобы при каждом Pull Request или push запускались те же команды, что и локально:

uv run ruff format --check .
uv run ruff check .
uv run pytest
uv run mkdocs build
uv build

CI не должен заменять локальную проверку. Он должен автоматизировать уже понятный набор проверок качества.

F6. Pre-commit hooks

Настройте pre-commit hooks для автоматического запуска ruff перед созданием commit.

Цель задания — не спрятать ошибки, а сделать так, чтобы простые проблемы форматирования исправлялись до попадания кода в историю проекта.

F7. MkDocs Material или автоматическая API-документация

Улучшите документацию:

F8. Публикация документации

Опубликуйте документацию через GitHub Pages или ReadTheDocs.

В отчете укажите ссылку на опубликованную документацию.

F9. Trusted Publishing

Настройте публикацию на TestPyPI через Trusted Publishing и GitHub Actions без хранения API-токена в настройках репозитория.

Это задание требует аккуратной настройки прав и не входит в обязательную часть.

F10. Мини-бенчмарк

Добавьте простой benchmark в examples/ или scripts/.

Например, сравните:

Результат не обязан быть промышленно точным. Цель — показать, что структура данных влияет на производительность и память.

F11. CHANGELOG и версии

Добавьте CHANGELOG.md:

# Changelog

## 0.1.1

- Fixed package metadata.
- Added client usage example.

## 0.1.0

- Initial implementation of FullMatrix, BlockMatrix and BandMatrix.
- Added tests and MkDocs documentation.
- Published package to TestPyPI.

Финальный чек-лист

Перед сдачей проверьте каждый пункт:

Справочные ссылки