← domain #9

Source : authority_lakshana.py

data/library/books/brihaddesi_sharma_1992/formal_grammar/authority_lakshana.py · 796 lines · 31670 bytes
"""Domaine 9 — méta-grammaire de l'autorité et du lakṣaṇa-discours Synthèse 6c.3 du Brihaddesi (Sharma 1992) depuis : - 33 concepts du cluster domain_id=9 - 20 règles génératives 6b - 75 affirmations sourcées - partition Leiden domain_id=9 (Bharata, Mātaṅga, lakṣaṇa, tāra, antaramārga, sandhis, nirvahana, Deśī rāgas, Sārngadeva, …) Ce domaine est META : il n'opère pas sur des svaras concrets, il décrit COMMENT le Brihaddesi structure ses propres affirmations : - qui est cité (Bharata = autorité ultime du Nāṭyaśāstra, Mātaṅga = auteur du Brihaddesi, Sārngadeva = expansion postérieure) ; - ce que c'est qu'une définition (lakṣaṇa : 2-fold = general + particular, 10 lakṣaṇas pour les jātis, 5-fold pour audūvita) ; - comment une exposition se déploie (les 5 sandhis du drame : mukha, pratimukha, garbha, vimarsa, nirvahana — chacun reçoit une assignation de rāga par Bharata) ; - les voies intermédiaires (antaramārga) entre mārga et deśī ; - le registre tāra (extension du chant à partir de l'aṁśa). Anti-fabrication : chaque type/constante/opération/contrainte cite ≥1 evidence (rule_id 6b ou affirmation_id). Toute valeur sans citation va dans UNRESOLVED. Python 3.10+. Importable directement, aucune 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 # ============================================================================= class Authority(str, Enum): """Authorities cited or referenced in the Brihaddesi. BHARATA is the ultimate authority (author of the Nāṭyaśāstra), cited repeatedly by Mātaṅga with the formula "Similarly has said Bharata —". MATANGA is the author of the Brihaddesi itself — self-reference is explicit (cid=9, 12 attributions, 0 rules). SARNGADEVA is a later author (Saṅgītaratnākara) whose extensions are noted when they exceed Bharata + Mātaṅga. KUMBHA, SARDULA appear once each as ancillary authorities. evidence: aff#171, aff#182, aff#191, aff#221, aff#312, aff#2150, aff#2468 (Bharata cited by Mātaṅga); aff#532, aff#543, aff#1595, aff#2878 (Mātaṅga self-reference); aff#1558, aff#1907, aff#1961 (Sārngadeva extension); aff#3108 (Kumbha contradicting Mātaṅga); aff#1957 (Šārdūla). """ BHARATA = "bharata" MATANGA = "matanga" SARNGADEVA = "sarngadeva" KUMBHA = "kumbha" SARDULA = "sardula" # Stratification chronologique / hiérarchique des autorités telle qu'elle # émerge du texte. Bharata est antérieur et ultime (rang 0) ; Mātaṅga (rang 1) # se réfère à lui ; Sārngadeva (rang 2) étend Bharata+Mātaṅga ; Kumbha et # Šārdūla (rang 3) sont postérieurs et marginaux. # evidence: aff#537 (Bharata antérieur, contrasté avec Mātaṅga), # aff#1556 + aff#1557 (Bharata et Mātaṅga = même strate de 10 # lakṣaṇas, Sārngadeva ajoute 2), # aff#3108 (Kumbha postérieur à Mātaṅga, le contredit). AUTHORITY_RANK: dict[Authority, int] = { Authority.BHARATA: 0, # ultimate (NŚ) Authority.MATANGA: 1, # author of Brihaddesi Authority.SARNGADEVA: 2, # Saṅgītaratnākara, later Authority.KUMBHA: 3, # later commentator Authority.SARDULA: 3, # ancillary } class LakshanaScope(str, Enum): """Lakṣaṇa is two-fold: general (sāmānya) and particular (viśiṣṭa / "aṁśa-and-the-like"). General is 4-fold; particular is in the form of aṁśa and similar features. evidence: aff#531 (general 4-fold / special aṁśa-and-like), aff#540 (lakṣaṇa is two-fold), R_lakshana_class """ GENERAL = "general" # sāmānya — 4-fold PARTICULAR = "particular" # viśiṣṭa — aṁśa-and-like class LakshanaCompleteness(str, Enum): """A jāti's identity is described by its lakṣaṇas. The status of a jāti depends on how many of its canonical 10 lakṣaṇas are preserved vs. altered: - SUDDHA: all 10 canonical lakṣaṇas conform (śuddhā = "pure") - VIKRTA: ≥1 lakṣaṇa altered (vikṛtā = "modified") - INCOMPLETE: <10 lakṣaṇas specified (under-described) evidence: aff#107 (alteration of one/two/many lakṣaṇas transforms śuddhā → vikṛtā), R_lakshanas_jati, aff#1823 (the 10 lakṣaṇas of jātis include graha, aṁśa, tāra, mandra, etc.). """ SUDDHA = "suddha" VIKRTA = "vikrta" INCOMPLETE = "incomplete" class DramaSandhi(str, Enum): """The five sandhis (junctures) of drama per Nāṭyaśāstra XIX, enumerated identically in the Brihaddesi. evidence: R_272_sandhis_enum (full enumeration with source NS_XIX), aff#1903 (Garbha-sandhi is third among five; mukha + pratimukha are first two; vimarsa + nirvahana are last two), aff#1930 (mukha + pratimukha are first two among five). """ MUKHA = "mukha" # 1st — opening PRATIMUKHA = "pratimukha" # 2nd — counter-opening GARBHA = "garbha" # 3rd — sprouting, fruit still enshrouded VIMARSA = "vimarsa" # 4th — reflection / criticism NIRVAHANA = "nirvahana" # 5th — achievement of the fruit (phala) # Ordinal position of each sandhi — exposed because R_160_nirvahana and the # definitional affirmations cite ordinals explicitly. # evidence: R_272_sandhis_enum (5 sandhis enumerated in order), # R_160_nirvahana (nirvahana := ordinal == 5), # aff#1903 (garbha = 3rd), aff#1911 (nirvahana = 5th). SANDHI_ORDINAL: dict[DramaSandhi, int] = { DramaSandhi.MUKHA: 1, DramaSandhi.PRATIMUKHA: 2, DramaSandhi.GARBHA: 3, DramaSandhi.VIMARSA: 4, DramaSandhi.NIRVAHANA: 5, } class TaraGatiSpan(int, Enum): """The extent (gati) of tāra register from the aṁśa svara. Three permissible spans coexist in the source: 4, 5, or 7 svaras above the aṁśa. Beyond these is "undesirable". The "threefold extent" claim (aff#167) is reconciled with the explicit enumeration {4, 5, 7} in aff#179 — these are exactly three values. evidence: aff#154 (5th svara, sometimes 6th), aff#165 (5th svara from aṁśa), aff#166 (5th svara or 4 svaras), aff#167 (extent is threefold), aff#179 (4th, 5th or 7th svara from aṁśa), R_4p0_018, R_64_tara_extent. note: aff#154 mentions "sometimes 6th" as a softer variant; not retained in the canonical set since R_64_tara_extent enumerates {4,5,7} — see UNRESOLVED. """ FOURTH = 4 FIFTH = 5 SEVENTH = 7 class Vritti(str, Enum): """Dramatic vṛtti (mode of presentation). Bhāratī is the first of the four vṛttis, characterised by predominance of verbal expression. evidence: r_brd_457_bharati_def, aff#1993 (first among four vṛttis, predominance of verbal expression), aff#1432 (Bhadrāvatī elā combined with bhāratī vṛtti). note: only Bhāratī is operationally pinned in domain 9; the other three vṛttis (kaiśikī, ārabhaṭī, sāttvatī) are named in NŚ but no domain-9 rule specifies them → see UNRESOLVED. """ BHARATI = "bharati" # ----------------------------------------------------------------------------- # Bharata's assignments of rāgas to sandhis # ----------------------------------------------------------------------------- # Bharata prescribes which rāga / svara-class fits which dramatic juncture. # The four pinned cases in domain 9 are: # evidence: aff#625 (ṣaḍja-grāma rāga → pratimukha), # aff#626 (sādhārita rāga → garbha), # aff#627 (pañcama → avamarsa, i.e. vimarsa-adjacent), # aff#632 (madhyama-grāma rāga → mukha), # aff#614 + r_brd_381_nirvahana_use (śuddhakaiśikamadhyama → # nirvahana). SANDHI_RAGA_ASSIGNMENT: dict[DramaSandhi, str] = { DramaSandhi.MUKHA: "madhyamagrama_raga", # aff#632 DramaSandhi.PRATIMUKHA: "sadjagrama_raga", # aff#625 DramaSandhi.GARBHA: "sadharita_raga", # aff#626 DramaSandhi.VIMARSA: "pancama", # aff#627 (avamarsa) DramaSandhi.NIRVAHANA: "suddhakaisikamadhya_raga", # aff#614, # r_brd_381_nirvahana_use } # ----------------------------------------------------------------------------- # Lakṣaṇa counts per object-of-description # ----------------------------------------------------------------------------- # Different objects of description carry different lakṣaṇa-counts. The two # canonical ones in domain 9: # evidence: aff#1823 (10 lakṣaṇas of jātis: graha, aṁśa, tāra, mandra, …), # R_lakshanas_jati, # aff#2403 (audūvita lakṣaṇa is fivefold), # R_lakshana_class (general 4-fold; jāti=10; audūvita=5-fold). LAKSHANA_COUNT: dict[str, int] = { "jati": 10, # aff#1823, R_lakshanas_jati "audūvita": 5, # aff#2403, R_lakshana_class "lakshana_general": 4, # aff#531, R_lakshana_class (general 4-fold) } # The named features explicitly listed among the 10 jāti-lakṣaṇas. Only # those named in source affirmations are kept; the remaining slots are # left as UNRESOLVED. # evidence: aff#1823 (the 10 lakṣaṇas of jātis include graha, aṁśa, tāra, # mandra etc.), R_lakshanas_jati. JATI_LAKSHANA_NAMED: tuple[str, ...] = ( "graha", "amsa", "tara", "mandra", ) # Śārngadeva's expansion: he adds two markers — samnyāsa-vinyāsa and # antaramārga — to the 10 lakṣaṇas accepted by Bharata + Mātaṅga, yielding 12. # evidence: aff#1551, aff#1556, aff#1557, aff#1558, # R_1257_jati_laksanas_sarngadeva. SARNGADEVA_ADDED_LAKSHANAS: frozenset[str] = frozenset({ "samnyasa_vinyasa", "antaramarga", }) # Number of jāti-lakṣaṇas accepted per authority. NOT 10 → 12 by individual # rule of addition: Sārngadeva's set is a strict superset. # evidence: aff#1556 (Bharata = 10), aff#1557 (Mātaṅga = 10), # aff#1558 + R_1257_jati_laksanas_sarngadeva (Sārngadeva = 12). LAKSHANA_COUNT_BY_AUTHORITY: dict[Authority, int] = { Authority.BHARATA: 10, Authority.MATANGA: 10, Authority.SARNGADEVA: 12, } # ----------------------------------------------------------------------------- # Deśī rāgas typology # ----------------------------------------------------------------------------- # Mātaṅga: 3 types of deśī rāgas. Sārngadeva: 4 types (adds upānga). # evidence: aff#1961, aff#1962, desi_raga_types_count. DESI_RAGA_TYPE_COUNT: dict[Authority, int] = { Authority.MATANGA: 3, # aff#1961 Authority.SARNGADEVA: 4, # aff#1962 (adds upānga) } DESI_RAGA_ADDED_BY_SARNGADEVA: str = "upanga" # aff#1962 # ----------------------------------------------------------------------------- # Antaramārga components — the six aṅgas whose mutual connection defines it # ----------------------------------------------------------------------------- # evidence: R_c150_antaramarga (components = {graha, apanyasa, vinyasa, # samnyasa, nyasa, amsa}; mutual connection), # aff#1553, aff#205 (antaramārga + nyāsa manifests jāti). ANTARAMARGA_COMPONENTS: frozenset[str] = frozenset({ "graha", "apanyasa", "vinyasa", "samnyasa", "nyasa", "amsa", }) # ============================================================================= # TYPES — dataclasses # ============================================================================= @dataclass(frozen=True) class Citation: """A reference made by Mātaṅga to a prior authority. The standard form of citation in the Brihaddesi is the formula "Similarly has said Bharata —" followed by a verse, optionally tagged with an NŚ locus. evidence: aff#148, aff#171, aff#182, aff#191, aff#221, aff#312, aff#2150, aff#2468 (canonical "Similarly has said Bharata —" pattern); aff#178, aff#222 (NŚ XXVIII.70 / NŚ XXVIII.72 loci); aff#1873 (citation occurring after Anu. 129). """ citing: Authority # who is making the citation (usually MATANGA) cited: Authority # who is being cited (usually BHARATA) locus: str | None = None # e.g. "NS_XXVIII.72", "NS_XIX", or None topic: str | None = None # bare concept name being cited about def __post_init__(self) -> None: if self.citing == self.cited: raise ValueError( "Citation requires citing != cited " "(self-reference is not a citation)" ) if AUTHORITY_RANK[self.citing] < AUTHORITY_RANK[self.cited]: # rank 0 = most ancient; citing must not be older than cited. raise ValueError( f"{self.citing.value} (rank {AUTHORITY_RANK[self.citing]}) " f"cannot cite {self.cited.value} " f"(rank {AUTHORITY_RANK[self.cited]}): authority order " "violated" ) @dataclass(frozen=True) class Lakshana: """A lakṣaṇa is a defining-characteristic. A treatise IS its set of lakṣaṇas. Each lakṣaṇa has: - a scope (general vs. particular); - a target object (the thing being defined: a jāti, audūvita, etc.); - a name (e.g. "graha", "aṁśa", "tāra"…) when it is itself a feature. evidence: aff#531, aff#540 (two-fold structure), aff#914 (each bhāṣā has lakṣaṇas), aff#1180 (a bhāṣā should be sung in accordance with its lakṣaṇas), R_lakshana_class. """ name: str scope: LakshanaScope target: str # what is being defined ("jati", "audūvita", "bhasha", …) @dataclass(frozen=True) class LakshanaSet: """A set of lakṣaṇas describing one object. Used to compute its completeness status (śuddhā / vikṛtā / incomplete). evidence: aff#107 (alteration of ≥1 lakṣaṇa transforms śuddhā into vikṛtā), aff#1823 (10 lakṣaṇas of jātis), R_lakshanas_jati. """ target: str # e.g. "jati" expected_count: int # canonical N (10 for jāti) present: frozenset[str] # named lakṣaṇas actually specified altered: frozenset[str] = field(default_factory=frozenset) def __post_init__(self) -> None: if self.altered - self.present: raise ValueError( "altered lakṣaṇas must be a subset of present lakṣaṇas: " f"unknown altered={self.altered - self.present}" ) @dataclass(frozen=True) class TaraRange: """The tāra register extending from a starting svara (the aṁśa) by a permitted gati-span (4, 5, or 7). evidence: aff#165 (extent of tāra begins from aṁśa), aff#179 (4th / 5th / 7th from aṁśa), R_64_tara_extent, R_4p0_018, aff#1859 (tāra properly begins from octave of ga). """ amsa: str # bare name of the aṁśa svara (e.g. "sa", "ga"), kept # as str because Svara enum lives in domain 3 span: TaraGatiSpan def __post_init__(self) -> None: if self.span not in (TaraGatiSpan.FOURTH, TaraGatiSpan.FIFTH, TaraGatiSpan.SEVENTH): raise ValueError( f"tāra span must be 4, 5 or 7; got {self.span}" ) @dataclass(frozen=True) class Antaramarga: """The intermediate melodic pathway. Defined by: - mutual connection of the 6 aṅgas (graha, apanyāsa, vinyāsa, samnyāsa, nyāsa, aṁśa); - non-repetition (anabhyāsa) of svaras; - leaping (vilanghana) of svaras; - effect: many svaras become non-aṁśas; with nyāsa it manifests jātis; non-aṁśa svaras may NOT be sparse here. evidence: R_4p1_368, R_c150_antaramarga, aff#205, aff#207, aff#485, aff#1553, aff#1864, aff#1873. """ non_repetition: bool = True # anabhyāsa, aff#1864, R_4p1_368 leaping: bool = True # vilanghana, R_4p1_368 components: frozenset[str] = field( default_factory=lambda: ANTARAMARGA_COMPONENTS ) def __post_init__(self) -> None: # mutual connection requires that all 6 aṅgas be present # (R_c150_antaramarga: "mutual_connection(components)") if not self.components.issuperset(ANTARAMARGA_COMPONENTS): missing = ANTARAMARGA_COMPONENTS - self.components raise ValueError( f"Antaramārga requires all 6 aṅgas in mutual connection; " f"missing: {sorted(missing)}" ) @dataclass(frozen=True) class SandhiAssignment: """An assignment of a rāga/svara-class to a dramatic juncture, per Bharata's prescription. evidence: aff#614, aff#625, aff#626, aff#627, aff#632, r_brd_381_nirvahana_use. """ sandhi: DramaSandhi raga: str attributed_to: Authority = Authority.BHARATA # ============================================================================= # OPÉRATIONS — dérivations exécutables # ============================================================================= def lakshana_completeness(ls: LakshanaSet) -> LakshanaCompleteness: """Classify a LakshanaSet as śuddhā / vikṛtā / incomplete. Rule (aff#107, R_lakshanas_jati): - if fewer than expected_count lakṣaṇas are present → INCOMPLETE - elif any lakṣaṇa is altered → VIKRTA - else → SUDDHA evidence: aff#107 ("alteration of one, two or many lakṣaṇas transforms śuddhā into vikṛtā"), R_lakshanas_jati, R_lakshana_class. """ if len(ls.present) < ls.expected_count: return LakshanaCompleteness.INCOMPLETE if ls.altered: return LakshanaCompleteness.VIKRTA return LakshanaCompleteness.SUDDHA def authority_strength(claim_attributed_to: Authority) -> int: """Return a stratification rank for a claim's authority. Lower = older = stronger in the Brihaddesi's epistemic order (Bharata = 0). Use case: when two claims conflict, the one attributed to the lower-rank authority prevails (or at minimum, must be addressed first). evidence: AUTHORITY_RANK (see its citations: aff#537, aff#1556, aff#1557, aff#1558, aff#3108). """ return AUTHORITY_RANK[claim_attributed_to] def resolve_authority_conflict( claim_a: tuple[str, Authority], claim_b: tuple[str, Authority], ) -> tuple[str, Authority]: """Given two conflicting claims, return the one whose authority is more senior (lower rank = older/ultimate). This does NOT prove the claim correct — it only reflects the text's own epistemic ordering, in which Bharata's word is the touchstone. Tie-breaker: same rank → returns claim_a unchanged (caller must mark as UNRESOLVED). evidence: aff#537 (Bharata as prior authority contrasted with Mātaṅga), aff#3108 (Kumbha contradicts Mātaṅga, no resolution offered), aff#1931 (Bharata's text doesn't say X — used to challenge a claim). """ rank_a = AUTHORITY_RANK[claim_a[1]] rank_b = AUTHORITY_RANK[claim_b[1]] return claim_a if rank_a <= rank_b else claim_b def cite_chain(citations: Iterable[Citation]) -> list[Authority]: """Return the linear chain of authorities walked by a list of citations, in the order they cite (citing → cited). Example: [Mātaṅga cites Bharata] → [BHARATA, MATANGA]-reversed-walk = [MATANGA, BHARATA]. evidence: standard citation pattern (aff#171, aff#182, aff#191, aff#221, aff#312, aff#2150, aff#2468 — "Similarly has said Bharata —"), aff#2929 (Simhabhūpāla cites Mātaṅga: chained citation). """ chain: list[Authority] = [] for c in citations: if not chain: chain.append(c.citing) elif chain[-1] != c.citing: # not a continuous chain — caller is composing distinct citations chain.append(c.citing) chain.append(c.cited) return chain def sandhi_for_raga(raga: str) -> DramaSandhi | None: """Inverse lookup: which sandhi does Bharata assign to this rāga? evidence: SANDHI_RAGA_ASSIGNMENT (see its evidence affirmations). Returns None if rāga is not among the 5 pinned cases. """ for sandhi, assigned in SANDHI_RAGA_ASSIGNMENT.items(): if assigned == raga: return sandhi return None def raga_for_sandhi(sandhi: DramaSandhi) -> str: """Forward lookup: which rāga does Bharata assign to this sandhi? evidence: SANDHI_RAGA_ASSIGNMENT. """ return SANDHI_RAGA_ASSIGNMENT[sandhi] def tara_endpoint_index(amsa_index: int, span: TaraGatiSpan) -> int: """Compute the absolute svara-position reached by tāra extension from the aṁśa. The aṁśa is at position `amsa_index`; tāra ends `span` positions above (where the count is inclusive of the aṁśa per R_4p0_018: "from the use of aṁśa svara"). evidence: R_4p0_018 ("ascent from aṁśa to the fifth svara above"), aff#165, aff#179. """ return amsa_index + int(span) - 1 def jati_lakshana_set_for_authority( authority: Authority, present: Iterable[str], altered: Iterable[str] = (), ) -> LakshanaSet: """Build a LakshanaSet for a jāti according to the count accepted by `authority` (10 for Bharata/Mātaṅga, 12 for Sārngadeva). evidence: LAKSHANA_COUNT_BY_AUTHORITY, R_1257_jati_laksanas_sarngadeva, R_lakshanas_jati. """ if authority not in LAKSHANA_COUNT_BY_AUTHORITY: raise ValueError( f"Lakṣaṇa count not specified for {authority.value} in domain 9" ) return LakshanaSet( target="jati", expected_count=LAKSHANA_COUNT_BY_AUTHORITY[authority], present=frozenset(present), altered=frozenset(altered), ) # ============================================================================= # CONTRAINTES — validations # ============================================================================= def is_well_formed_sandhi_sequence(seq: Iterable[DramaSandhi]) -> bool: """A well-formed dramatic exposition presents the five sandhis in their canonical ordinal order (1 → 2 → 3 → 4 → 5). evidence: R_272_sandhis_enum (enumeration in order), aff#1903 (garbha = 3rd, mukha+pratimukha = first two, vimarsa+ nirvahana = last two), aff#1930. """ seq = list(seq) if not seq: return True ordinals = [SANDHI_ORDINAL[s] for s in seq] return all(ordinals[i] < ordinals[i + 1] for i in range(len(ordinals) - 1)) def is_consistent_lakshana_count(target: str, n: int) -> bool: """Verify that a claimed lakṣaṇa-count for a target matches the Brihaddesi's canonical count. Returns True for unknown targets (under-constrained). evidence: LAKSHANA_COUNT (see citations). """ expected = LAKSHANA_COUNT.get(target) if expected is None: return True return n == expected def citation_respects_authority_order(c: Citation) -> bool: """A citation in the Brihaddesi is always: a younger authority citing an older one. Bharata is never cited as citing anyone (he is rank 0). evidence: aff#537 (Mātaṅga sets himself "after" Bharata), AUTHORITY_RANK (cf. aff#3108 — even when Kumbha contradicts Mātaṅga, Mātaṅga remains the cited senior in that pair). """ return AUTHORITY_RANK[c.citing] > AUTHORITY_RANK[c.cited] def is_valid_tara_span(span: int) -> bool: """True iff `span` is one of the three permitted tāra extents. evidence: R_64_tara_extent (∈ {4,5,7}), aff#179. """ return span in (int(TaraGatiSpan.FOURTH), int(TaraGatiSpan.FIFTH), int(TaraGatiSpan.SEVENTH)) # ============================================================================= # UNRESOLVED — concepts mentionnés mais non formellement épinglés # ============================================================================= UNRESOLVED: tuple[dict[str, str], ...] = ( { "concept": "the 6 unnamed jāti-lakṣaṇas", "reason": "R_lakshanas_jati and aff#1823 explicitly enumerate only " "4 of 10 (graha, aṁśa, tāra, mandra) and trail off with " "'etc.' — the remaining 6 names are not pinned by any " "domain-9 rule.", }, { "concept": "tāra span = 6 svaras", "reason": "aff#154 mentions 'sometimes even the ascent up to the " "sixth svara is tāra' as a softer variant. R_64_tara_extent " "and aff#179 give canonical {4,5,7}. Not retained as a " "valid TaraGatiSpan member — would conflict with the " "'threefold extent' claim of aff#167.", }, { "concept": "the other three vṛttis (kaiśikī, ārabhaṭī, sāttvatī)", "reason": "Bhāratī is the first of four (aff#1993, r_brd_457_" "bharati_def) but the other three are not pinned by any " "domain-9 rule — only Bhāratī is enumerated.", }, { "concept": "jāti-lakṣaṇa altered-feature semantics", "reason": "aff#107 says alteration of '1, 2, or many' lakṣaṇas " "yields vikṛtā — but no rule specifies WHICH alteration " "produces WHICH named vikṛti, so the mapping vikṛtā-name " "↔ altered-set is not pinned.", }, { "concept": "Mātaṅga's seven gītis vs. Kasyapa", "reason": "aff#543 attributes seven gītis to Mātaṅga; aff#1595 " "records that 'Mātaṅga' was replaced with 'Kasyapa' in " "the printed text — disputed authorship, not modelled.", }, { "concept": "antara/kākalī starting mūrchanā", "reason": "aff#3108: Mātaṅga accepts mūrchanās starting on antara/" "kākalī, Kumbha later denies it without acknowledging " "Mātaṅga's position. Disagreement not resolved by any " "rule.", }, { "concept": "NŚ XXVIII.72 as a free-standing citation node", "reason": "cluster_id=716 is a bare locus with 2 citation " "affirmations (aff#178 NS XXVIII.70, aff#222 NS XXVIII.72) " "but no rule binds it to any operation in domain 9.", }, { "concept": "dhvani theory ↔ Mātaṅga influence", "reason": "aff#2993 attributes a philosophical lineage from Mātaṅga " "to dhvani theory in literature — outside the operational " "scope of a formal grammar; recorded for traceability.", }, ) # ============================================================================= # Self-test # ============================================================================= if __name__ == "__main__": # Authorities & ranks assert AUTHORITY_RANK[Authority.BHARATA] == 0 assert AUTHORITY_RANK[Authority.MATANGA] == 1 assert (AUTHORITY_RANK[Authority.SARNGADEVA] > AUTHORITY_RANK[Authority.MATANGA]) # Citation: Mātaṅga citing Bharata is valid; reverse is invalid c1 = Citation( citing=Authority.MATANGA, cited=Authority.BHARATA, locus="NS_XXVIII.72", topic="nyasa_apanyasa", ) assert citation_respects_authority_order(c1) try: Citation(citing=Authority.BHARATA, cited=Authority.MATANGA) assert False, "Bharata cannot cite Mātaṅga (rank order violated)" except ValueError: pass try: Citation(citing=Authority.BHARATA, cited=Authority.BHARATA) assert False, "Citation requires citing != cited" except ValueError: pass # Authority strength a = ("dhaivata may be omitted in sadjagrama", Authority.MATANGA) b = ("dhaivata may NOT be omitted in sadjagrama", Authority.BHARATA) winner = resolve_authority_conflict(a, b) assert winner[1] == Authority.BHARATA, "Bharata wins on rank" # Cite chain chain = cite_chain([c1]) assert chain == [Authority.MATANGA, Authority.BHARATA] # Lakshana completeness ls_full_pure = jati_lakshana_set_for_authority( Authority.BHARATA, present=["graha", "amsa", "tara", "mandra", "nyasa", "apanyasa", "sancara", "graha2", "alpatva", "bahutva"], ) assert lakshana_completeness(ls_full_pure) == LakshanaCompleteness.SUDDHA ls_full_altered = jati_lakshana_set_for_authority( Authority.BHARATA, present=["graha", "amsa", "tara", "mandra", "nyasa", "apanyasa", "sancara", "graha2", "alpatva", "bahutva"], altered=["amsa"], ) assert lakshana_completeness(ls_full_altered) == LakshanaCompleteness.VIKRTA ls_incomplete = jati_lakshana_set_for_authority( Authority.BHARATA, present=["graha", "amsa"], ) assert lakshana_completeness(ls_incomplete) == LakshanaCompleteness.INCOMPLETE # Sārngadeva expects 12, not 10 ls_sarnga_only_10 = jati_lakshana_set_for_authority( Authority.SARNGADEVA, present=["graha", "amsa", "tara", "mandra", "nyasa", "apanyasa", "sancara", "graha2", "alpatva", "bahutva"], ) assert lakshana_completeness(ls_sarnga_only_10) == \ LakshanaCompleteness.INCOMPLETE ls_sarnga_full = jati_lakshana_set_for_authority( Authority.SARNGADEVA, present=["graha", "amsa", "tara", "mandra", "nyasa", "apanyasa", "sancara", "graha2", "alpatva", "bahutva", "samnyasa_vinyasa", "antaramarga"], ) assert lakshana_completeness(ls_sarnga_full) == LakshanaCompleteness.SUDDHA # Sandhi ordering assert is_well_formed_sandhi_sequence([ DramaSandhi.MUKHA, DramaSandhi.PRATIMUKHA, DramaSandhi.GARBHA, DramaSandhi.VIMARSA, DramaSandhi.NIRVAHANA, ]) assert not is_well_formed_sandhi_sequence([ DramaSandhi.NIRVAHANA, DramaSandhi.MUKHA, ]) # Sandhi assignment lookups assert raga_for_sandhi(DramaSandhi.NIRVAHANA) == "suddhakaisikamadhya_raga" assert sandhi_for_raga("madhyamagrama_raga") == DramaSandhi.MUKHA assert sandhi_for_raga("not_a_known_raga") is None # Tāra range tr = TaraRange(amsa="sa", span=TaraGatiSpan.FIFTH) assert tr.span == TaraGatiSpan.FIFTH assert is_valid_tara_span(7) assert not is_valid_tara_span(6) # see UNRESOLVED # If aṁśa is position 0, span 5 reaches position 4 (5th svara inclusive) assert tara_endpoint_index(0, TaraGatiSpan.FIFTH) == 4 # Antaramārga: must include all 6 aṅgas am = Antaramarga() assert am.non_repetition and am.leaping try: Antaramarga(components=frozenset({"graha", "amsa"})) assert False, "Antaramārga requires all 6 components" except ValueError: pass # Lakṣaṇa count consistency assert is_consistent_lakshana_count("jati", 10) assert not is_consistent_lakshana_count("jati", 12) # Bharata/Mātaṅga assert is_consistent_lakshana_count("audūvita", 5) assert not is_consistent_lakshana_count("audūvita", 4) assert is_consistent_lakshana_count("unknown_target", 99) # under-constrained # Sandhi ordinals (cross-check enum vs. dict) for s in DramaSandhi: assert SANDHI_ORDINAL[s] in (1, 2, 3, 4, 5) assert SANDHI_ORDINAL[DramaSandhi.NIRVAHANA] == 5 assert SANDHI_ORDINAL[DramaSandhi.GARBHA] == 3 # Deśī rāga type counts assert DESI_RAGA_TYPE_COUNT[Authority.MATANGA] == 3 assert DESI_RAGA_TYPE_COUNT[Authority.SARNGADEVA] == 4 print("authority_lakshana.py — all self-tests pass")