← domain #4

Source : marga_style.py

data/library/books/brihaddesi_sharma_1992/formal_grammar/marga_style.py · 809 lines · 30540 bytes
"""Domaine 4 — mārga / gīti / pada-style (temporal frame of song-rendering) Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II) depuis : - 82 règles génératives 6b - 134 affirmations sourcées - 97 concepts (partition Leiden domain_id=4) Le domaine couvre : - les 3 mārgas du système ancien des tālas (citra, vārtika, dakṣiṇa) - les 4 gītis de la classification de Bharata pour pada-rendering (māgadhī, ardhamāgadhī, sambhāvitā, pṛthulā) - les divisions temporelles kalā (ekakala, dvikala, catuṣkala) - la yati (samā, gopucchā, srotogatā) et la pāṇi/graha - l'opposition mārga / deśī 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 floues sont listées dans UNRESOLVED. Python 3.10+. Importable directement, sans dépendance externe. """ from __future__ import annotations from dataclasses import dataclass from enum import Enum from typing import Iterable # ============================================================================= # CONSTANTES — énumérations fermées du Brihaddesi # ============================================================================= class Mārga(str, Enum): """The three mārgas of the ancient tāla system (Nāṭyaśāstra heritage). "mārga" = path; the span of a tāla. NŚ enumerates three mārgas per tāla in a doubling ratio: citra (shortest), vārtika (×2 citra), dakṣiṇa (×2 vārtika). evidence: R_marga_def, R_2230_three_margas_tala (aff#1908, aff#3154) """ CITRA = "citra" VARTIKA = "vartika" DAKSINA = "daksina" class Style(str, Enum): """The fundamental opposition: structured-ancient (mārga) vs regional (deśī). deśī = regional/ethnic music, fundamentally tied to a region or people (śabara, pulinda, kāmboja, baṅga, kirāta, bāhlīka, āndhra, draviḍa, …). The opposition is structural: deśī begins with a group of four svaras, mārga does not. evidence: R_346_desi_definition, desi_definition_regional_ethnic, R_509_marga (aff#184, aff#186, aff#1853, aff#2905, aff#2907) """ MARGA = "marga" DESI = "desi" class KalāTāla(str, Enum): """The three kalā-based tāla divisions tied to the mārgas. Each kalā has a fixed mātrā-duration; the kalā-tāla of a mārga is determined by the size of the metrical unit ("on account of the use of N-mātrā units"). evidence: R_108_dvikala_definition, ekakala_in_citra_marga (aff#2732), R_1897_001 (aff#2733), catushkala_tala_daksina_marga (aff#2731) """ EKAKALA = "ekakala" # 2-mātrā units → citra mārga DVIKALA = "dvikala" # 4-mātrā units → vārtika mārga CATUSKALA = "catuskala" # 8-mātrā units → dakṣiṇa mārga class Yati(str, Enum): """The three yatis = order/sequence of layas in a piece. evidence: yati_definition_and_types (aff#3167, aff#1996), sama_yati_uniform_laya, gopuccha_yati_definition, srotogata_yati_definition """ SAMA = "sama" # constant laya throughout GOPUCCHA = "gopuccha" # fast → slow (cow's-tail tapering) SROTOGATA = "srotogata" # slow → fast (river gathering current) class Laya(str, Enum): """The three layas (tempi) cited as predominating in mārgas. evidence: R_86_citra_marga_composition (druta laya in citra), R_573_vartika_marga (madhya laya in vārtika), daksina_marga_attributes (vilambita laya in dakṣiṇa) """ DRUTA = "druta" # fast — citra mārga MADHYA = "madhya" # medium — vārtika mārga VILAMBITA = "vilambita" # slow — dakṣiṇa mārga class Pāṇi(str, Enum): """The three pāṇis = phasing of song-onset vs tāla-onset. Each pāṇi is equated with a graha (point of song-entry): uparipāṇi ≡ anāgata graha (instrument first, song later) samapāṇi ≡ sama graha (simultaneous) avapāṇi ≡ atīta graha (song first, instrument later) ≡ adhaḥpāṇi (synonym) evidence: R_2243_uparipani_anagata (aff#3175), BD_6_1_R008 (aff#3174, aff#3186), R_86_citra_marga_composition (uparipāṇi in citra), R_573_vartika_marga (samapāṇi in vārtika), daksina_marga_attributes (avapāṇi in dakṣiṇa) """ UPARI = "uparipani" # = anāgata graha — citra mārga SAMA = "samapani" # — vārtika mārga AVA = "avapani" # = atīta graha = adhaḥpāṇi — dakṣiṇa mārga class Graha(str, Enum): """The three grahas — paired one-to-one with the pāṇis. evidence: R_2243_uparipani_anagata, BD_6_1_R008 """ ANAGATA = "anagata" # ≡ uparipāṇi SAMA = "sama" # ≡ samapāṇi (paired by inference: complete the # three-way symmetry; affirmations only pin the # two asymmetric grahas — see UNRESOLVED) ATITA = "atita" # ≡ avapāṇi / adhaḥpāṇi class Gīti(str, Enum): """The four pada-gīti categories of Bharata, cited by Mātaṅga. Note: a fifth gīti name (Gauḍī gīti, R_1472_01 / gauda_giti_definition) appears in the Brihaddesi but in a different (rāga-rendering) context, not as a pada-gīti category — kept out of this enum. The 5-gīti list (śuddhā/bhinnā/gauḍī/vesarā/sādhāraṇī) cited in some secondary literature is NOT formalised here: no rule in the 82-rule set enumerates it for the pada-gīti axis — see UNRESOLVED. evidence: R_padagitis_section, R_66_sambhavita_definition, R_4p0_010, R_c85_magadhi, R_4p0_019 """ MAGADHI = "magadhi" # Bharata rank 1 ARDHAMAGADHI = "ardhamagadhi" # Bharata rank 2 SAMBHAVITA = "sambhavita" # Bharata rank 3 PRTHULA = "prthula" # Bharata rank 4 class SyllableKind(str, Enum): """Syllabic units used to characterise gīti composition. evidence: R_66_sambhavita_definition (sambhāvitā = guru-dominant), R_5p2_586 (pṛthulā = laghu-dominant), R_86_citra_marga_composition (guru+laghu+druta in citra), r_brd_506_druta_notation (druta = 1/2 mātrā) """ LAGHU = "laghu" # short (1 mātrā) GURU = "guru" # long (2 mātrās) PLUTA = "pluta" # extra-long (3 mātrās) — R_1900_01 DRUTA = "druta" # half-mātrā (1/2) — R_498_shunya, r_brd_506_druta_notation class Limb(str, Enum): """The "limb" (avayava) of a mārga — a structural feature of the instrumental-vocal coupling. evidence: R_86_citra_marga_composition (ogha in citra), R_573_vartika_marga (anugata in vārtika), daksina_marga_attributes (tattya in dakṣiṇa), R_2236_gitanuga_vadya_varieties (ogha + anugata definitions) """ OGHA = "ogha" # profuse strokes per song-unit — citra ANUGATA = "anugata" # instrumental follows song, not identical — vārtika TATTYA = "tattya" # — dakṣiṇa (limb defined only by attribution # in rule body; no separate gloss in 6b set) class BharataRank(int, Enum): """The four-rank ordering of pada-gītis in Bharata's classification. evidence: R_c85_magadhi (māgadhī rank=1), R_4p0_019 (ardhamāgadhī rank=2), R_66_sambhavita_definition (sambhāvitā rank=3), R_4p0_010 (pṛthulā rank=4) """ MAGADHI = 1 ARDHAMAGADHI = 2 SAMBHAVITA = 3 PRTHULA = 4 # ----------------------------------------------------------------------------- # Mātrā arithmetic — fixed values from the rule set # ----------------------------------------------------------------------------- # Number of mātrās in the metrical unit of each mārga. # evidence: R_1978_matra_marga_assignment (aff#2738) # "The mātrās to be used in dakṣiṇā, vṛtti and citrā mārgas are eight, # four and two respectively." MARGA_UNIT_MATRAS: dict[Mārga, int] = { Mārga.CITRA: 2, Mārga.VARTIKA: 4, Mārga.DAKSINA: 8, } # Doubling ratio between the three mārgas (NŚ enumeration). # evidence: R_marga_def (aff#1908) MARGA_RATIO: dict[Mārga, int] = { Mārga.CITRA: 1, Mārga.VARTIKA: 2, Mārga.DAKSINA: 4, } # Number of mātrās per kalā for each kalā-tāla. # evidence: ekakala_in_citra_marga (2-mātrā units = ekakala), # R_1897_001 (4-mātrā units = dvikala), # catushkala_tala_daksina_marga (8-mātrā units = catuṣkala) KALATALA_UNIT_MATRAS: dict[KalāTāla, int] = { KalāTāla.EKAKALA: 2, KalāTāla.DVIKALA: 4, KalāTāla.CATUSKALA: 8, } # Mātrā-duration of syllable kinds (in mātrās). # evidence: prosodic convention sourced via SyllableKind evidence above. SYLLABLE_MATRAS: dict[SyllableKind, float] = { SyllableKind.DRUTA: 0.5, # R_498_shunya, r_brd_506_druta_notation SyllableKind.LAGHU: 1.0, SyllableKind.GURU: 2.0, SyllableKind.PLUTA: 3.0, # R_1900_01 ("pluta(3_mātrā)") } # Number of mātrās per gīti-unit in each mārga context. # evidence: # citra+māgadhī: R_86_citra_marga_composition / aff#2727 (2 mātrās) # vārtika+sambhāvitā: aff#2726 (4 mātrās), R_573_vartika_marga # dakṣiṇa+pṛthulā: aff#2725 (8 mātrās), daksina_marga_attributes GITI_UNIT_MATRAS: dict[tuple[Mārga, Gīti], int] = { (Mārga.CITRA, Gīti.MAGADHI): 2, (Mārga.VARTIKA, Gīti.SAMBHAVITA): 4, (Mārga.DAKSINA, Gīti.PRTHULA): 8, } # Number of kalās in a gīti-cycle for the canonical mārga-gīti pairings. # evidence: # māgadhī gīti in citra has 12 kalās (R_c231_magadhi_giti, aff#378) # vārtika has 24 kalās with sambhāvitā (R_c28_vartika, aff#377) GITI_KALAS: dict[tuple[Mārga, Gīti], int] = { (Mārga.CITRA, Gīti.MAGADHI): 12, (Mārga.VARTIKA, Gīti.SAMBHAVITA): 24, # (Mārga.DAKSINA, Gīti.PRTHULA): no isolated kalā-count rule — UNRESOLVED } # Mātrās = 8 total in the hand-action paradigm of the ancient tāla. # evidence: r_brd_2217_matras_count (aff#3170) N_MATRAS_HAND_PARADIGM: int = 8 # ----------------------------------------------------------------------------- # Mārga profile — canonical co-attribution of features # ----------------------------------------------------------------------------- # Each mārga collects a fixed bundle of (yati, laya, pāṇi, gīti, limb, # kalā-tāla, mātrās-per-unit) from the rule set. These are NOT independent # axes — the Brihaddesi names them together. # # evidence per mārga: # CITRA: R_86_citra_marga_composition (aff#2746, aff#2780), # ekakala_in_citra_marga (aff#2732), # R_c1896_ekakala_tala_magadhi (aff#2719) # VARTIKA: R_573_vartika_marga (aff#2754, aff#2779), # R_1897_001 (aff#2733, aff#2720) # DAKSINA: daksina_marga_attributes (aff#2747, aff#2778), # catushkala_tala_daksina_marga (aff#2731) @dataclass(frozen=True) class MārgaProfile: """Static co-attribution bundle for a mārga. Every field has at least one supporting affirmation; see comments above. """ mārga: Mārga yati: Yati laya: Laya pāṇi: Pāṇi graha: Graha gīti: Gīti limb: Limb kalā_tāla: KalāTāla unit_matras: int MARGA_PROFILES: dict[Mārga, MārgaProfile] = { Mārga.CITRA: MārgaProfile( mārga=Mārga.CITRA, yati=Yati.SAMA, laya=Laya.DRUTA, pāṇi=Pāṇi.UPARI, graha=Graha.ANAGATA, gīti=Gīti.MAGADHI, limb=Limb.OGHA, kalā_tāla=KalāTāla.EKAKALA, unit_matras=MARGA_UNIT_MATRAS[Mārga.CITRA], ), Mārga.VARTIKA: MārgaProfile( mārga=Mārga.VARTIKA, yati=Yati.SROTOGATA, laya=Laya.MADHYA, pāṇi=Pāṇi.SAMA, graha=Graha.SAMA, # paired by symmetry — see UNRESOLVED gīti=Gīti.SAMBHAVITA, limb=Limb.ANUGATA, kalā_tāla=KalāTāla.DVIKALA, unit_matras=MARGA_UNIT_MATRAS[Mārga.VARTIKA], ), Mārga.DAKSINA: MārgaProfile( mārga=Mārga.DAKSINA, yati=Yati.GOPUCCHA, laya=Laya.VILAMBITA, pāṇi=Pāṇi.AVA, graha=Graha.ATITA, gīti=Gīti.PRTHULA, limb=Limb.TATTYA, kalā_tāla=KalāTāla.CATUSKALA, unit_matras=MARGA_UNIT_MATRAS[Mārga.DAKSINA], ), } # ----------------------------------------------------------------------------- # Gīti profile — syllabic composition of each gīti # ----------------------------------------------------------------------------- @dataclass(frozen=True) class GītiProfile: """Static profile of a pada-gīti category. `dominant_syllable` = the syllable kind that predominates by source rule; `bharata_rank` = ordinal in Bharata's classification (1..4); `home_mārga` = the canonical mārga of this gīti (the one whose MārgaProfile.gīti points to it); `unit_matras` = mātrās per gīti-unit in the home mārga (if pinned). """ gīti: Gīti bharata_rank: BharataRank dominant_syllable: SyllableKind | None home_mārga: Mārga | None unit_matras: int | None GITI_PROFILES: dict[Gīti, GītiProfile] = { Gīti.MAGADHI: GītiProfile( gīti=Gīti.MAGADHI, bharata_rank=BharataRank.MAGADHI, # No "dominant syllable" rule for māgadhī — its rule body # (R_1900_01) gives a composition "2 gurus + 1 laghu + 1 pluta" # rather than a single dominant kind. dominant_syllable=None, home_mārga=Mārga.CITRA, unit_matras=GITI_UNIT_MATRAS[(Mārga.CITRA, Gīti.MAGADHI)], ), Gīti.ARDHAMAGADHI: GītiProfile( gīti=Gīti.ARDHAMAGADHI, bharata_rank=BharataRank.ARDHAMAGADHI, dominant_syllable=None, # defined relationally as half-māgadhī home_mārga=None, # not pinned to a mārga by rule unit_matras=None, ), Gīti.SAMBHAVITA: GītiProfile( gīti=Gīti.SAMBHAVITA, bharata_rank=BharataRank.SAMBHAVITA, dominant_syllable=SyllableKind.GURU, # R_66, aff#2718, aff#3163 home_mārga=Mārga.VARTIKA, unit_matras=GITI_UNIT_MATRAS[(Mārga.VARTIKA, Gīti.SAMBHAVITA)], ), Gīti.PRTHULA: GītiProfile( gīti=Gīti.PRTHULA, bharata_rank=BharataRank.PRTHULA, dominant_syllable=SyllableKind.LAGHU, # R_5p2_586, aff#2724 home_mārga=Mārga.DAKSINA, unit_matras=GITI_UNIT_MATRAS[(Mārga.DAKSINA, Gīti.PRTHULA)], ), } # Composition unit of the citra mārga gīti-cycle (māgadhī). # evidence: R_86_citra_marga_composition (aff#2780) — "one guru, one laghu, # one druta"; total = 2 + 1 + 0.5 = 3.5 mātrās for the *syllable sum*, # while the gīti-unit itself is 2 mātrās (R_86 also gives "matras: 2"). # We expose the composition list but not arithmetic equality, to avoid # imposing an unsourced relation between the two. CITRA_MARGA_UNIT_COMPOSITION: tuple[SyllableKind, ...] = ( SyllableKind.GURU, SyllableKind.LAGHU, SyllableKind.DRUTA, ) # Composition of māgadhī gīti in citra mārga (extra rule). # evidence: R_1900_01 — "2*guru + 1*laghu + 1*pluta" MAGADHI_GITI_CITRA_COMPOSITION: tuple[SyllableKind, ...] = ( SyllableKind.GURU, SyllableKind.GURU, SyllableKind.LAGHU, SyllableKind.PLUTA, ) # Composition of vārtika mārga metrical units. # evidence: R_c1926_vartika_marga_units — "2 gurus + 1 guru + 1 laghu" (i.e. G,G,G,L) VARTIKA_MARGA_UNIT_COMPOSITION: tuple[SyllableKind, ...] = ( SyllableKind.GURU, SyllableKind.GURU, SyllableKind.GURU, SyllableKind.LAGHU, ) # Composition of dakṣiṇa mārga (guru-multiplicity pattern). # evidence: BD_6_1_R015 — "4+2+1 gurus" configuration DAKSINA_MARGA_GURU_CONFIG: tuple[int, int, int] = (4, 2, 1) # ----------------------------------------------------------------------------- # Vṛtti — three dramatic / temporal-component "modes" # ----------------------------------------------------------------------------- class Vṛtti(str, Enum): """Three vṛttis distinguished by predominance of song vs instrument. evidence: R_1907_vrtti_enumeration (aff#2745) """ DAKSINA = "daksina" # song-dominant VRTTI = "vrtti" # both song + instrument CITRA = "citra" # instrument-dominant # Layas of the vṛtti-mārga (separate from the three pada-gīti mārgas). # evidence: R_1929_001 — "Vṛtti-mārga has three layas: vārtika, citra, dhruva" VRTTI_MARGA_LAYAS: tuple[str, ...] = ("vartika_marga", "citra_marga", "dhruva_marga") # Layas of the dakṣiṇa-mārga operation. # evidence: BD_6_1_R015 — three layas inside dakṣiṇa = [dakṣiṇa, vārtika, citra] DAKSINA_MARGA_LAYAS: tuple[Mārga, ...] = (Mārga.DAKSINA, Mārga.VARTIKA, Mārga.CITRA) # Layas of the citra-mārga operation. # evidence: aff#2775 — "Citra mārga operation contains three layas: Citra # mārga, dhruva mārga, śūnya mārga" # Note: dhruva-mārga and śūnya-mārga are not modelled in Mārga enum # (UNRESOLVED — they are layas-of-citra, not separate mārgas of the canonical # three). CITRA_MARGA_LAYAS_NAMES: tuple[str, ...] = ( "citra_marga", "dhruva_marga", "shunya_marga", ) # ----------------------------------------------------------------------------- # deśī ethnic list — the closed enumeration cited in R_346_desi_definition # ----------------------------------------------------------------------------- # evidence: aff#186 — śabara, pulinda, kāmboja, baṅga, kirāta, bāhlīka, āndhra, draviḍa DESI_ETHNIC_GROUPS: tuple[str, ...] = ( "shabara", "pulinda", "kamboja", "banga", "kirata", "bahlika", "andhra", "dravida", ) # Number of named groups (aff#1853 says "eight ethnic names cited as examples") N_DESI_ETHNIC_GROUPS: int = 8 # ----------------------------------------------------------------------------- # Pada-gīti relational rules # ----------------------------------------------------------------------------- # evidence: R_4p0_019 (aff#2723, aff#2735) — "ardhamāgadhī is completed in # half of māgadhi gīti" ARDHAMAGADHI_DURATION_RATIO_TO_MAGADHI: float = 0.5 # evidence: aff#2722, R_c85_magadhi — "māgadhī = gīti repeated thrice" MAGADHI_REPETITION_COUNT: int = 3 # evidence: R_5p3_2244 — "dvirnivṛtta = 'twice returned' pada repetition in # māgadhī" DVIRNIVRTTA_REPETITION_COUNT: int = 2 # ============================================================================= # OPÉRATIONS — dérivations exécutables # ============================================================================= def profile_of(mārga: Mārga) -> MārgaProfile: """Return the canonical co-attribution bundle of a mārga. evidence: MARGA_PROFILES dict (each mārga has its source rules cited) """ return MARGA_PROFILES[mārga] def giti_of(mārga: Mārga) -> Gīti: """Return the canonical gīti of a mārga. evidence: R_86_citra_marga_composition (citra → māgadhī), R_573_vartika_marga (vārtika → sambhāvitā), daksina_marga_attributes (dakṣiṇa → pṛthulā) """ return MARGA_PROFILES[mārga].gīti def home_marga_of(gīti: Gīti) -> Mārga | None: """Return the mārga in which a gīti is canonically performed, or None if the gīti has no pinned home mārga in the rule set. Note: ardhamāgadhī has no home mārga in 6b — it is defined relationally as "half-māgadhī". evidence: GITI_PROFILES (each entry cites its rule) """ return GITI_PROFILES[gīti].home_mārga def kalatala_of(mārga: Mārga) -> KalāTāla: """Return the canonical kalā-tāla of a mārga. evidence: ekakala_in_citra_marga, R_1897_001, catushkala_tala_daksina_marga """ return MARGA_PROFILES[mārga].kalā_tāla def unit_matras_of(mārga: Mārga) -> int: """Return the mātrā-count of a mārga's metrical unit. evidence: R_1978_matra_marga_assignment (aff#2738) """ return MARGA_UNIT_MATRAS[mārga] def graha_for_pani(pāṇi: Pāṇi) -> Graha: """Return the graha equated with a given pāṇi. evidence: R_2243_uparipani_anagata (upari ≡ anāgata), BD_6_1_R008 (ava/adhaḥ ≡ atīta). For samapāṇi ≡ sama-graha: paired by completing the three-way symmetry; no isolated rule pins this equation — see UNRESOLVED. """ return { Pāṇi.UPARI: Graha.ANAGATA, Pāṇi.SAMA: Graha.SAMA, Pāṇi.AVA: Graha.ATITA, }[pāṇi] def derive_ardhamagadhi_duration(magadhi_matras: float) -> float: """Compute ardhamāgadhī duration from māgadhī duration (× 1/2). evidence: R_4p0_019 (aff#2735) — "ardhamāgadhī is completed in half of māgadhi gīti" """ if magadhi_matras < 0: raise ValueError("magadhi_matras must be non-negative") return magadhi_matras * ARDHAMAGADHI_DURATION_RATIO_TO_MAGADHI def matras_of_syllables(seq: Iterable[SyllableKind]) -> float: """Sum the mātrā-durations of a syllable sequence. evidence: SYLLABLE_MATRAS dict (rule citations on each entry) """ return float(sum(SYLLABLE_MATRAS[s] for s in seq)) def is_giti_in_marga(gīti: Gīti, mārga: Mārga) -> bool: """True iff `gīti` is the canonical gīti of `mārga`. evidence: home_marga_of() — same rule set. Returns False (not "unknown") for gītis with no home mārga; this is the conservative answer. """ return GITI_PROFILES[gīti].home_mārga == mārga def vrtti_marga_doubling_ratio(a: Mārga, b: Mārga) -> int: """Doubling ratio between two mārgas in the citra→vārtika→dakṣiṇa series. Return the integer multiple s.t. unit(b) = ratio × unit(a) when b > a in the doubling chain, or fraction-rounded otherwise (raises if undefined). evidence: R_marga_def (aff#1908) — "citra (shortest), vārtika (×2 citra), dakṣiṇa (×2 vārtika)" """ ra, rb = MARGA_RATIO[a], MARGA_RATIO[b] if rb % ra != 0: raise ValueError( f"non-integer doubling ratio between {a.value} and {b.value}" ) return rb // ra # ============================================================================= # CONTRAINTES — validations # ============================================================================= def is_marga_profile_consistent(profile: MārgaProfile) -> bool: """Cross-check: profile matches the canonical bundle for its mārga. evidence: MARGA_PROFILES (sourced bundle). """ return profile == MARGA_PROFILES[profile.mārga] def is_valid_gita_in_marga(gīti: Gīti, mārga: Mārga) -> bool: """True iff `gīti` is permitted in `mārga`. Currently each gīti has at most one home mārga (the canonical pairing); ardhamāgadhī is permitted nowhere by source rule (UNRESOLVED). Special case: māgadhī gīti also has a derivation rule in citra mārga (R_c231_magadhi_giti) — already its home mārga, so no extra entry needed. evidence: GITI_PROFILES, R_c231_magadhi_giti """ home = GITI_PROFILES[gīti].home_mārga return home is not None and home == mārga def is_valid_unit_matras(mārga: Mārga, unit_matras: int) -> bool: """Cross-check a metrical-unit mātrā count against R_1978's mapping. evidence: R_1978_matra_marga_assignment (aff#2738) """ return MARGA_UNIT_MATRAS[mārga] == unit_matras def is_valid_kalatala_for_marga(mārga: Mārga, kt: KalāTāla) -> bool: """True iff `kt` is the canonical kalā-tāla of `mārga`. evidence: ekakala_in_citra_marga, R_1897_001, catushkala_tala_daksina_marga """ return MARGA_PROFILES[mārga].kalā_tāla == kt def is_doubling_chain(seq: list[Mārga]) -> bool: """True iff `seq` follows the canonical doubling chain citra → vārtika → dakṣiṇa (each unit = 2 × previous). evidence: R_marga_def (aff#1908) — doubling structure of NŚ mārgas. """ if len(seq) < 2: return True units = [MARGA_UNIT_MATRAS[m] for m in seq] return all(units[i + 1] == 2 * units[i] for i in range(len(units) - 1)) def is_marga_vs_desi_distinct(s1: Style, s2: Style) -> bool: """True iff `s1` and `s2` are the two opposed styles {mārga, deśī}. evidence: R_346_desi_definition, R_marga_def — mārga/deśī are opposed structural types; deśī begins with a group of four svaras, mārga does not (aff#184). """ return s1 != s2 and {s1, s2} == {Style.MARGA, Style.DESI} # ============================================================================= # UNRESOLVED — concepts mentioned but not formally pinned by any 6b rule # ============================================================================= # Listed here for traceability; consumed by the JSON manifest. UNRESOLVED: tuple[dict[str, str], ...] = ( { "concept": "5-gīti list (śuddhā/bhinnā/gauḍī/vesarā/sādhāraṇī)", "reason": "Cited in secondary literature for rāga-rendering context, " "but no rule in the 82-rule pada-gīti axis enumerates it. " "Brihaddesi distinguishes pada-gīti (4 categories) from " "rāga-rendering gītis (padagiti_disambiguation_from_raga_giti) " "but does not formalise the 5-list here.", }, { "concept": "ardhamāgadhī home mārga & kalā-tāla", "reason": "Defined only relationally as 'half-māgadhī' " "(R_4p0_019, R_5p3_1923). No rule pins an independent " "mārga or kalā-tāla for it.", }, { "concept": "sama-graha ≡ samapāṇi equation", "reason": "R_2243_uparipani_anagata pairs upari with anāgata, " "BD_6_1_R008 pairs ava/adhaḥ with atīta. The remaining " "graha (sama) is paired with samapāṇi by symmetry; no " "explicit rule asserts this equation in the 6b set.", }, { "concept": "dhruva-mārga and śūnya-mārga", "reason": "Cited as layas inside citra-mārga operation (aff#2775) " "and inside vṛtti-mārga (R_1929_001), but not treated as " "members of the canonical three-mārga enumeration. Their " "operational status is ambiguous.", }, { "concept": "dakṣiṇa + pṛthulā kalā-count", "reason": "R_c28_vartika gives 24 kalās for vārtika and R_c231 gives " "12 for citra+māgadhī; no isolated kalā-count rule for " "the dakṣiṇa+pṛthulā pairing exists in the rule set.", }, { "concept": "vārtika as alias of vṛtti", "reason": "R_c28_vartika and aff#3160 use 'vṛtti' as alias for " "vārtika mārga, while R_1907_vrtti_enumeration treats " "vṛtti as one of three vṛttis (alongside dakṣiṇā and " "citrā). Same word, two senses; not formally disambiguated.", }, { "concept": "tattya (limb of dakṣiṇa)", "reason": "Named as the limb of dakṣiṇa (daksina_marga_attributes) " "but no separate glossing rule defines its operational " "content the way ogha and anugata are defined " "(R_2236_gitanuga_vadya_varieties).", }, ) # ============================================================================= # Self-test (executed at import-time only if __main__) # ============================================================================= if __name__ == "__main__": # --- Sanity: every mārga has a complete profile and the bundle is # internally consistent. for m in Mārga: p = profile_of(m) assert is_marga_profile_consistent(p), m assert p.mārga == m assert p.unit_matras == MARGA_UNIT_MATRAS[m] assert KALATALA_UNIT_MATRAS[p.kalā_tāla] == p.unit_matras, m # --- Sanity: pāṇi ↔ graha pairing is one-to-one. for pani in Pāṇi: g = graha_for_pani(pani) # Round-trip via the profiles marga_with_pani = [ m for m, prof in MARGA_PROFILES.items() if prof.pāṇi == pani ] assert len(marga_with_pani) == 1, pani assert MARGA_PROFILES[marga_with_pani[0]].graha == g # --- Sanity: every gīti with a home mārga matches that mārga's profile. for gt, gp in GITI_PROFILES.items(): if gp.home_mārga is not None: assert MARGA_PROFILES[gp.home_mārga].gīti == gt, gt assert is_valid_gita_in_marga(gt, gp.home_mārga) # Inverse cases for other in Mārga: if other != gp.home_mārga: assert not is_valid_gita_in_marga(gt, other), (gt, other) # --- Sanity: bharata ranks are exactly 1..4 ranks = sorted(p.bharata_rank.value for p in GITI_PROFILES.values()) assert ranks == [1, 2, 3, 4] # --- Doubling chain: citra → vārtika → dakṣiṇa is valid; reversed is not assert is_doubling_chain([Mārga.CITRA, Mārga.VARTIKA, Mārga.DAKSINA]) assert not is_doubling_chain([Mārga.DAKSINA, Mārga.VARTIKA, Mārga.CITRA]) assert vrtti_marga_doubling_ratio(Mārga.CITRA, Mārga.DAKSINA) == 4 assert vrtti_marga_doubling_ratio(Mārga.CITRA, Mārga.VARTIKA) == 2 assert vrtti_marga_doubling_ratio(Mārga.VARTIKA, Mārga.DAKSINA) == 2 # --- Mātrā-arithmetic of citra mārga unit composition. # R_86 composition (G + L + druta) sums to 2 + 1 + 0.5 = 3.5. # The mārga-unit itself is 2 mātrās (R_86 also says "matras: 2"), # so the syllable-sum and the unit-count are NOT equal — we only # check that the sum is well-defined and positive. s = matras_of_syllables(CITRA_MARGA_UNIT_COMPOSITION) assert s == 3.5, s # Māgadhī composition sums to 2g+l+pluta = 4 + 1 + 3 = 8 assert matras_of_syllables(MAGADHI_GITI_CITRA_COMPOSITION) == 8.0 # Vārtika composition (G,G,G,L) sums to 7 assert matras_of_syllables(VARTIKA_MARGA_UNIT_COMPOSITION) == 7.0 # --- ardhamāgadhī = ½ × māgadhī assert derive_ardhamagadhi_duration(12) == 6.0 assert derive_ardhamagadhi_duration(0) == 0.0 # --- KalāTāla cross-check for m in Mārga: kt = kalatala_of(m) assert is_valid_kalatala_for_marga(m, kt) for other_kt in KalāTāla: if other_kt != kt: assert not is_valid_kalatala_for_marga(m, other_kt) # --- unit_matras cross-check for m in Mārga: assert is_valid_unit_matras(m, MARGA_UNIT_MATRAS[m]) assert not is_valid_unit_matras(m, MARGA_UNIT_MATRAS[m] + 1) # --- Style opposition assert is_marga_vs_desi_distinct(Style.MARGA, Style.DESI) assert is_marga_vs_desi_distinct(Style.DESI, Style.MARGA) assert not is_marga_vs_desi_distinct(Style.MARGA, Style.MARGA) # --- deśī ethnic list assert len(DESI_ETHNIC_GROUPS) == N_DESI_ETHNIC_GROUPS == 8 # --- Gīti unit-mātrās match the home-mārga's unit for (m, g), n in GITI_UNIT_MATRAS.items(): assert MARGA_PROFILES[m].gīti == g, (m, g) assert MARGA_UNIT_MATRAS[m] == n, (m, g, n) print("marga_style.py — all self-tests pass")