← 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")