Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b88d608
feat(metrics): add metrics module and average word length function
fin-griffin Feb 3, 2026
a140049
feat(metrics): add std deviation, skewness, kurtosis of word length a…
fin-griffin Feb 3, 2026
68b1de0
feat(metrics): add hapax, dis and tri legomena ratios to metric suite
fin-griffin Feb 3, 2026
071aedb
feat: add internal private tokenizer
fin-griffin Feb 3, 2026
9b8a059
feat(lexicon): add function word set
fin-griffin Feb 3, 2026
aac44de
feat: migrate tokenization to internal tokenizer
fin-griffin Feb 3, 2026
90581a0
feat(metrics): add function word ratio
fin-griffin Feb 3, 2026
b875d3c
feat(metrics): add richness; ttr and mattr
fin-griffin Feb 4, 2026
b63dac8
feat(stylometry): add character-level tokenizer for n-gram computation
fin-griffin Feb 5, 2026
38c03b4
refactor(metrics): add helpers for computing ttr and mattr from seque…
fin-griffin Feb 5, 2026
cd61c8b
refactor(metrics): add helpers for computing word length statistics f…
fin-griffin Feb 5, 2026
34cfaa7
feat(metrics): add richness metrics for character n-grams; n = 3,4,5
fin-griffin Feb 5, 2026
80bc070
chore(metrics): add __repr__ special method to MetricSpec for clean p…
fin-griffin Feb 5, 2026
c05929e
feat(tests): add test suite for stylometric metrics
fin-griffin Feb 5, 2026
c358d43
chore(metrics): add block comments to metrics suite
fin-griffin Feb 5, 2026
2770a5d
feat(ci): add test workflow; on push tests must pass, on PR into main…
fin-griffin Feb 5, 2026
00a1ffe
fix(ci): create uv venv in ci
fin-griffin Feb 5, 2026
a396a89
fix(ci): run pytest inside uv venv
fin-griffin Feb 5, 2026
91ecd52
chore: add slots to MetricSpec dataclass
fin-griffin Feb 6, 2026
0a79b29
feat(datasets): add lightweight dataset specification with version pi…
fin-griffin Feb 6, 2026
2637517
chore: remove lexicon package and migrate function words list
fin-griffin Feb 6, 2026
90e059f
chore: refactor _lexicons.py
fin-griffin Feb 6, 2026
39f5328
fix(datasets): allow single string split to be passed into dataset spec
fin-griffin Feb 6, 2026
ed647c8
chore: update __init__.py for top-level package
fin-griffin Feb 6, 2026
b20434d
feat(datasets): add VoiceDataset class (HF datasets.Dataset wrapper)
fin-griffin Feb 11, 2026
bd6c0bb
feat(datasets): add dataset materialisation utility
fin-griffin Feb 11, 2026
b8d5042
feat(tests): add test suite for
fin-griffin Feb 12, 2026
9f0a4bf
feat(tests): add test suite for get_dataset.py
fin-griffin Feb 17, 2026
66e4ea7
chore: fix imports in test suite
fin-griffin Feb 17, 2026
43fd01f
Merge branch 'feature/stylometry' into feature/datasets
fin-griffin Feb 17, 2026
81f97ab
Merge pull request #1 from acceleratescience/feature/datasets
fin-griffin Feb 17, 2026
b1eabba
fix: fix getitem method in example sequence
fin-griffin Feb 17, 2026
77a0cd6
feat: add utilities for comparing stylometric distributions
fin-griffin Feb 18, 2026
ab5d282
feat: add plotting utilities
fin-griffin Feb 18, 2026
5d1d110
feat: add core comparison function
fin-griffin Feb 18, 2026
8f22829
chore: update API shape after adding comparison utilities
fin-griffin Feb 18, 2026
c387bbd
chore: fix circular import
fin-griffin Feb 18, 2026
d392260
feat(tests): add unit test suite for comparison.py
fin-griffin Feb 18, 2026
4993972
chore: exclude plotting utilities from pytest coverage checks
fin-griffin Feb 18, 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
Binary file added .coverage
Binary file not shown.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run]
omit =
*/plotting.py
39 changes: 39 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: tests

on:
push:
pull_request:

jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH

- name: Create venv
run: uv venv

- name: Install dependencies
run: uv pip install -e .[dev]

- name: Run tests (push)
if: github.event_name == 'push'
run: uv run pytest

- name: Run tests + coverage gate (PR into main)
if: github.event_name == 'pull_request' && github.base_ref == 'main'
run: uv run pytest --cov=voice --cov-report=term-missing --cov-fail-under=90
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ htmlcov/
# Local configs
.env
*.local

# Notebooks
**.ipynb
14 changes: 10 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,25 +58,31 @@ repos:
hooks:
- id: isort

# Type checking
# Type checking (keep for src; exclude tests)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
args: ["--config-file", "pyproject.toml"]
exclude: ^tests/

# Docstring correctness (sphinx convention)
# Docstring correctness (sphinx convention) (keep for src; exclude tests)
- repo: https://github.com/jsh9/pydoclint
rev: 0.7.6
hooks:
- id: pydoclint
args: ["--config=pyproject.toml"]
exclude: ^tests/

# Cognitive complexity limit via flake8-cognitive-complexity
# Cognitive complexity limit via flake8-cognitive-complexity (keep for src; exclude tests)
- repo: https://github.com/pycqa/flake8
rev: 7.3.0
hooks:
- id: flake8
additional_dependencies:
- flake8-cognitive-complexity
args: ["--max-cognitive-complexity=10"]
args:
- --max-line-length=79
- --extend-ignore=E203
- --max-cognitive-complexity=10
exclude: ^tests/
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"cspell/**",
"LICENSE",
".secrets.baseline",
"*.egg-info/"
"*.egg-info/",
"**/*.ipynb"
]
}
10 changes: 10 additions & 0 deletions cspell/library-words.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
huggingface
resp
passthrough
figsize
whitegrid
kdeplot
xlabel
ylabel
frameon
allclose
13 changes: 13 additions & 0 deletions cspell/project-words.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
hapax
dis
tri
legomenon
legomena
MATTR
TTR
ngram
prov
wasserstein
bonf
bonferroni
aeiou
51 changes: 45 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ version = "0.1.0"
description = "Fine-tuning for stylistic fidelity"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
dependencies = [
"datasets>=4.5.0",
"dotenv>=0.9.9",
"huggingface-hub>=1.4.1",
"matplotlib>=3.10.8",
"scipy>=1.17.0",
"seaborn>=0.13.2",
]

[build-system]
requires = ["setuptools>=68", "wheel"]
Expand All @@ -20,15 +27,19 @@ dev = [
"flake8-cognitive-complexity>=0.1.0",
"isort>=7.0.0",
"mypy>=1.19.1",
"notebook>=7.5.3",
"pre-commit>=4.5.1",
"pydoclint>=0.8.3",
"pytest>=9.0.2",
"pytest-cov>=7.0.0",
"ruff>=0.14.14",
]

[tool.ruff]
line-length = 88
line-length = 79
target-version = "py312"
force-exclude = true
extend-ignore = ["D107"]

[tool.ruff.lint]
select = [
Expand All @@ -49,25 +60,53 @@ select = [
"C901",
]

ignore = [
"ANN101", "ANN102",
]
ignore = []

[tool.ruff.lint.mccabe]
max-complexity = 12

[tool.ruff.lint.pydocstyle]
convention = "pep257"

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = [
"D",
"ANN",
"ARG001",
"ARG005",
"B007",
]

[tool.mypy]
python_version = "3.12"
strict = true
warn_unused_configs = true
no_implicit_optional = true

[[tool.mypy.overrides]]
module = ["tests.*"]
ignore_errors = true

[[tool.mypy.overrides]]
module = [
"datasets",
"datasets.*",
"huggingface_hub",
"huggingface_hub.*",
"numpy",
"numpy.*",
"scipy.stats",
"scipy.stats.*",
"seaborn",
"seaborn.*",
"matplotlib",
"matplotlib.*",
]
ignore_missing_imports = true

[tool.isort]
profile = "black"
line_length = 88
line_length = 79

[tool.pydoclint]
style = "sphinx"
Expand Down
10 changes: 10 additions & 0 deletions src/voice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@
This package provides tools for fine-tuning and evaluating
LLMs for stylistic fidelity.
"""

from voice.datasets import DatasetSpec, get_dataset
from voice.stylometry import get_metrics, make_comparison

__all__: list[str] = [
"DatasetSpec",
"get_metrics",
"get_dataset",
"make_comparison",
]
15 changes: 15 additions & 0 deletions src/voice/datasets/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Datasets package for VOICE.

This package contains user-facing abstractions for dataset management,
in particular we expose:

- `DatasetSpec` - Lightweight, declarative specification of a dataset.
- `VoiceDataset` - Split-aware wrapper around a Hugging Face dataset.
- `get_dataset` - Entrypoint for materialising a dataset from a specification.
"""

from voice.datasets.dataset import DatasetSpec, VoiceDataset
from voice.datasets.get_dataset import get_dataset

__all__: list[str] = ["DatasetSpec", "VoiceDataset", "get_dataset"]
67 changes: 67 additions & 0 deletions src/voice/datasets/_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Dataset schema primitives.

This module defines lightweight schema-level abstractions shared across
the dataset loading pipeline.

This module is private by convention and not part of the public API.
"""

from __future__ import annotations

from enum import Enum


class Split(str, Enum):
"""
Enumeration of supported dataset splits.

.. attribute :: TRAIN

Name of the training split

.. attribute :: VALIDATION

Name of the validation split

.. attribute :: TEST

Name of the test split
"""

TRAIN = "train"
VALIDATION = "validation"
TEST = "test"

def __repr__(self) -> str:
"""
Return the split as a string.

:return: Readable string of the split
"""
return f"'{self.value}'"

@classmethod
def parse(cls, value: Split | str) -> Split:
"""
Parse a split value from a string or Split instance.

:param value: Split name or Split enum value
:return: Parsed Split enum value
:raises TypeError: If the value is not a Split or string
:raises ValueError: If the value does not correspond to a valid split
"""
if isinstance(value, cls):
return value
if not isinstance(value, str):
raise TypeError(
f"`split` must be a Split or str, got {type(value).__name__}"
)
v = value.strip().lower()
try:
return cls(v)
except ValueError as e:
allowed = ", ".join(s.value for s in cls)
raise ValueError(
f"Invalid split {value!r}. Allowed: {allowed}"
) from e
16 changes: 16 additions & 0 deletions src/voice/datasets/_specs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Pre-defined dataset specifications for testing.

All specifications are of type `voice.datasets.DatasetSpec`.

This module is private by convention and not part of the public API.
"""

from voice.datasets import DatasetSpec
from voice.datasets._schema import Split

BUSH_LATEST = DatasetSpec(
repo_id="AccelerateScience/bush-dataset",
revision="main",
splits=(Split.TRAIN, Split.VALIDATION, Split.TEST),
)
Loading