# vyakarana


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

Vyākaraṇa is Sanskrit grammar, and its foundation is Pāṇini’s
Aṣṭādhyāyī, about 4,000 rules (sūtras) numbered by book, chapter and
rule, such as 3.1.68. vidyut includes a program that applies these rules
step by step, and this module uses it to show how a written word is
derived. The second section matches a word-by-word analysis of a line to
the line’s written words, and the third names the sandhi rule at the
join of two words.

## Specs and derivations

A pada is an inflected word. To ask vidyut for one, you describe it in a
single string called a spec. A spec is written in SLP1 with its fields
separated by `|`, and the first field says what kind of word it is:

    s|<stem>|<liṅga>|<vibhakti>|<vacana>            a noun or adjective from a plain stem (a compound's stem written joined)
    a|<text>                                        an indeclinable (avyaya)
    k|<dhātu>|<kṛt>|<liṅga>|<vibhakti>|<vacana>     a noun from a root and a kṛt suffix, such as a participle
    ka|<dhātu>|<kṛt>                                an indeclinable kṛt form (ktvā, lyap, tumun)
    t|<dhātu>|<prayoga>|<lakāra>|<puruṣa>|<vacana>[|P|A]  a finite verb; P or A asks for active or middle endings

Liṅga is gender, vibhakti case, vacana number, prayoga voice, lakāra
tense or mood, and puruṣa person, each given by vidyut’s SLP1 name.
`s|rAma|puM|praTamA|eka` is rāmaḥ, masculine nominative singular. A
dhātu (verbal root) is written
`<aupadeśika>,<gaṇa>,<antargaṇa>,<sanādi+…>,<upasarga+…>`: the root as
the Dhātupāṭha lists it, its verb class, its sub-class, any suffixes
that make a new root such as the causative, and any prefixes, with
unused fields left empty. `t|BU,BvAdi,,,|kartari|la~w|praTama|eka|P` is
bhavati.
[`dhatu`](https://vedicreader.github.io/ganapati/vyakarana.html#dhatu)
turns such a field into a vidyut `Dhatu`, and
[`made`](https://vedicreader.github.io/ganapati/vyakarana.html#made)
gives the stem vidyut builds from it: the root alone, with its prefixes
set apart, or with a kṛt suffix the kṛdanta’s stem.
`made('Ra\\Sa~,divAdi')` is naS, `made('A\\px~,svAdi,,,pra', 'kta')` is
prApta, and a field vidyut cannot read gives an empty string.

[`pada`](https://vedicreader.github.io/ganapati/vyakarana.html#pada)
turns a spec into a vidyut `Pada`, its noun stem made by
[`stem`](https://vedicreader.github.io/ganapati/vyakarana.html#stem),
which gives a feminine in -ā or -ī its feminine suffix so sītā declines
as sītāyai, not sītaiḥ, and
[`derive`](https://vedicreader.github.io/ganapati/vyakarana.html#derive)
returns its derivation, a vidyut `Prakriya`. When you also pass the
written word as `surf`,
[`derive`](https://vedicreader.github.io/ganapati/vyakarana.html#derive)
returns only a derivation that ends in that word, or None, so a wrong
spec gives nothing rather than some other word. `derives(spec, surf)`
asks the same question and returns True or False.
[`steps`](https://vedicreader.github.io/ganapati/vyakarana.html#steps)
lists a derivation’s steps as `(source, rule, result)`, each rule at
most once while the form stays the same, with the source `A` for the
Aṣṭādhyāyī, `D` for the Dhātupāṭha, `U` for the Uṇādipāṭha and `V` for a
vārttika.
[`norm`](https://vedicreader.github.io/ganapati/vyakarana.html#norm)
prepares a form for comparison with a derivation by removing accents and
writing a final anusvāra as m.

To describe a spec in words,
[`spec_words`](https://vedicreader.github.io/ganapati/vyakarana.html#spec_words)
gives its grammar in English (given the written form, a doer-voice verb
reads middle when only the ātmanepada endings reach it, as kurute),
[`spec_lemma`](https://vedicreader.github.io/ganapati/vyakarana.html#spec_lemma)
gives the headword it stands on, and
[`spec_root`](https://vedicreader.github.io/ganapati/vyakarana.html#spec_root)
gives the root with its prefixes, such as `pra+Ap`. `KRT` and `GRAM_EN`
hold the English names behind
[`spec_words`](https://vedicreader.github.io/ganapati/vyakarana.html#spec_words).

vidyut is imported inside the functions that need it, and only
`vidyut.prakriya` is loaded, never the kosha or the segmenter.
[`vy`](https://vedicreader.github.io/ganapati/vyakarana.html#vy) holds
the one `Vyakarana` object, set to record every step.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L19"
target="_blank" style="float:right; font-size:smaller">source</a>

### vy

``` python
def vy():
```

*Return the shared vidyut `Vyakarana`, which records each step of a
derivation.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L25"
target="_blank" style="float:right; font-size:smaller">source</a>

### dhatu

``` python
def dhatu(
    f
):
```

*The vidyut `Dhatu` a dhātu field
`upadeśa,gaṇa,antargaṇa,sanādi,prefixes` names: `BU,BvAdi` is bhū.*

``` python
assert dhatu('BU,BvAdi').aupadeshika == 'BU' and _dhatu is dhatu   # the old private name still imports
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L36"
target="_blank" style="float:right; font-size:smaller">source</a>

### stem

``` python
def stem(
    t, g
):
```

*Return the `Pratipadika` of stem `t` in gender `g`, a feminine in -ā or
-ī carrying its feminine suffix (sītā, nadī) so vidyut declines it as
one.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L42"
target="_blank" style="float:right; font-size:smaller">source</a>

### pada

``` python
def pada(
    spec
):
```

*Turn a spec into a vidyut `Pada`, or None if the spec cannot be read.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L57"
target="_blank" style="float:right; font-size:smaller">source</a>

### norm

``` python
def norm(
    slp
):
```

*Prepare an SLP1 form for comparison with a derivation by removing
accents and marks and writing a final anusvāra as m.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L63"
target="_blank" style="float:right; font-size:smaller">source</a>

### derive

``` python
def derive(
    spec, surf:NoneType=None
):
```

*Derive the word a spec names, returning the derivation that ends in
`surf` (SLP1) when given, else the first, or None.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L72"
target="_blank" style="float:right; font-size:smaller">source</a>

### derives

``` python
def derives(
    spec, surf
):
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L75"
target="_blank" style="float:right; font-size:smaller">source</a>

### steps

``` python
def steps(
    p
):
```

*List a derivation’s steps as `(source, rule, result)`, the source being
`A` (Aṣṭādhyāyī), `D` (Dhātupāṭha), `U` (Uṇādipāṭha), `V` (vārttika) or
another source’s own name.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L101"
target="_blank" style="float:right; font-size:smaller">source</a>

### made

``` python
def made(
    f, krt:str=''
):
```

*The stem vidyut builds from dhātu field `f`, alone or with kṛt `krt`,
or an empty string when it cannot.*

``` python
assert made('Ra\\Sa~,divAdi') == 'naS' and made('A\\px~,svAdi,,,pra', 'kta') == 'prApta' and made('BU,nogana') == '' and _made is made
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L110"
target="_blank" style="float:right; font-size:smaller">source</a>

### spec_lemma

``` python
def spec_lemma(
    spec
):
```

*Return the SLP1 headword a spec stands on: the stem, the indeclinable,
a kṛt form’s stem (`prApta`), or a verb’s root with its prefixes
(`pra+Ap`).*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L118"
target="_blank" style="float:right; font-size:smaller">source</a>

### spec_root

``` python
def spec_root(
    spec
):
```

*Return the root of a kṛt form or a verb with its prefixes (`pra+Ap`),
or ’’ for a noun from a plain stem.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L133"
target="_blank" style="float:right; font-size:smaller">source</a>

### spec_words

``` python
def spec_words(
    spec, surf:NoneType=None
):
```

*Describe a spec’s grammar in English words: the kṛt suffix’s meaning if
there is one, then case, number and gender, or tense, person, number and
voice.*

The checks below use two words with well-known derivations. Deriving
रामः applies rules 4.1.2, 8.2.66 and 8.3.15, and deriving विजयते applies
3.4.78, 3.1.68, 7.3.84 and 6.1.78. The spec for rāmaḥ checked against
the written form rāmeṇa gives None.

``` python
def codes(spec, surf): return [c for s, c, _ in steps(derive(spec, surf)) if s == 'A']
assert derive('s|rAma|puM|praTamA|eka', 'rAmaH').text == 'rAmaH'
assert {'4.1.2', '8.2.66', '8.3.15'} <= set(codes('s|rAma|puM|praTamA|eka', 'rAmaH'))
assert {'3.4.78', '3.1.68', '7.3.84', '6.1.78'} <= set(codes('t|ji\\,BvAdi,,,vi|kartari|la~w|praTama|eka', 'vijayate'))
assert derive('s|rAma|puM|praTamA|eka', 'rAmeRa') is None
assert derive('t|BU,BvAdi,,,|kartari|la~w|praTama|eka|P').text == 'Bavati'
assert spec_words('s|rAma|puM|tftIyA|eka') == ['instrumental', 'singular', 'masculine']
assert spec_lemma('k|A\\px~,svAdi,,,pra|kta|puM|dvitIyA|eka') == 'prApta' and spec_root('k|A\\px~,svAdi,,,pra|kta|puM|dvitIyA|eka') == 'pra+Ap'
assert norm('rAmaM') == 'rAmam'
assert derive('s|sItA|strI|caturTI|eka', 'sItAyE') and derive('s|nadI|strI|tftIyA|eka', 'nadyA') and derive('s|lakzmI|strI|praTamA|eka', 'lakzmIH')
kf = 't|qukf\\Y,tanAdi,,,|kartari|la~w|praTama|eka'
assert spec_words(kf, 'kurute')[-1] == 'middle' and spec_words(kf, 'karoti')[-1] == 'active' and spec_words(kf)[-1] == 'active'
cs = [c for _, c, _ in steps(derive('s|rAma|puM|dvitIyA|eka', 'rAmam'))]
assert cs.count('1.1.73') == 1
```

## Placing a word-by-word analysis on the written line

A word-by-word analysis lists a line’s words in reading order with
sandhi undone, so it has more, shorter entries than the line has written
tokens. स कालेनेह is two written tokens and three entries: सः, कालेन, इह.
[`align`](https://vedicreader.github.io/ganapati/vyakarana.html#align)
works out which entries belong to which token. It takes the tokens and a
list of entries, each an object with the word in `.w`, such as the
records from
[`etym_entries`](https://vedicreader.github.io/ganapati/lemma.html#etym_entries),
and returns one list of entry positions per token.

The match keeps the order of both lists. A token may take up to four
consecutive entries, and they fit when their joined sandhi skeleton is
similar enough to the token’s: a difflib similarity ratio of at least
0.72, scored by the token’s length. An entry that no token fits is
dropped at a cost of 0.5. That cost was tuned on Gītā 4.2. Any lower and
कालेनेह loses इह; any higher and विदुः takes a सः that the analysis added
but the line does not have.

[`sandhi_skeleton`](https://vedicreader.github.io/ganapati/vyakarana.html#sandhi_skeleton)
is what makes the comparison tolerant of sandhi. It removes marks, drops
a final s, r, ḥ, m or ṃ, and makes long vowels short, since a word’s
final sound and its vowel length are what sandhi most often changes.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L145"
target="_blank" style="float:right; font-size:smaller">source</a>

### sandhi_skeleton

``` python
def sandhi_skeleton(
    slp
):
```

*Reduce an SLP1 word to a form that survives sandhi: marks removed, a
final s, r, H, m or M dropped, and long vowels made short.*

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L152"
target="_blank" style="float:right; font-size:smaller">source</a>

### align

``` python
def align(
    tk, es
):
```

*Match a line’s written tokens to its word-by-word entries in order,
returning the list of entry positions each token takes.*

``` python
from collections import namedtuple
```

``` python
E = namedtuple('E', 'w')
tk = 'स कालेनेह महता योगो नष्टः परन्तप'.split()
es = [E(x) for x in ('सः', 'कालेन', 'इह', 'महता', 'योगः', 'नष्टः', 'परन्तप')]
assert align(tk, es) == [[0], [1, 2], [3], [4], [5], [6]]
assert sandhi_skeleton('rAmaH') == 'rama'
```

## The sandhi at a join

Sandhi is the set of sound changes where two words meet: ca and eva are
written caiva, and rāmaḥ before gacchati becomes rāmo.
[`join_rule`](https://vedicreader.github.io/ganapati/vyakarana.html#join_rule)
takes the word before the join and the word after, both in SLP1 with
sandhi undone, and names the rule that joins them. It returns
`(id, name, sūtra)` from `JOINS`, such as
`('vrddhi', 'vṛddhi', '6.1.88')`, or None for a join that no class fits.
`JOINS` describes 25 classes that cover the joins of classical verse,
each with its Aṣṭādhyāyī sūtra and a one-line description, plus a `none`
entry for words written apart.

Pass the written form as `written` when you have it. Two pairs of rules
can only be told apart that way: whether the y or v of ayādi was kept or
dropped (te ime written tayime or ta ime), and whether t before ś became
cch (tacchrutvā) or only c.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L226"
target="_blank" style="float:right; font-size:smaller">source</a>

### join_rule

``` python
def join_rule(
    left, right, written:str=''
):
```

*Name the sandhi between `left` and `right` (both SLP1, sandhi undone)
as `(id, name, sūtra)` from `JOINS`, using the `written` form when
given, or None if no class fits.*

``` python
for l, r, w, k in [('BIma', 'arjuna', 'BImArjuna', '6.1.101'), ('mahA', 'izvAsAH', 'mahezvAsAH', '6.1.87'), ('ca', 'eva', 'cEva', '6.1.88'),
                   ('karmaRi', 'eva', 'karmaRyeva', '6.1.77'), ('karO', 'uBO', 'karAvuBO', '6.1.78'), ('te', 'api', "te'pi", '6.1.109'),
                   ('te', 'ime', 'ta ime', '8.3.19'), ('rAmaH', 'gacCati', 'rAmo gacCati', '6.1.114'), ('rAmaH', 'api', "rAmo'pi", '6.1.113'),
                   ('hariH', 'gacCati', 'harir gacCati', '8.2.66'), ('tam', 'vande', 'taM vande', '8.3.23'), ('vAk', 'ISa', 'vAgISa', '8.2.39'), ('tat', 'ca', 'tacca', '8.4.40')]:
    assert join_rule(l, r, w)[2] == k, (l, r, join_rule(l, r, w))
assert join_rule('', 'iti') is None
```

## Joining two words

[`join_rule`](https://vedicreader.github.io/ganapati/vyakarana.html#join_rule)
names a join that is already written.
[`join`](https://vedicreader.github.io/ganapati/vyakarana.html#join)
writes it. Give it two words in SLP1 as each stands alone and it returns
the left word as written, the right word as written, and the class from
[`join_rule`](https://vedicreader.github.io/ganapati/vyakarana.html#join_rule).
When the two words merge into one, as tava and iti do in taveti, the
right word comes back as None.

The joins come from vidyut’s table of 1,468 sandhi rules
(`ganapati/data/vidyut_sandhi_rules.csv`, MIT licence, in
`ganapati/data/LICENSE-vidyut`). vidyut’s Python binding has a splitter
but no joiner, so ganapati reads the table itself. A final visarga is
read as s, or as r in punar, antar and prātar, and saḥ and eṣaḥ drop
their visarga before a consonant by 6.1.132.

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L271"
target="_blank" style="float:right; font-size:smaller">source</a>

### rules

``` python
def rules(
    p:NoneType=None
):
```

*vidyut’s joins from `p` (default `RULES`) as
`{(left end, right first sound): result}`, a space in the result keeping
the words apart.*

``` python
len(rules()), rules()[('a', 'i')], rules()[('as', 'a')]
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L280"
target="_blank" style="float:right; font-size:smaller">source</a>

### join

``` python
def join(
    left, right
):
```

*Join `left` and `right` (SLP1, each as it stands alone) as
`(left written, right written or None if they merge, class from join_rule or None)`.*

``` python
assert join('rAmaH', 'api') == ('rAmo', "'pi", ('o_a', '-aḥ to -o', '6.1.113'))
assert join('tava', 'iti') == ('taveti', None, ('guna', 'guṇa', '6.1.87'))
assert join('saH', 'gacCati')[:2] == ('sa', 'gacCati')
assert join('punaH', 'api')[0] == 'punar'
assert join('', 'iti') == ('', 'iti', None)
join('ca', 'eva'), join('tat', 'ca')
```

------------------------------------------------------------------------

<a
href="https://github.com/vedicreader/ganapati/blob/main/ganapati/vyakarana.py#L296"
target="_blank" style="float:right; font-size:smaller">source</a>

### samhita

``` python
def samhita(
    ws
):
```

*Words in order (SLP1) as one saṃhitā line:
`(line, [(k, class of the join between word k and k+1)])`, merged words
written as one.*

``` python
line, js = samhita(['rAmaH', 'api', 'vanam', 'gacCati'])
assert line == "rAmo 'pi vanaM gacCati", line
assert [j[2] if j else None for _, j in js] == ['6.1.113', None, '8.3.23']
assert samhita([]) == ('', [])
line, js
```
