← domain #7
Source : desi_raga_ela.py
data/library/books/brihaddesi_sharma_1992/formal_grammar/desi_raga_ela.py · 794 lines · 29148 bytes
"""Domaine 7 — Elā / deśī rasa / individual deśī rāgas
Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II) depuis :
- 33 règles génératives 6b (cluster_id ∈ domain_id=7)
- 53 affirmations sourcées
- partition Leiden domain_id=7 (50 concepts)
Périmètre : la forme-chant Elā (distincte du prabandha générique), ses dhātus
structuraux, les rāgas deśī nommés (bhadrāvatī, nandāvatī, sāttvatī, vārāhī…),
les associations rasa des mélodies deśī, les regroupements gaṇa, et les
catégorisations āyurvédiques de la voix (vāta / pitta / kapha / sannipāta).
Anti-fabrication : chaque type, opération et contrainte cite son evidence
(rule_id 6b ou affirmation_id). Aucune valeur non sourcée n'est introduite.
Les zones où la source nomme un concept sans en pinner la spécification
sont listées dans UNRESOLVED — pas comblées par plausibilité.
Python 3.10+. Importable directement, sans dépendance externe.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Iterable
# =============================================================================
# CONSTANTES — énumérations fermées du Brihaddesi (vol. II Elā chapter)
# =============================================================================
class DhātuSense(str, Enum):
"""The two textual senses of `dhātu` in Brihaddesi.
evidence: R_dhatu_polysemy (aff#1445, aff#3063), R_123_dhatus_definition
"""
MUSIC = "music" # tonal-cum-rhythmic structure of a composition
AYURVEDA = "ayurveda" # bodily constituent (3- or 7-fold list)
class Humour(str, Enum):
"""The three Ayurvedic humours, mapped 1:1 to a voice category in the
Brihaddesi voice typology.
evidence: aff#2944 (vata/pitta/kapha = three dhātus),
sannipata_humour_admixture_fourfold_voice (aff#2951)
"""
VATA = "vata" # air — dynamic aspect
PITTA = "pitta" # bile — heat/energy (R_2053_pitta_dhatu)
KAPHA = "kapha" # phlegm — static (R_5p3_2054 / aff#2943)
class VoiceCategory(str, Enum):
"""Fourfold voice classification by humour predominance.
evidence: sannipata_humour_admixture_fourfold_voice (aff#2951),
R_1637_kaphaja_voice (aff#2147)
"""
VATAJA = "vataja"
PITTAJA = "pittaja"
KAPHAJA = "kaphaja"
SANNIPATA = "sannipata" # admixture of the three humours
class Rasa(str, Enum):
"""The eight rasas enumerated by Mātaṅga.
Note: Mātaṅga's count is 8 (Śānta included, Bhayānaka omitted) — this is
explicitly marked as a deviation from the classical 8-or-9 list elsewhere.
The names labelled `MATANGA_LISTED_OTHER` reflect the affirmation's own
truncated enumeration ("Paroksa, Sringara, Bibhatsa, Hasya, Karuna,
Nisada, Santa, …" — the source quote does not list all 8 explicitly).
evidence: r_brd_363_rasa_gandhari_and_count (aff#2822 enumeration_8),
aff#418 (Gāndhārī carries karuṇa), aff#840 (yuddhavīra→vīra),
aff#1229 (karuṇa↔vegavantī), aff#1922 ("mature karuṇa")
"""
SRINGARA = "sringara"
HASYA = "hasya"
KARUNA = "karuna"
VIRA = "vira"
BIBHATSA = "bibhatsa"
SANTA = "santa" # included by Mātaṅga (aff#2822)
PAROKSA = "paroksa" # listed in aff#2822 quote
NISADA = "nisada" # listed in aff#2822 quote
# Bhayānaka deliberately OMITTED — aff#2822
# Mātaṅga's eight-rasa enumeration. The exact composition beyond the 6 names
# quoted in aff#2822 is UNRESOLVED at the level of the affirmation text.
# evidence: r_brd_363_rasa_gandhari_and_count, aff#2822
RASA_COUNT_MATANGA: int = 8
RASA_OMITTED_BY_MATANGA: tuple[str, ...] = ("bhayānaka",) # aff#2822
class Vṛtti(str, Enum):
"""Dramatic vṛttis (modes/styles) referenced in elā chapters.
The Brihaddesi text in this domain pins only one of the four — sāttvatī —
by full definition (sattvati_vrtti_definition). The others are named in
tradition but no rule isolates them here → see UNRESOLVED.
evidence: sattvati_vrtti_definition (aff#1989), aff#1427
"""
SATTVATI = "sattvati"
# bhāratī / kaiśikī / ārabhaṭī : 4-vṛtti list closed by aff#1989 ("the
# four vṛttis of drama") but individual rules absent in domain_7 rule
# set → UNRESOLVED
class Rīti(str, Enum):
"""Diction styles named in elā assignments.
evidence: R_1164_gaudiya_riti_nandavati (aff#1428),
vaidarbhi_riti_in_bhadravati (aff#1431)
"""
GAUDIYA = "gaudiya"
VAIDARBHI = "vaidarbhi"
# pāñcālī / lāṭī : standard 4-rīti list per tradition, but not pinned by
# rules in this domain — see UNRESOLVED
class Caste(str, Enum):
"""Caste lineage assignments for elā varieties.
evidence: bhadravati_caste_colour (aff#1437, śūdra),
R_c453_nandavati (aff#1425, vaiśya — `vaiśyānāṁ kulasambhavā`)
"""
SHUDRA = "shudra"
VAISYA = "vaisya"
# brāhmaṇa / kṣatriya not assigned to any elā in domain_7 rule set
# =============================================================================
# CONSTANTES — listes nominales fermées (closed enumerations)
# =============================================================================
# Bodily dhātus, 7-fold enumeration (Suśruta lineage cited by Mātaṅga).
# evidence: aff#2318 (sapta dhātavaḥ Skt enumeration),
# aff#2319 (citation of Suśruta), aff#3063 (Monier-Williams gloss)
DHATUS_BODY_SEVEN: tuple[str, ...] = (
"rasa", # chyle / serum (tvak in Suśruta variant — aff#3062)
"asṛk", # blood
"māṁsa", # flesh
"medas", # fat
"asthi", # bone
"majjā", # marrow
"śukra", # semen
)
# Ayurvedic three-humour list (also called dhātus per aff#2944).
# evidence: aff#2944, R_123_dhatus_definition
DHATUS_HUMOUR_THREE: tuple[Humour, ...] = (Humour.VATA, Humour.PITTA, Humour.KAPHA)
# Voice categories — fourfold per sannipāta admixture.
# evidence: sannipata_humour_admixture_fourfold_voice (aff#2951)
VOICE_CATEGORIES_FOURFOLD: tuple[VoiceCategory, ...] = (
VoiceCategory.VATAJA,
VoiceCategory.PITTAJA,
VoiceCategory.KAPHAJA,
VoiceCategory.SANNIPATA,
)
# Five regional varieties of deśailā.
# evidence: desaila_five_regional_varieties (aff#81), R_1222_01 (aff#1498)
DESAILA_REGIONAL_VARIETIES: tuple[str, ...] = (
"lāṭailā", # Lāṭa region
"karṇāṭailā", # Karṇāṭa region
"gauḍailā", # Gauḍa region
"āndhrailā", # Āndhra region
"drāviḍailā", # Drāviḍa region
)
# Gaṇa varieties — closed 7-fold enumeration by svara-count and jāti-count.
# evidence: R_727_gana_definition (aff#266, aff#268, aff#275)
# Each entry: (svara_count, n_jatis_within_variety)
GANA_VARIETIES: tuple[tuple[int, int], ...] = (
(1, 3), # eka-svara × 3 jātis
(2, 3), # dvi-svara × 3 jātis
(3, 3), # tri-svara × 3 jātis
(4, 3), # catuḥ-svara × 3 jātis
(5, 4), # pañca-svara × 4 jātis
(6, 1), # ṣaṭ-svara × 1
(7, 1), # sapta-svara × 1
)
# Anuprāsa placement options for the canonical Elā form.
# evidence: R_c1149_ela (aff#1402) — body lists three alternative placements
ANUPRASA_PLACEMENTS: tuple[str, ...] = (
"feet_1_2",
"feet_3_or_4",
"foot_5",
)
# =============================================================================
# TYPES — dataclasses
# =============================================================================
@dataclass(frozen=True)
class Dhātu:
"""A dhātu in the Brihaddesi sense.
Polysemous: in MUSIC sense it is the tonal-cum-rhythmic skeleton of a
composition, paired with `mātu` (text). In AYURVEDA sense it is a
bodily constituent — either of the 3-humour or 7-substance list.
evidence: R_dhatu_polysemy (aff#1445 music sense, aff#3063 7-body sense,
aff#2944 3-humour sense), R_461_matu (aff#1446 dhātu↔mātu pair)
"""
sense: DhātuSense
name: str
def __post_init__(self) -> None:
if self.sense == DhātuSense.AYURVEDA:
if (
self.name not in DHATUS_BODY_SEVEN
and self.name not in {h.value for h in Humour}
):
raise ValueError(
f"Ayurvedic dhātu name {self.name!r} not in 7-body list "
f"(R_123_dhatus_definition, aff#2318) "
f"nor in 3-humour list (aff#2944)"
)
# MUSIC sense: name is a free composition section label, no closed list
@dataclass(frozen=True)
class Mātu:
"""The text component of a composition, paired with Dhātu.
evidence: R_461_matu (aff#1446)
"""
text: str # actual lyric content; opaque token here
@dataclass(frozen=True)
class GaṇaVariety:
"""A gaṇa variety: a group-class indexed by svara count and jāti count.
A gaṇa is a group whose maximum jāti-count is 3 in the general case
(aff#266: `triṣātistu gaṇaḥ smṛtaḥ`), with the qualification that this
is an indication of the maximum (aff#275). Specific gaṇas at 5 svaras
rise to 4 jātis (R_727_gana_definition).
evidence: R_727_gana_definition (aff#266, aff#268, aff#275)
"""
svara_count: int
n_jatis_in_variety: int
def __post_init__(self) -> None:
if (self.svara_count, self.n_jatis_in_variety) not in GANA_VARIETIES:
raise ValueError(
f"({self.svara_count}, {self.n_jatis_in_variety}) is not a "
f"canonical gaṇa variety — R_727_gana_definition allows only "
f"{GANA_VARIETIES}"
)
@dataclass(frozen=True)
class ElāProfile:
"""Specification of an individual named Elā variety.
Fields populated only when a domain_7 rule pins the value. Unsourced
slots stay None — NEVER guessed.
Canonical Elā form definition (R_c1149_ela, aff#1402):
- 4 gītis per foot
- anuprāsa placed in feet 1-2 OR feet 3-or-4 OR foot 5
- combined with pratitāla
- thematic register: renunciation, well-being, beauty, valour,
steadfastness
- marked with name of object-of-description and composer name
Individual elās below may specialise / override these defaults.
evidence: see per-instance evidence in ELA_PROFILES below.
"""
name: str
parent_rāga: str | None = None
tāla: str | None = None
rasa: Rasa | None = None
vṛtti: Vṛtti | None = None
rīti: Rīti | None = None
devatā: str | None = None
caste: Caste | None = None
colour: str | None = None
n_feet: int | None = None
gaṇa_structure: tuple[tuple[str, int], ...] = ()
ornamentation: str | None = None
notes: tuple[str, ...] = ()
# Per-Elā evidence is given inline as a constructor argument; the dict below
# is keyed by canonical name. Only slots that a rule/affirmation in domain_7
# pins explicitly are set; everything else is None (= UNRESOLVED for that
# slot, not "default").
ELA_PROFILES: dict[str, ElāProfile] = {
# bhadrāvatī — R_c454_bhadravati composite (bhadravati_ela_structural_features,
# bhadravati_caste_colour, vaidarbhi_riti_in_bhadravati, R_458_varahi)
# evidence: aff#1429 (pada sequence + mūrchanā), aff#1430 (kaṅkāla tāla +
# kakubha rāga), aff#1434 (5 bhūmi-gaṇas + 1 jala), aff#1437 (śūdra,
# black), aff#1431 (vaidarbhī rīti), aff#1433/aff#1994 (vārāhī devatā)
"bhadrāvatī": ElāProfile(
name="bhadrāvatī",
parent_rāga="kakubha",
tāla="kaṅkāla",
rīti=Rīti.VAIDARBHI,
devatā="vārāhī",
caste=Caste.SHUDRA,
colour="black",
gaṇa_structure=(("bhūmi", 5), ("jala", 1)),
notes=(
"padas one by one (aff#1429)",
"brimming with mūrchanā sounds (aff#1429)",
),
),
# nandāvatī — R_c453_nandavati composite
# evidence: aff#1424 (mālavakaiśika rāga + pratitāla + small gamakas),
# aff#1425 (rasa=vīra, vaiśya lineage), aff#1436 (5 ambara + mārtaṇḍa),
# aff#1427 (vṛtti=sāttvatī), aff#1428 (rīti=gauḍīyā),
# R_1163_indrani_nandavati_deity (aff#1426 devatā=indrāṇī, conf=0.7)
"nandāvatī": ElāProfile(
name="nandāvatī",
parent_rāga="mālavakaiśika",
tāla="pratitāla",
rasa=Rasa.VIRA,
vṛtti=Vṛtti.SATTVATI,
rīti=Rīti.GAUDIYA,
devatā="indrāṇī",
caste=Caste.VAISYA,
gaṇa_structure=(("ambara", 5), ("mārtaṇḍa", 1)),
ornamentation="small_gamakas",
),
# haṁsāvatī — hamsavati_ela_gana_structure (aff#1423)
"hamsāvatī": ElāProfile(
name="hamsāvatī",
gaṇa_structure=(("analaga", 5), ("vāyu", 1)),
),
# mālatī — r_brd_466_malati_def (aff#1464) + R_467_malati (aff#1478)
"mālatī": ElāProfile(
name="mālatī",
n_feet=4,
notes=(
"short+long syllables (aff#1464, aff#1478)",
"alliterations required (aff#1478)",
"sweet sounds, straight form, profuse gamakas+alaṅkāras (aff#1464)",
"variable length (aff#1478)",
),
),
# padminī — R_5p2_1199 (aff#1476)
"padminī": ElāProfile(
name="padminī",
n_feet=4,
notes=("varṇadhvani in each of the 4 feet (aff#1476)",),
),
# drāviḍailā — R_4p1_324 (aff#1493) + dravidaila_no_prasa_in_rasa (aff#1494)
"drāviḍailā": ElāProfile(
name="drāviḍailā",
notes=(
"born in Drāviḍa region (aff#1493)",
"adorned with bhāva, abhinaya, tāla (aff#1493)",
"devoid of prāsa, established in rasa (aff#1494)",
),
),
}
@dataclass(frozen=True)
class Vibhāṣā:
"""A vibhāṣā is a melodic form below the rāga level whose amśa svara and
rasa are explicitly pinned.
Modelled here only for vegavantī, the sole domain_7 instance with full
spec (completeness, aṁśa, rasa, bhāva).
evidence: r_brd_1043_vegavanti_structure (aff#1228 amśa+sampurna,
aff#1229 rasa=karuṇa+dainya), karuna_rasa_vegavanti_bhasa
"""
name: str
completeness: str # 'sampurna' for vegavantī
amśa: str # svara name as written in the source
rasa: Rasa
bhāva: str | None = None
VEGAVANTI: Vibhāṣā = Vibhāṣā(
name="vegavantī",
completeness="sampurna",
amśa="pañcama",
rasa=Rasa.KARUNA,
bhāva="profuse_dainya",
)
@dataclass(frozen=True)
class Śukasārikā:
"""The śukasārikā composition form: verse OR prose, dual tāla,
question-answer phrase pairs, adaptable to any rasa.
evidence: R_1360_shukasarika (aff#1788 form/tāla/Q-A, aff#1789 any rasa)
"""
form: str # 'gadya' or 'padya' (aff#1788)
tala_count: int = 2 # dual tāla — aff#1788
phrases: str = "question_answer_pairs"
rasa: Rasa | None = None # any rasa — aff#1789
def __post_init__(self) -> None:
if self.form not in {"gadya", "padya"}:
raise ValueError(
f"śukasārikā form must be 'gadya' or 'padya' "
f"(R_1360_shukasarika / aff#1788), got {self.form!r}"
)
if self.tala_count != 2:
raise ValueError(
f"śukasārikā uses dual tāla (tāla_count=2) per aff#1788, "
f"got {self.tala_count}"
)
# =============================================================================
# OPÉRATIONS — dérivations exécutables
# =============================================================================
def get_ela_profile(name: str) -> ElāProfile | None:
"""Lookup a named Elā variety profile.
evidence: ELA_PROFILES dict (per-entry evidence inline)
Returns None if the name is not sourced in domain_7 rule set.
"""
return ELA_PROFILES.get(name)
def all_ela_names() -> tuple[str, ...]:
"""Enumerate the Elā varieties pinned by domain_7 rules.
evidence: ELA_PROFILES dict (each entry sources ≥1 affirmation)
"""
return tuple(ELA_PROFILES.keys())
def is_desaila_region(name: str) -> bool:
"""True iff `name` is one of the 5 regional deśailā varieties.
evidence: desaila_five_regional_varieties (aff#81, aff#1498), R_1222_01
"""
return name in DESAILA_REGIONAL_VARIETIES
def gana_jati_count(svara_count: int) -> int:
"""Return the number of jātis for a gaṇa variety of given svara count.
Raises if svara_count is not a canonical gaṇa size (1..7).
evidence: R_727_gana_definition (aff#268 enumeration)
"""
for sv, nj in GANA_VARIETIES:
if sv == svara_count:
return nj
raise ValueError(
f"svara_count={svara_count} not in canonical gaṇa enumeration "
f"(R_727_gana_definition: {[sv for sv, _ in GANA_VARIETIES]})"
)
def voice_from_humour(predominant: Humour | None) -> VoiceCategory:
"""Map a predominant humour to its voice category, or SANNIPĀTA if
none predominates (admixture of all three).
evidence: sannipata_humour_admixture_fourfold_voice (aff#2951),
R_1637_kaphaja_voice (aff#2147)
"""
if predominant is None:
return VoiceCategory.SANNIPATA
return {
Humour.VATA: VoiceCategory.VATAJA,
Humour.PITTA: VoiceCategory.PITTAJA,
Humour.KAPHA: VoiceCategory.KAPHAJA,
}[predominant]
def rasa_of_entity(entity_name: str) -> Rasa | None:
"""Return the rasa explicitly assigned to a named entity in domain_7.
Closed lookup over rules that pin a rasa assignment.
Returns None if no rule pins it.
evidence: r_brd_363_rasa_gandhari_and_count (aff#418 Gāndhārī→karuṇa),
R_c453_nandavati (aff#1425 nandāvatī→vīra),
r_brd_1043_vegavanti_structure (aff#1229 vegavantī→karuṇa),
karuna_rasa_vegavanti_bhasa, R_904_yuddhavira_rasa (aff#840)
"""
table: dict[str, Rasa] = {
"gāndhārī": Rasa.KARUNA, # aff#418
"nandāvatī": Rasa.VIRA, # aff#1425
"vegavantī": Rasa.KARUNA, # aff#1229
"yuddhavīra": Rasa.VIRA, # aff#840 (vīra-class viniyoga)
}
return table.get(entity_name)
# =============================================================================
# CONTRAINTES — validations
# =============================================================================
def is_valid_ela_anuprasa_placement(placement: str) -> bool:
"""An Elā places anuprāsa in feet 1-2, feet 3-or-4, OR foot 5.
evidence: R_c1149_ela (aff#1402)
"""
return placement in ANUPRASA_PLACEMENTS
def is_valid_ela_tala(profile: ElāProfile) -> bool:
"""Canonical Elā form combines with pratitāla (R_c1149_ela, aff#1407).
Specific Elās may override with their own tāla (e.g., bhadrāvatī uses
kaṅkāla — aff#1430). This check returns True if either the profile
sources its own tāla or implicitly inherits pratitāla.
evidence: aff#1407 (pratitāla default), aff#1430 (kaṅkāla override),
R_c453_nandavati (pratitāla retained for nandāvatī)
"""
if profile.tāla is None:
return True # inherits pratitāla default; not invalidated
return isinstance(profile.tāla, str) and len(profile.tāla) > 0
def is_canonical_gana_variety(svara_count: int, n_jatis: int) -> bool:
"""True iff (svara_count, n_jatis) appears in the 7-variety table.
evidence: R_727_gana_definition (aff#268)
"""
return (svara_count, n_jatis) in GANA_VARIETIES
def is_valid_desaila_count(count: int) -> bool:
"""Deśailā has exactly 5 regional varieties.
evidence: desaila_five_regional_varieties (aff#81), R_1222_01 (aff#1498)
"""
return count == len(DESAILA_REGIONAL_VARIETIES)
def gana_total_jati_count() -> int:
"""Sum of jātis across all 7 gaṇa varieties (3+3+3+3+4+1+1 = 18).
evidence: R_727_gana_definition (aff#268) — derived sum.
"""
return sum(nj for _, nj in GANA_VARIETIES)
def is_valid_matanga_rasa_count(n: int) -> bool:
"""Mātaṅga enumerates exactly 8 rasas (Śānta included, Bhayānaka omitted).
evidence: r_brd_363_rasa_gandhari_and_count (aff#2822)
"""
return n == RASA_COUNT_MATANGA
# =============================================================================
# UNRESOLVED — concepts/slots mentioned but not formally pinned by 6b rules
# =============================================================================
# Listed here for traceability; consumed by the JSON manifest.
UNRESOLVED: tuple[dict[str, str], ...] = (
{
"concept": "three vṛttis other than sāttvatī (bhāratī, kaiśikī, ārabhaṭī)",
"reason": (
"aff#1989 closes the count at 4 ('among the four vṛttis') but no "
"domain_7 rule isolates the other three by definition."
),
},
{
"concept": "rītis other than gauḍīyā and vaidarbhī",
"reason": (
"the standard 4-rīti list (pāñcālī, lāṭī) is referenced in "
"tradition but no domain_7 rule pins them."
),
},
{
"concept": "Mātaṅga rasa list — exact composition of the 8",
"reason": (
"aff#2822 names only 6 of the 8 in its quoted enumeration "
"(Paroksa, Sringara, Bibhatsa, Hasya, Karuna, Nisada, Santa, …); "
"the 8th member is not visible in the affirmation text. "
"RASA enum lists candidates traced; identity of the 8th is "
"underdetermined here."
),
},
{
"concept": "individual specs for lāṭailā / karṇāṭailā / gauḍailā / āndhrailā",
"reason": (
"aff#81 and aff#1498 enumerate the 5 regional deśailās; only "
"drāviḍailā is pinned individually by R_4p1_324 (aff#1493) and "
"dravidaila_no_prasa_in_rasa (aff#1494). The other 4 are named "
"but not specified at the slot level."
),
},
{
"concept": "Elā varieties beyond the 6 modelled (gaudailā, lāṭailā, etc.)",
"reason": (
"the source enumerates further regional elās without pinning "
"their structural slots in the domain_7 rule set."
),
},
{
"concept": "devatā assignment confidence — indrāṇī ↔ nandāvatī",
"reason": (
"R_1163_indrani_nandavati_deity carries confidence=0.7 (lowest "
"among elā-devatā rules); kept in ELA_PROFILES but flagged."
),
},
{
"concept": "shukasārikā ↔ rasa: 'any rasa'",
"reason": (
"R_1360_shukasarika (aff#1789) explicitly states rasa is "
"unconstrained ('any desired rasa') — modelled as Optional[Rasa]; "
"this is sourced openness, not a missing fact."
),
},
{
"concept": "kaste (caste) assignments for haṁsāvatī, mālatī, padminī",
"reason": (
"Caste is pinned only for bhadrāvatī (śūdra, aff#1437) and "
"nandāvatī (vaiśya, aff#1425). Other Elās have no caste rule."
),
},
)
# =============================================================================
# Self-test (executed at import-time only if __main__)
# =============================================================================
if __name__ == "__main__":
# --- Sanity: Dhātu polysemy ---
d_music = Dhātu(sense=DhātuSense.MUSIC, name="udgrāha")
assert d_music.sense == DhātuSense.MUSIC
d_body = Dhātu(sense=DhātuSense.AYURVEDA, name="rasa")
assert d_body.name in DHATUS_BODY_SEVEN
d_humour = Dhātu(sense=DhātuSense.AYURVEDA, name="vata")
assert d_humour.name in {h.value for h in Humour}
try:
Dhātu(sense=DhātuSense.AYURVEDA, name="fabricated_dhatu")
except ValueError:
pass
else:
raise AssertionError("Ayurvedic dhātu name not validated")
# --- Sanity: dhātus body list has exactly 7 entries ---
assert len(DHATUS_BODY_SEVEN) == 7, (
f"R_123_dhatus_definition / aff#2318 expects 7 bodily dhātus, "
f"got {len(DHATUS_BODY_SEVEN)}"
)
# --- Sanity: humour triad has exactly 3 entries ---
assert len(DHATUS_HUMOUR_THREE) == 3, "aff#2944 expects 3 humours"
assert set(DHATUS_HUMOUR_THREE) == set(Humour), "humour list must equal enum"
# --- Sanity: voice categories fourfold per sannipāta admixture ---
assert len(VOICE_CATEGORIES_FOURFOLD) == 4
assert voice_from_humour(Humour.KAPHA) == VoiceCategory.KAPHAJA
assert voice_from_humour(None) == VoiceCategory.SANNIPATA
# --- Sanity: gaṇa varieties — exactly 7, totalling 18 jātis ---
assert len(GANA_VARIETIES) == 7, "R_727_gana_definition expects 7 varieties"
assert gana_total_jati_count() == 18, (
"R_727_gana_definition sums to 3+3+3+3+4+1+1=18"
)
assert gana_jati_count(5) == 4 # pañca-svara has 4 jātis (aff#268)
assert gana_jati_count(6) == 1
assert is_canonical_gana_variety(4, 3)
assert not is_canonical_gana_variety(4, 5)
try:
gana_jati_count(8)
except ValueError:
pass
else:
raise AssertionError("gana_jati_count(8) should raise")
# --- Sanity: GaṇaVariety dataclass validation ---
GaṇaVariety(svara_count=5, n_jatis_in_variety=4)
try:
GaṇaVariety(svara_count=2, n_jatis_in_variety=99)
except ValueError:
pass
else:
raise AssertionError("GaṇaVariety should reject non-canonical pair")
# --- Sanity: deśailā ---
assert is_valid_desaila_count(5)
assert not is_valid_desaila_count(4)
assert is_desaila_region("drāviḍailā")
assert not is_desaila_region("bhadrāvatī")
assert len(DESAILA_REGIONAL_VARIETIES) == 5
# --- Sanity: Elā profiles ---
bv = get_ela_profile("bhadrāvatī")
assert bv is not None
assert bv.parent_rāga == "kakubha" # aff#1430
assert bv.tāla == "kaṅkāla" # aff#1430
assert bv.rīti == Rīti.VAIDARBHI # aff#1431
assert bv.devatā == "vārāhī" # aff#1433
assert bv.caste == Caste.SHUDRA # aff#1437
assert bv.colour == "black" # aff#1437
assert bv.gaṇa_structure == (("bhūmi", 5), ("jala", 1)) # aff#1434
nv = get_ela_profile("nandāvatī")
assert nv is not None
assert nv.parent_rāga == "mālavakaiśika" # aff#1424
assert nv.tāla == "pratitāla" # aff#1424
assert nv.rasa == Rasa.VIRA # aff#1425
assert nv.caste == Caste.VAISYA # aff#1425
assert nv.vṛtti == Vṛtti.SATTVATI # aff#1427
assert nv.rīti == Rīti.GAUDIYA # aff#1428
assert nv.devatā == "indrāṇī" # aff#1426 (conf=0.7)
assert nv.gaṇa_structure == (("ambara", 5), ("mārtaṇḍa", 1)) # aff#1436
hv = get_ela_profile("hamsāvatī")
assert hv is not None
assert hv.gaṇa_structure == (("analaga", 5), ("vāyu", 1)) # aff#1423
pad = get_ela_profile("padminī")
assert pad is not None and pad.n_feet == 4 # aff#1476
mal = get_ela_profile("mālatī")
assert mal is not None and mal.n_feet == 4 # aff#1478
dra = get_ela_profile("drāviḍailā")
assert dra is not None
# aff#1494 says drāviḍailā devoid of prāsa; note recorded
assert any("devoid of prāsa" in n for n in dra.notes)
assert get_ela_profile("fabricated_ela") is None
assert len(all_ela_names()) == 6 # the 6 pinned by domain_7 rules
# --- Sanity: anuprāsa placement (R_c1149_ela / aff#1402) ---
assert is_valid_ela_anuprasa_placement("feet_1_2")
assert is_valid_ela_anuprasa_placement("foot_5")
assert not is_valid_ela_anuprasa_placement("foot_99")
assert len(ANUPRASA_PLACEMENTS) == 3
# --- Sanity: tāla validity ---
assert is_valid_ela_tala(ELA_PROFILES["bhadrāvatī"])
assert is_valid_ela_tala(ELA_PROFILES["hamsāvatī"]) # tāla=None → inherits
# --- Sanity: rasa lookups ---
assert rasa_of_entity("nandāvatī") == Rasa.VIRA # aff#1425
assert rasa_of_entity("vegavantī") == Rasa.KARUNA # aff#1229
assert rasa_of_entity("gāndhārī") == Rasa.KARUNA # aff#418
assert rasa_of_entity("yuddhavīra") == Rasa.VIRA # aff#840
assert rasa_of_entity("fabricated_entity") is None
assert is_valid_matanga_rasa_count(8) # aff#2822
assert not is_valid_matanga_rasa_count(9)
assert "bhayānaka" in RASA_OMITTED_BY_MATANGA
# --- Sanity: Vibhāṣā / vegavantī ---
assert VEGAVANTI.amśa == "pañcama" # aff#1228
assert VEGAVANTI.rasa == Rasa.KARUNA # aff#1229
assert VEGAVANTI.completeness == "sampurna" # aff#1228
assert VEGAVANTI.bhāva == "profuse_dainya" # aff#1229
# --- Sanity: Śukasārikā ---
sk = Śukasārikā(form="padya", rasa=Rasa.SRINGARA) # any rasa OK (aff#1789)
assert sk.tala_count == 2 # aff#1788
sk2 = Śukasārikā(form="gadya") # rasa unspecified OK
assert sk2.rasa is None
try:
Śukasārikā(form="fabricated_form")
except ValueError:
pass
else:
raise AssertionError("Śukasārikā form not validated")
try:
Śukasārikā(form="padya", tala_count=3)
except ValueError:
pass
else:
raise AssertionError("Śukasārikā dual-tāla constraint not enforced")
# --- Sanity: unresolved list non-empty (honest reporting) ---
assert len(UNRESOLVED) >= 5
print("desi_raga_ela.py — all self-tests pass")