← domain #5

Source : tala_prabandha.py

data/library/books/brihaddesi_sharma_1992/formal_grammar/tala_prabandha.py · 816 lines · 31135 bytes
"""Domaine 5 — tāla, prabandha, rhythmic compositions Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II) depuis : - 100 règles génératives 6b (cluster_id ∈ domain_id=5) - 172 affirmations sourcées - 91 concepts, 73 avec règles Domaine = formes de chant (prabandhas), leurs composants structurels (birudas, pāṭas, tenakas, padas, svaras), distinction gandharva/gāna, gamakas au niveau rythmique, kanda comme structure sectionnelle, cycles tāla. Anti-fabrication stricte : chaque type, constante, opération et contrainte cite ≥1 evidence (rule_id ou affirmation_id). Tensions épistémiques (ex. 7 vs 14 varnailā) préservées explicitement, jamais arbitrées par effacement. 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, Literal # ============================================================================= # CONSTANTES — énumérations fermées du Brihaddesi # ============================================================================= class Kala(str, Enum): """Time-division magnification of a tāla. Caccatpuṭa (and other tālas) operate in three kala-grades: ekakala (×1), dvikala (×2), catuṣkala (×4). Each kala pairs with a mārga. evidence: R_40_caccatputa_kala_marga (aff#393), R_gandharva_def (5 tālas × 3 mārgas) """ EKAKALA = "ekakala" DVIKALA = "dvikala" CATUSKALA = "catuskala" class Marga(str, Enum): """The three mārgas (tempo/style classes) of the gāndharva system, paired one-per-kala on caccatpuṭa-type tālas. evidence: R_gandharva_def (aff#3155: "Each of the five tālas of gāndharva have three mārgas"), R_40_caccatputa_kala_marga (aff#393) """ CITRA = "citra" # paired with ekakala VARTIKA = "vartika" # paired with dvikala DAKSINA = "daksina" # paired with catuṣkala class Giti(str, Enum): """Gīti style coupled to (kala, mārga) pair under caccatpuṭa. evidence: R_40_caccatputa_kala_marga (aff#393, "in citra mārga there is māgadhī gīti; in vārtika mārga there is sambhāvitā gīti") """ MAGADHI = "magadhi" # citra SAMBHAVITA = "sambhavita" # vārtika # daksina/catuṣkala : no specific gīti named in the rule — UNRESOLVED class CompositionType(str, Enum): """The two macro-classes of rhythmic music. Gāndharva = mārga (Vedic/canonical), 5 tālas × 3 mārgas, upaveda of Sāmaveda. Deśī = regional/local — "innumerable as deśī music". evidence: R_gandharva_def (aff#2902 upaveda, aff#3155 5×3), prabandhas_chapter_origin_innumerable (aff#1306, aff#1317), desi_gita_uses_vastu (aff for desī gītas) """ GANDHARVA = "gandharva" DESI = "desi" class Sthana(str, Enum): """Tempo grades for a gamaka. evidence: R_1008_sphurita_tempos ("Sphurita gamaka is performed in fast, medium and slow tempos"), gamaka_sthita_in_ela (aff#1449) """ DRUTA = "druta" # fast MADHYA = "madhya" # medium VILAMBITA = "vilambita" # slow / sthita (in elā) # ----------------------------------------------------------------------------- # Caccatpuṭa — the paradigm tāla of the system # ----------------------------------------------------------------------------- # Caccatpuṭa is the FIRST among the 5 tālas of Nāṭyaśāstra XXXI; the only # caturasra ("square") tāla; canonical shape = 4 kalās = 8 mātrās. # A 16-kalā variant is attested for the Kārmāravī rāga. # evidence: R_c47_caccatputa (aff#445, aff#487, aff#488, aff#1909) CACCATPUTA_RANK_IN_NS_XXXI: int = 1 # aff#1909 CACCATPUTA_SHAPE: str = "caturasra" # aff#1909 CACCATPUTA_KALAS_DEFAULT: int = 4 # aff#1909 CACCATPUTA_MATRAS_DEFAULT: int = 8 # aff#1909 CACCATPUTA_KALAS_KARMARAVI_VARIANT: int = 16 # aff#487, aff#488 # Kala × Mārga × Gīti table for caccatpuṭa. # evidence: R_40_caccatputa_kala_marga (aff#393) # Each row: (kala, mārga, gīti) — third entry is None where source silent. CACCATPUTA_KALA_MARGA_GITI: tuple[tuple[Kala, Marga, Giti | None], ...] = ( (Kala.EKAKALA, Marga.CITRA, Giti.MAGADHI), (Kala.DVIKALA, Marga.VARTIKA, Giti.SAMBHAVITA), (Kala.CATUSKALA, Marga.DAKSINA, None), # gīti not named — UNRESOLVED ) # ----------------------------------------------------------------------------- # Gāndharva system structural numbers # ----------------------------------------------------------------------------- # evidence: R_gandharva_def (aff#2902, aff#3155) GANDHARVA_N_TALAS: int = 5 GANDHARVA_N_MARGAS_PER_TALA: int = 3 GANDHARVA_UPAVEDA_OF: str = "samaveda" # ----------------------------------------------------------------------------- # Prabandha ordinals — pinned positions in the 48-list # ----------------------------------------------------------------------------- # The Brihaddesi enumerates "48 (eight above forty) varieties of beautiful # prabandhas" (aff#1519). Not all 48 positions are individually rule-pinned; # we list ONLY those with a sourced ordinal. # evidence per entry below. PRABANDHA_ORDINAL: dict[str, int] = { "kanda": 1, # R_172_kanda_definition (aff#1307 "ādyaḥ prabandhaḥ") "vrtta": 2, # R_vrtta_prabandha (rule body "2nd prabandha") "catuspadi": 11, # R_5p2_1083 (aff#1309) "dodhaka": 12, # R_c173_dodhaka (aff#1310) "totaka": 13, # R_174_totaka_definition (aff#1311) "vastu": 14, # R_vastu_prabandha (aff#1318) "hainsapat": 16, # R_1085_hainsapat_16th_prabandha (aff#1312) "asvalila": 18, # R_1086_asvalila_eighteenth (aff#1313) "simhalila": 20, # simhalila_twentieth_prabandha (aff#1314) "shukacancu": 22, # shukacancu_position_22 "vicitra": 23, # vicitra_definition_and_rank "caturanga": 25, # r_brd_440_caturanga_def (aff#1323) "simhavikranta": 28, # R_1092_01 (aff) "kalahamsa": 29, # R_1093_kalahamsa_ordinal_29 (aff#1333) "ghata": 30, # R_4p1_442 (aff#1327) "cakravala": 31, # R_443_cakravala "tripadi": 32, # BD_6_1_R026 "patakarana": 34, # patakarana_thirty_fourth (aff#1330) "kaivata": 35, # R_5p2_1096 (aff#1331) "karana": 40, # R_1101_karana_40th_combined_5 "matrika": 43, # prabandha_enumeration_48_varieties (aff#1332) "varnasvara": 46, # R_204_varnasvara_definition (aff#1515) "muktavali": 47, # R_muktavali_def "pratapavardhana": 48, # R_c206_pratapavardhana (aff#1338) } # Total prabandha varieties (closed enumeration claim). # evidence: prabandha_enumeration_48_varieties (aff#1519 "eight above forty") PRABANDHA_VARIETIES_TOTAL: int = 48 # Epistemic tension: also "innumerable as deśī music". # evidence: prabandhas_chapter_origin_innumerable (aff#1306, aff#1317) # Both claims are preserved here — they are NOT mutually exclusive (48 = # "beautiful" canonical forms; deśī forms are unbounded). # ----------------------------------------------------------------------------- # Tāla assignments to prabandhas (closed mapping where rule-sourced) # ----------------------------------------------------------------------------- # evidence per entry below. PRABANDHA_TALA: dict[str, str | None] = { "kanda": None, # R_172_kanda_definition: "bereft of tāla" "kalahamsa": "jhampa", # R_5p2_1135 / jhampatala_kalahamsaka "kalahamsaka": "jhampata", # R_1367_kalahamsaka_composition "hayalila": "turagalila", # R_1122_turagalila_hayalila (aff#1375) "gajalila": "gajalila", # R_1124_gajalila_definition "simhavikranta": "aditala", # R_1131_aditala_simhavikranta "krauncapada": "pratitala", # R_1115 / R_446 (aff#) "bandhakarana": "karana", # R_1141_bandhakarana_definition } # Tālas in which varṇailā is performed (open set, ⊇ this list). # evidence: varnaila_talas_set (rule body), R_4p1_473 (aff) VARNAILA_TALAS: tuple[str, ...] = ( "mantha", "dvitiya", "kankala", "dmatitala", ) # Equivalent listing in R_4p1_473: kankāla is one of {maṇṭha, dvitīya, # kankāla, jhaṁpatitāla}. The two enumerations co-exist (jhaṁpatitāla vs # ḍmatitāla = OCR/transliteration variants of the same fourth tāla). # ----------------------------------------------------------------------------- # Varṇailā — seven syllabically-regulated varieties # ----------------------------------------------------------------------------- # evidence: varnaila_seven_varieties (aff#1452), varnaila_seven_count (aff#1471) VARNAILA_VARIETIES_SEVEN: tuple[str, ...] = ( "madanavati", "shashilekha", "prabhavati", "malati", "lalita", "hemavati", "kusumavati", ) # Syllable count per foot, position-indexed: # evidence: varnaila_syllable_count_progression (aff#1456) # "Beginning with eleven syllables and ending with seventeen" VARNAILA_MIN_SYLLABLES: int = 11 VARNAILA_MAX_SYLLABLES: int = 17 # Epistemic tension preserved: R_608_varnaila_14_varieties (aff#1497) asserts # 14 varieties on a different page. Both numbers are sourced; the seven-list # is operative for syllable-count derivation, the fourteen-count is an # alternative enumeration not paired with explicit names. See UNRESOLVED. VARNAILA_VARIETIES_FOURTEEN_COUNT: int = 14 # R_608 (aff#1497) # ----------------------------------------------------------------------------- # Composition elements — building blocks of prabandhas # ----------------------------------------------------------------------------- class CompositionElement(str, Enum): """The seven structural elements that combine to build a prabandha. Compiled from explicit composition/relation rules across the domain. Each element below has at least one rule body that names it as a constituent of a sourced composition. evidence: R_204_varnasvara_definition (svara, pada, pata, tenaka, hand_pata), R_172_kanda_definition (pata, biruda), R_c94_birudas (biruda), BD_6_1_R028 (tenaka), R_446_pratitala_rel (pada, svara), R_1132_01 (pada, svara, pata), gamaka_in_shukacancu_opening (gamaka) """ SVARA = "svara" # note/tone — passim PADA = "pada" # textual unit — pada_one_of_four_gita_components PATA = "pata" # drum/hand syllable — BD_6_1_R005, BD_6_1_R028 by analogy TENAKA = "tenaka" # "tenna/tena" syllable — BD_6_1_R028 BIRUDA = "biruda" # eulogistic Sanskrit compound — R_472_biruda GAMAKA = "gamaka" # vocal ornament — R_378_gamakas HAND_PATA = "hand_pata" # drum syllable on the hands — R_204_varnasvara # Eulogy themes that birudas express. # evidence: R_c94_birudas BIRUDA_THEMES: tuple[str, ...] = ( "plenitude", "renoncement", "bien-etre", "prosperite", ) # Compositions explicitly listed as biruda-bearing. # evidence: R_c94_birudas (rule body), aff#1377, aff#1386, aff#1396, aff#1522 BIRUDA_HOSTS: frozenset[str] = frozenset({ "kanda", "hayalila_second_half", "simhalila", "vicitra", "tripadi", "satpadi", "pratapavardhana", }) # ----------------------------------------------------------------------------- # Prabandha defects # ----------------------------------------------------------------------------- # evidence: prabandha_defects (aff#1524) class PrabandhaDefect(str, Enum): LOKA_DEFECTIVE = "loka_defective" SASTRA_DEFECTIVE = "sastra_defective" REPETITIVE = "repetitive" METRE_DEFECTIVE = "metre_defective" VULGAR = "vulgar" ORDER_DEVIATION = "order_deviation" # ============================================================================= # TYPES — dataclasses # ============================================================================= @dataclass(frozen=True) class TalaConfig: """A tāla instance under a specific kala / mārga selection. evidence: R_40_caccatputa_kala_marga, R_c47_caccatputa, R_gandharva_def """ name: str shape: str | None # "caturasra" for caccatpuṭa, else None unless sourced kalas: int # mātrās = kalas × 2 for caccatpuṭa default kala_grade: Kala | None marga: Marga | None giti: Giti | None = None # only sourced for ekakala/dvikala caccatpuṭa @dataclass(frozen=True) class Prabandha: """A prabandha = a song-form. The Brihaddesi describes 48 (aff#1519); deśī instances are innumerable (aff#1306). evidence: prabandha_chapter_and_authority, prabandha_enumeration_48_varieties, R_172_kanda_definition (template for fields) """ name: str ordinal: int | None # position in the 48-list, if pinned tala: str | None # explicit tāla, or None if "bereft of tāla" elements: frozenset[CompositionElement] language: str | None = None # e.g. "karpata" for kanda n_halves: int | None = None # 2 for dodhaka, hayalīlā… n_pada: int | None = None # e.g. 8 for śarabhalīla n_raga: int | None = None n_tala: int | None = None def __post_init__(self) -> None: if self.ordinal is not None and not (1 <= self.ordinal <= PRABANDHA_VARIETIES_TOTAL): raise ValueError( f"prabandha ordinal must be in [1, {PRABANDHA_VARIETIES_TOTAL}], " f"got {self.ordinal} (evidence: prabandha_enumeration_48_varieties)" ) if not self.elements: raise ValueError( "A prabandha must combine at least one composition element " "(R_204_varnasvara_definition, R_172_kanda_definition)" ) @dataclass(frozen=True) class Varnaila: """A varṇailā = a syllable-regulated elā variety. Seven varieties named in aff#1452, with syllable count 11..17 per foot (aff#1456). Varṇailā is "regulated by syllable count alone, with no regulation of tāla, rasa, or rāga" (aff#1460) — but is sung in tālas {maṇṭha, dvitīya, kaṅkāla, ḍmatitāla} (aff#1462). The two statements are reconciled by reading "no regulation" as "no metre-derived obligation". evidence: varnaila_seven_varieties, varnaila_seven_count, varnaila_syllable_count_progression, varnaila_regulated_by_syllables_only, varnaila_talas_set """ name: str # must be in VARNAILA_VARIETIES_SEVEN syllables_per_foot: int talas: tuple[str, ...] # ⊇ VARNAILA_TALAS in practice description_theme: str = "qualities_glory_enthusiasm_steadfastness" # evidence for theme: varnaila_definition_quality_descriptions (aff#1461) def __post_init__(self) -> None: if self.name not in VARNAILA_VARIETIES_SEVEN: raise ValueError( f"Unknown varṇailā variety {self.name!r}; allowed = " f"{VARNAILA_VARIETIES_SEVEN} (varnaila_seven_varieties, aff#1452)" ) if not (VARNAILA_MIN_SYLLABLES <= self.syllables_per_foot <= VARNAILA_MAX_SYLLABLES): raise ValueError( f"varṇailā syllables_per_foot must be in " f"[{VARNAILA_MIN_SYLLABLES}, {VARNAILA_MAX_SYLLABLES}], got " f"{self.syllables_per_foot} (varnaila_syllable_count_progression, aff#1456)" ) @dataclass(frozen=True) class GamakaApplication: """A gamaka rendered in a specific compositional context. Constraints: - In elā, gamaka must be sthita (slow/steady) — gamaka_sthita_in_ela (aff#1449) - In śukacañcu, gamaka opens the composition combined with tāla — gamaka_in_shukacancu_opening (aff#1378) - Gamaka is not restricted to "shake" — gamaka_definition_vocal_ornament_inc_shakes (aff#1897) evidence: R_378_gamakas, gamaka_sthita_in_ela, gamaka_in_shukacancu_opening, gamaka_definition_vocal_ornament_inc_shakes """ host_form: str # composition name (ela, sukacancu, vibhasha…) tempo: Sthana position: Literal["opening", "any"] = "any" @dataclass(frozen=True) class Biruda: """A panegyrical Sanskrit compound phrase. evidence: R_472_biruda (aff#1983, aff#1467), R_c94_birudas """ theme: str # one of BIRUDA_THEMES (or compound-eulogy) host_form: str # must be in BIRUDA_HOSTS placement: Literal["pada_end", "second_half", "general"] = "general" def __post_init__(self) -> None: if self.host_form not in BIRUDA_HOSTS: raise ValueError( f"biruda host {self.host_form!r} not in sourced set " f"{set(BIRUDA_HOSTS)} (R_c94_birudas)" ) # ============================================================================= # OPÉRATIONS — dérivations exécutables # ============================================================================= def caccatputa_config(kala: Kala) -> TalaConfig: """Return the caccatpuṭa configuration for a given kala-grade, with the sourced (mārga, gīti) pairing. evidence: R_c47_caccatputa (shape/kalas/matras), R_40_caccatputa_kala_marga (kala→mārga→gīti row) """ for k, marga, giti in CACCATPUTA_KALA_MARGA_GITI: if k == kala: return TalaConfig( name="caccatputa", shape=CACCATPUTA_SHAPE, kalas=CACCATPUTA_KALAS_DEFAULT, kala_grade=kala, marga=marga, giti=giti, ) raise ValueError( f"No sourced row for kala={kala} in caccatpuṭa table " f"(R_40_caccatputa_kala_marga)" ) def caccatputa_karmaravi_variant() -> TalaConfig: """The 16-kalā variant of caccatpuṭa attested for the Kārmāravī rāga. evidence: R_c47_caccatputa (aff#487, aff#488 "sixteen kalās") """ return TalaConfig( name="caccatputa_karmaravi", shape=CACCATPUTA_SHAPE, kalas=CACCATPUTA_KALAS_KARMARAVI_VARIANT, kala_grade=None, marga=None, giti=None, ) def varnaila_at(index: int) -> Varnaila: """Build the k-th varṇailā (0..6) with its sourced syllable count. The k-th variety has 11+k syllables per foot, by the progression rule. evidence: varnaila_seven_varieties (aff#1452), varnaila_syllable_count_progression (aff#1456), varnaila_talas_set """ if not (0 <= index < len(VARNAILA_VARIETIES_SEVEN)): raise IndexError( f"varṇailā index must be in [0, {len(VARNAILA_VARIETIES_SEVEN)}); " f"got {index}" ) return Varnaila( name=VARNAILA_VARIETIES_SEVEN[index], syllables_per_foot=VARNAILA_MIN_SYLLABLES + index, talas=VARNAILA_TALAS, ) def all_varnailas() -> list[Varnaila]: """Enumerate the 7 sourced varṇailā varieties. evidence: varnaila_seven_varieties, varnaila_seven_count """ return [varnaila_at(k) for k in range(len(VARNAILA_VARIETIES_SEVEN))] def kanda() -> Prabandha: """Build kanda — the 1st prabandha, sans tāla, combined with pāṭas and birudas, mixed with Karpāṭa-language pada. evidence: R_172_kanda_definition (aff#1307, aff#1339, aff#1340) """ return Prabandha( name="kanda", ordinal=PRABANDHA_ORDINAL["kanda"], tala=None, elements=frozenset({ CompositionElement.PATA, CompositionElement.BIRUDA, CompositionElement.PADA, }), language="karpata", ) def hayalila() -> Prabandha: """Build hayalīlā — composition where first half is sung with svaras, second half with birudas, in turagalīla tāla. evidence: R_1123_01, R_4p1_1362, R_1122_turagalila_hayalila (aff#1375) """ return Prabandha( name="hayalila", ordinal=None, # no ordinal pinned in 6b rule set tala="turagalila", elements=frozenset({ CompositionElement.SVARA, CompositionElement.BIRUDA, }), n_halves=2, ) def sarabhalila() -> Prabandha: """Build śarabhalīla — 8 pādas, each with a distinct rāga and distinct tāla, composed of svaras + pāṭas. evidence: R_1127_sarabhalila_eight_feet, BD_6_1_R013, BD_6_1_R005 """ return Prabandha( name="sarabhalila", ordinal=None, tala=None, # 8 distinct tālas — not a single value elements=frozenset({CompositionElement.SVARA, CompositionElement.PATA}), n_pada=8, n_raga=8, n_tala=8, ) def kalahamsa() -> Prabandha: """Build kalahaṁsa — 29th prabandha; one pada sung first, then svaras, combined with jhampā tāla. evidence: R_1093_kalahamsa_ordinal_29 (aff#1333), R_5p2_1135, R_1134_kalahamsa_definition """ return Prabandha( name="kalahamsa", ordinal=PRABANDHA_ORDINAL["kalahamsa"], tala=PRABANDHA_TALA["kalahamsa"], elements=frozenset({CompositionElement.PADA, CompositionElement.SVARA}), ) def prabandha_by_ordinal(n: int) -> str | None: """Look up the prabandha name at the n-th canonical position. evidence: PRABANDHA_ORDINAL (each entry sourced individually). Returns None if no rule pins that position. """ if not (1 <= n <= PRABANDHA_VARIETIES_TOTAL): raise ValueError( f"ordinal must be in [1, {PRABANDHA_VARIETIES_TOTAL}], got {n}" ) for name, ord_ in PRABANDHA_ORDINAL.items(): if ord_ == n: return name return None def gamaka_in_ela(host_form: str) -> GamakaApplication: """Construct a gamaka constrained for elā context: must be sthita. evidence: gamaka_sthita_in_ela (aff#1449), R_378_gamakas (gamakas as melodic ornaments) """ if "ela" not in host_form.lower(): raise ValueError( f"gamaka_in_ela expects an elā-class host; got {host_form!r} " f"(gamaka_sthita_in_ela, aff#1449)" ) return GamakaApplication( host_form=host_form, tempo=Sthana.VILAMBITA, position="any", ) def gamaka_in_sukacancu() -> GamakaApplication: """Opening gamaka for śukacañcu, combined with tāla. evidence: gamaka_in_shukacancu_opening (aff#1378), shukacancu_definition_svaras_patas_regional """ return GamakaApplication( host_form="sukacancu", tempo=Sthana.MADHYA, # not pinned — placeholder default position="opening", ) def biruda_for(host_form: str, theme: str = "eulogy") -> Biruda: """Place a biruda in a sourced host form. evidence: R_c94_birudas, R_472_biruda (aff#1983) """ placement: Literal["pada_end", "second_half", "general"] = "general" if host_form == "hayalila_second_half": placement = "second_half" elif host_form == "kanda": placement = "general" return Biruda(theme=theme, host_form=host_form, placement=placement) # ============================================================================= # CONTRAINTES — validations # ============================================================================= def is_valid_prabandha_ordinal(n: int) -> bool: """A prabandha ordinal must lie in the closed 48-range. evidence: prabandha_enumeration_48_varieties (aff#1519) """ return 1 <= n <= PRABANDHA_VARIETIES_TOTAL def is_valid_caccatputa_kala_marga(kala: Kala, marga: Marga) -> bool: """A (kala, mārga) pair is valid iff present in the sourced table. evidence: R_40_caccatputa_kala_marga (aff#393) """ for k, m, _ in CACCATPUTA_KALA_MARGA_GITI: if k == kala and m == marga: return True return False def is_valid_biruda_host(host_form: str) -> bool: """Birudas may appear only in the rule-attested host set. evidence: R_c94_birudas (aff#1345, aff#1377, aff#1386, aff#1396, aff#1522) """ return host_form in BIRUDA_HOSTS def is_valid_varnaila_syllable_count(n: int) -> bool: """Varṇailā syllable count per foot lies in [11, 17]. evidence: varnaila_syllable_count_progression (aff#1456) """ return VARNAILA_MIN_SYLLABLES <= n <= VARNAILA_MAX_SYLLABLES def is_valid_gamaka_in_ela(g: GamakaApplication) -> bool: """In elā, gamaka must be sthita (= vilambita). evidence: gamaka_sthita_in_ela (aff#1449) """ if "ela" not in g.host_form.lower(): return True # constraint only applies to elā contexts return g.tempo == Sthana.VILAMBITA # ============================================================================= # UNRESOLVED — concepts mentioned but not pinned by 6b rules # ============================================================================= UNRESOLVED: tuple[dict[str, str], ...] = ( { "concept": "dhātu / udgrāha / dhruva / antara / ābhoga sectional structure", "reason": "later Sangīta-Ratnākara codifies prabandha into 4-section " "dhātus, but Brihaddesi's 6b rule set does not isolate them. " "Kanda's 'sectional' character is alluded (R_172_kanda_definition) " "without explicit dhātu vocabulary.", }, { "concept": "varṇailā variety count (7 vs 14)", "reason": "varnaila_seven_count (aff#1471, vol_II_p124) and " "varnaila_seven_varieties (aff#1452, vol_II_p123) give 7 named; " "R_608_varnaila_14_varieties (aff#1497, vol_II_p127) gives 14. " "Both preserved; the 14-list is not enumerated by name in 6b.", }, { "concept": "catuṣkala/dakṣiṇa gīti of caccatpuṭa", "reason": "R_40_caccatputa_kala_marga gives gīti for ekakala (māgadhī) " "and dvikala (sambhāvitā) but the rule body trails off for " "catuṣkala/dakṣiṇa.", }, { "concept": "ordinals of prabandhas 3-10, 15, 17, 19, 21, 24, 26-27, 33, " "36-39, 41-42, 44-45", "reason": "PRABANDHA_VARIETIES_TOTAL=48 (aff#1519) but only 24 ordinals " "are individually rule-pinned in 6b. Source enumeration " "uddesa-list 'does not match described forms exactly' " "(prabandha_chapter_and_authority).", }, { "concept": "biruda placement / yati-gamaka coupling in elā", "reason": "R_472_biruda mentions 'gamaka_on_yati' after a biruda at " "pada_end in elā; no rule isolates yati structure here.", }, { "concept": "gandharva-deśī formal boundary", "reason": "gāndharva = 5 tālas × 3 mārgas (R_gandharva_def); deśī = " "'innumerable' (aff#1306). The crossover (e.g. caccatpuṭa " "used in deśī rāgas) is attested but not formally typed in 6b.", }, { "concept": "dvipathaka 4 subforms", "reason": "R_c256_dvipathaka asserts 4 subforms with/without tāla; " "individual subforms are not enumerated by name.", }, ) # ============================================================================= # Self-test (executed only when __main__) # ============================================================================= if __name__ == "__main__": # caccatpuṭa configurations c1 = caccatputa_config(Kala.EKAKALA) assert c1.marga == Marga.CITRA and c1.giti == Giti.MAGADHI c2 = caccatputa_config(Kala.DVIKALA) assert c2.marga == Marga.VARTIKA and c2.giti == Giti.SAMBHAVITA c3 = caccatputa_config(Kala.CATUSKALA) assert c3.marga == Marga.DAKSINA and c3.giti is None # UNRESOLVED gīti assert c1.kalas == 4 and c1.shape == "caturasra" cv = caccatputa_karmaravi_variant() assert cv.kalas == 16 # kala/mārga validation assert is_valid_caccatputa_kala_marga(Kala.EKAKALA, Marga.CITRA) assert not is_valid_caccatputa_kala_marga(Kala.EKAKALA, Marga.DAKSINA) # varṇailā enumeration vs = all_varnailas() assert len(vs) == 7 assert vs[0].name == "madanavati" and vs[0].syllables_per_foot == 11 assert vs[6].name == "kusumavati" and vs[6].syllables_per_foot == 17 assert is_valid_varnaila_syllable_count(11) assert is_valid_varnaila_syllable_count(17) assert not is_valid_varnaila_syllable_count(10) assert not is_valid_varnaila_syllable_count(18) # unknown variety rejected try: Varnaila(name="unknown", syllables_per_foot=12, talas=VARNAILA_TALAS) assert False, "should have raised" except ValueError: pass # prabandha constructors k = kanda() assert k.ordinal == 1 assert k.tala is None assert CompositionElement.BIRUDA in k.elements h = hayalila() assert h.tala == "turagalila" and h.n_halves == 2 s = sarabhalila() assert s.n_pada == 8 and s.n_raga == 8 and s.n_tala == 8 kh = kalahamsa() assert kh.ordinal == 29 and kh.tala == "jhampa" # ordinal lookup assert prabandha_by_ordinal(1) == "kanda" assert prabandha_by_ordinal(48) == "pratapavardhana" assert prabandha_by_ordinal(13) == "totaka" assert prabandha_by_ordinal(5) is None # not pinned in 6b assert is_valid_prabandha_ordinal(1) assert is_valid_prabandha_ordinal(48) assert not is_valid_prabandha_ordinal(0) assert not is_valid_prabandha_ordinal(49) try: prabandha_by_ordinal(49) assert False except ValueError: pass # gamaka in elā must be sthita / vilambita g_ela = gamaka_in_ela("lalita_ela") assert g_ela.tempo == Sthana.VILAMBITA assert is_valid_gamaka_in_ela(g_ela) g_bad = GamakaApplication(host_form="lalita_ela", tempo=Sthana.DRUTA) assert not is_valid_gamaka_in_ela(g_bad) try: gamaka_in_ela("kanda") assert False except ValueError: pass # gamaka in śukacañcu = opening g_su = gamaka_in_sukacancu() assert g_su.position == "opening" # birudas: host validation assert is_valid_biruda_host("kanda") assert is_valid_biruda_host("simhalila") assert not is_valid_biruda_host("varnaila") b = biruda_for("hayalila_second_half", theme="plenitude") assert b.placement == "second_half" try: biruda_for("random_form") assert False except ValueError: pass # prabandha defect enum assert PrabandhaDefect.REPETITIVE.value == "repetitive" # gāndharva structural numbers assert GANDHARVA_N_TALAS == 5 and GANDHARVA_N_MARGAS_PER_TALA == 3 assert GANDHARVA_UPAVEDA_OF == "samaveda" # prabandha total / coverage assert PRABANDHA_VARIETIES_TOTAL == 48 pinned = sorted(PRABANDHA_ORDINAL.values()) assert pinned[0] == 1 and pinned[-1] == 48 assert len(set(pinned)) == len(pinned) # ordinals unique # Prabandha invalid-ordinal raises try: Prabandha(name="x", ordinal=49, tala=None, elements=frozenset({CompositionElement.SVARA})) assert False except ValueError: pass # Empty-elements Prabandha rejected try: Prabandha(name="x", ordinal=1, tala=None, elements=frozenset()) assert False except ValueError: pass print("tala_prabandha.py — all self-tests pass")