Metadata-Version: 2.4
Name: ipakit
Version: 0.1.0
Summary: IPA phonetic toolkit
Author: Kevin Lenzo
License-Expression: MIT
Project-URL: Homepage, https://github.com/lenzo-ka/ipakit
Project-URL: Repository, https://github.com/lenzo-ka/ipakit
Project-URL: Issues, https://github.com/lenzo-ka/ipakit/issues
Keywords: ipa,phonetics,phonology,linguistics,speech,cmu,arpabet,timit
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
Classifier: Topic :: Text Processing :: Linguistic
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Provides-Extra: lint
Requires-Dist: ruff>=0.15; extra == "lint"
Requires-Dist: mypy>=2.1; extra == "lint"
Requires-Dist: black<27,>=26.5; extra == "lint"
Provides-Extra: icu
Requires-Dist: icukit-pyicu>=78.3.0; extra == "icu"
Provides-Extra: dev
Requires-Dist: ipakit[icu,lint,test]; extra == "dev"
Dynamic: license-file

# ipakit

[![CI](https://github.com/lenzo-ka/ipakit/actions/workflows/ci.yml/badge.svg)](https://github.com/lenzo-ka/ipakit/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/ipakit.svg)](https://pypi.org/project/ipakit/)
[![Python versions](https://img.shields.io/pypi/pyversions/ipakit.svg)](https://pypi.org/project/ipakit/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A pure-Python IPA (International Phonetic Alphabet) phonetic toolkit:
phonetic features, distances, natural classes, and conversion between IPA and
CMU ARPABET, X-SAMPA, Kirshenbaum, and TIMIT notations.

- **Zero runtime dependencies** — all phonetic data ships as XML in the package.
- **Typed** (`py.typed`, mypy-strict clean).
- **Both a library and a CLI** (`ipakit`).

## Install

```bash
pip install ipakit
```

For development (tests, linters, and the X-SAMPA table tooling):

```bash
pip install -e ".[dev]"
```

## Quick start (Python)

```python
import ipakit

# Phonetic features and descriptions
ipakit.describe("p")            # 'voiceless bilabial plosive'
ipakit.features("p")            # {'manner': 'plosive', 'place': 'bilabial', ...}

# Phonetic distance (0.0 identical … 1.0 maximally different)
ipakit.distance("p", "b")       # 0.043   (differ only in voicing)
ipakit.nearest_phones("p", n=3) # [('ɸ', 0.005), ('f', 0.008), ('p͡f', 0.008)]
ipakit.word_similarity("kæt", "kæd")   # 0.986

# Tokenize / normalize (tie-bar affricates, diphthongs)
ipakit.tokenize("t͡ʃe͡ɪnd͡ʒ")   # ['t͡ʃ', 'e͡ɪ', 'n', 'd͡ʒ']

# Validate
ipakit.validate_ipa("kæt")      # []  (valid)
ipakit.validate_ipa("k4t")      # [{'type': 'error', 'code': 'unknown_symbol', ...}]
```

### Conversions

```python
# CMU ARPABET
ipakit.to_cmu("ˈkæt")             # ['K', 'AE1', 'T']
ipakit.to_ipa(["K", "AE1", "T"])  # 'kˈæt'

# X-SAMPA (ASCII)
ipakit.ipa_to_xsampa("t͡ʃ")        # 't_S'
ipakit.xsampa_to_ipa("t_S")        # 't͡ʃ'

# Kirshenbaum / TIMIT
ipakit.to_kirshenbaum("kæt")       # 'k&t'
ipakit.to_timit("kæt")             # ['k', 'ae', 't']

# Features straight from a non-IPA symbol (list of per-segment dicts)
ipakit.features_from_xsampa("t_S")  # [{'manner': 'affricate', 'place': 'postalveolar', ...}]
ipakit.features_from_cmu("K")       # [{'manner': 'plosive', 'place': 'velar', ...}]
```

By default converters skip symbols they can't map. Pass `strict=True` to any of
them to raise `ValueError` on unconvertible input instead:

```python
ipakit.to_cmu("k4t")                # ['K', 'T']  (the '4' is skipped)
ipakit.to_cmu("k4t", strict=True)   # ValueError: Cannot convert to CMU ARPABET: ...
```

### Distribution-aware distance

`distance()` is the **raw feature metric** — an absolute, inventory-independent
mean over phonetic features (so `distance("p", "b")` is always `0.043`). Raw
distances bunch up in a narrow band, which makes fixed thresholds hard to pick.
`normalized_distance()` renormalizes a raw distance to its **percentile** within
the bundled IPA inventory's pairwise distribution, spreading values across
`[0, 1]`:

```python
ipakit.distance("p", "b")             # 0.043   raw feature distance
ipakit.normalized_distance("p", "b")  # 0.155   percentile within bundled IPA
ipakit.normalized_distance("p", "a")  # 0.602
ipakit.confusability("p", "b")        # 0.845   complement of normalized_distance
```

For a model over a chosen reference inventory — percentiles are **relative** to
it and not comparable across inventories — use `distance_model()`:

```python
from ipakit import Phoneset

eng = ipakit.distance_model(
    Phoneset.from_list(
        ["p", "b", "t", "d", "k", "ɡ", "s", "z", "m", "n", "l", "ɹ", "a", "i", "u"],
        name="english",
    )
)
eng.distance("p", "b")                       # 0.267   percentile within this 15-phone set
eng.nearest("p", n=3)                        # [('t', 0.048), ('s', 0.086), ('k', 0.21)]
eng.word_similarity("kæt", "kæd")            # 0.956
eng.is_similar("kæt", "kæd", threshold=0.8)  # True
```

`distance_model()` also accepts `gamma` (power transform to push dissimilar
pairs apart), `sub_mode="di"` (delete+insert substitution cost for word
alignment), and `threshold` / `max_length_ratio` defaults for `is_similar`. The
raw pairwise matrix ships as `ipakit/data/confusion.json`; per-inventory models
reuse it and only re-slice the percentile distribution.

## Conventions

- **Stress is placed on the vowel** (the syllable nucleus), not the syllable
  onset: `to_ipa(["K", "AE1", "T"])` → `kˈæt`. Syllabification is preserved
  across round trips (`W AO1 T ER0` ↔ `wˈɔtɚ`).
- **Affricates and diphthongs use the tie bar** (`t͡ʃ`, `e͡ɪ`).
- **Round-trip guarantee (X-SAMPA only):** IPA written in these conventions
  round-trips through X-SAMPA (`ipa → xsampa → ipa`). The only exceptions are
  `b͡v` and `t͡θ`, where the X-SAMPA tie encoding `_` collides with a
  diacritic/tone encoding (`_v`, `_T`) — an inherent X-SAMPA ambiguity that ICU
  shares. The CMU, TIMIT, and Kirshenbaum mappings are lossy (they collapse IPA
  distinctions) and carry no round-trip guarantee.

## CLI

```text
ipakit features p                    # Get features for 'p'
ipakit describe p                    # "voiceless bilabial plosive"
ipakit convert to-cmu "kˈæt"         # IPA to CMU: K AE1 T (stress on the vowel)
ipakit convert to-ipa K AE1 T        # CMU to IPA: kˈæt
ipakit convert to-xsampa "t͡ʃ"        # IPA to X-SAMPA: t_S
ipakit query match plosive bilabial  # Find phones by feature
ipakit analysis natural-class p t k  # Shared features of a set
ipakit analysis minimal-pairs p      # Find similar phones
ipakit distance pair p b             # Raw feature distance: ~0.04
ipakit distance confusability p b    # Inventory-relative: 0.8454
ipakit distance word kæt kæd         # Word similarity: 0.9742
```

The `distance confusability`/`word` commands use the distribution-aware model;
scope them to a reference inventory with `--phoneset FILE` (one phone per line).

Most commands accept `--format json` (or `-j`) for machine-readable output.
Run `ipakit`, `ipakit <group>`, or append `help`/`-h` anywhere for usage.

## Development

```bash
pip install -e ".[dev]"   # or ".[test]" / ".[lint]" for a lean subset
pre-commit install        # black, ruff, mypy --strict, hygiene hooks
pytest                    # unit tests + docstring examples
```

CI (`.github/workflows/ci.yml`) mirrors these on every push/PR across Python
3.11–3.13, and validates the committed derived artifacts (the IPA ↔ X-SAMPA
table and the phone-distance matrix) against their generators in `scripts/`.

## License

MIT — see [LICENSE](LICENSE).
