← domain #10

Source : treatise_meta.py

data/library/books/brihaddesi_sharma_1992/formal_grammar/treatise_meta.py · 659 lines · 25253 bytes
"""Domaine 10 — treatise-meta (BRHADDEŚĪ / Nāda / Rāgalakṣaṇa / Saṅkarailā / Nrtta) Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II), domaine méta-textuel : identité réflexive du traité, théorie du Nāda comme principe ontologique upstream, Rāgalakṣaṇa (spécification de rāga), Saṅkarailā (elā mixte), Nrtta (danse pure), références croisées (SR = Saṅgītaratnākara, etc.). Anti-fabrication stricte : chaque type, constante, opération et contrainte cite ≥1 evidence (rule_id 6b ou affirmation_id de la DB pipeline). Aucune valeur plausible-mais-non-sourcée n'est introduite — voir UNRESOLVED. Ce domaine est essentiellement déclaratif (52 affirmations, 18 règles, 16 concepts-avec-règles). Beaucoup de concepts ont 0 règle directe ; cela reflète la nature méta-textuelle du domaine (citations, identifiants manuscrits, attributions). Les opérations modélisent les questions méta exploitables (is_brihaddesi_topic, requires_ragalakshana_components, nada_origin_chain, classify_ela, etc.). Python 3.10+. Importable directement, sans dépendance externe. """ from __future__ import annotations from dataclasses import dataclass, field from enum import Enum # ============================================================================= # IDENTITÉ DU TRAITÉ — métadonnées # ============================================================================= # evidence: brhaddesi_treatise_metadata (rule), aff#2018, aff#2020, aff#2022, # aff#2064, aff#2079, aff#2080 TREATISE_TITLE_IAST: str = "Bṛhaddeśī" TREATISE_AUTHOR_IAST: str = "Mātaṅga Muni" TREATISE_FIRST_PUBLISHED: int = 1992 TREATISE_PUBLISHER: str = "IGNCA" # Indira Gandhi National Centre for the Arts # aff#72: dated approximately 750 CE (attribution — circa, not pinned). TREATISE_DATE_CE_CIRCA: int = 750 # affirmation_id=72 ("approximately 750 CE") # evidence: aff#57 (etymology), R_7_brhaddesi_structure TREATISE_ETYMOLOGY_GLOSS: str = ( "Bṛhad + deśī — the 'great deśī [text]', a treatise systematically " "classifying regional/desi musical phenomena." ) # ============================================================================= # STRUCTURE EN SECTIONS — table verbatim de Sharma vol. I p.007 # ============================================================================= # evidence: R_7_brhaddesi_structure (rule), aff#2064 (six-section ToC quote), # aff#2070..aff#2074 (sections I-V individually attested), # aff#2849 (section VI = Varṇālaṅkāra), aff#2068+aff#2868 (a Pada-gīti # section VII is mentioned by editor — explicitly OUTSIDE the # canonical 6-section list per aff#2064 / R_7). # # The rule R_7 closes the structure at six sections. Pada-gīti (VII) is # editorial / manuscript-tradition extension; we keep it tagged separately to # preserve provenance. class Section(str, Enum): """The six canonical sections of the Brihaddesi per Sharma 1992 ToC. evidence: R_7_brhaddesi_structure, aff#2064 """ DESI = "desi" # I — aff#2070 NADA = "nada" # II — aff#2071 SRUTI = "sruti" # III — aff#2072 SVARA = "svara" # IV — aff#2073 GRAMA_MURCHANA = "grama_murchana" # V — aff#2074 VARNA_ALANKARA = "varna_alankara" # VI — aff#2849 CANONICAL_SECTIONS: tuple[Section, ...] = ( Section.DESI, Section.NADA, Section.SRUTI, Section.SVARA, Section.GRAMA_MURCHANA, Section.VARNA_ALANKARA, ) # Extra-canonical editorial section attested in the manuscript tradition. # evidence: aff#2068 ("Pada-gīti constitutes Section VII"), aff#2868, # aff#2789. Not in R_7's canonical six. EDITORIAL_SECTION_PADA_GITI: str = "pada_giti" # Chapter-level structural assertions (within sections / cross-cutting). # evidence per entry below. CHAPTERS: dict[int, dict[str, str]] = { 3: { # R_656_ragalakshana_chapter (aff#74), ragalakshana_chapter_iii # (aff#1593), aff#860 "name_iast": "Rāgalakṣaṇa", "topic": "nature and definition of rāga", "evidence": "R_656_ragalakshana_chapter; ragalakshana_chapter_iii; aff#860", }, 5: { # R_1076_desiragadhyaya_chapter5 (aff#1294) "name_iast": "deśī-rāgādhyāya", "topic": "treatment of deśī rāgas", "evidence": "R_1076_desiragadhyaya_chapter5; aff#1294", }, 6: { # aff#1525 ("Thus ends the sixth chapter") "name_iast": "(sixth chapter, unnamed in this assertion)", "topic": "concluding chapter as attested by colophon", "evidence": "aff#1525", }, } # Nāda-prakaraṇa internal topic list (section II micro-structure). # evidence: R_c1619_nadaprakaranam_struct (aff#2107), aff#2055 NADAPRAKARANA_TOPICS: tuple[str, ...] = ( "prashamsa", # glory "utpatti", # origin "lakshanam", # definition "bhedah", # kinds ) # ============================================================================= # TÉMOIGNAGE MANUSCRIT — MS A, MS B, P.t. # ============================================================================= # evidence: aff#2875 (MS A, MS B, P.t. mentioned together as variant sources), # aff#1562 (MS A), aff#2865 (MS B), aff#2875 (P.t.) class ManuscriptWitness(str, Enum): """Manuscript / editorial witnesses cited in Sharma's apparatus. evidence: aff#2875 (joint mention), aff#1562 (MS A), aff#2865 (MS B) P.t. is attested in aff#2875 and recurs in apparatus footnotes; its full expansion is not given in the affirmations corpus — see UNRESOLVED. """ MS_A = "MS_A" MS_B = "MS_B" PT = "P.t." # ============================================================================= # CROSS-REFERENCES — autres traités cités # ============================================================================= # evidence per entry. The biblio is non-extensible — we only list treatises # the Brihaddesi (or Sharma's edition apparatus) actually cites. @dataclass(frozen=True) class TreatiseReference: """A cross-reference from / about the Brihaddesi to another treatise. evidence: per-entry below. """ sigil: str # short label as used in Sharma's apparatus full_name_iast: str | None role: str # e.g. 'authority', 'cited_definition', 'variant_recension' evidence: str # affirmation_id / rule_id chain CROSS_REFERENCES: tuple[TreatiseReference, ...] = ( TreatiseReference( sigil="SR", full_name_iast="Saṅgītaratnākara", role="later_treatise_citing_brihaddesi_concepts", # aff#1607 (SR II.2.25-26 defines Ākṣiptikā), # aff#1791 (SR IV.232), aff#3067 (cakra/svara correspondence) evidence="aff#1607; aff#1791; aff#3067", ), TreatiseReference( sigil="NS", full_name_iast="Nāṭyaśāstra", role="authority_with_variant_recension", # aff#2871: 'cf. NŚ XXIX,44-48 and the variant recension ... 75-78' evidence="aff#2871", ), TreatiseReference( sigil="Saktikāgama", full_name_iast="Śaktikāgama", role="cited_for_identification", # aff#2900: Saktikāgama cited re Sakti = vowel ikāra evidence="aff#2900", ), # Sabdakalpadruma (cluster 1983) appears as a concept but no affirmation # body pins its role — see UNRESOLVED. ) # ============================================================================= # NĀDA — principe ontologique upstream # ============================================================================= # evidence: R_nada_def, aff#2108..aff#2113, aff#2115, aff#2119 class NadaOrigin(str, Enum): """Posited origins of nāda. The Brihaddesi gives one primary statement (fire+air) and explicitly notes alternative authorities — aff#2119. evidence: aff#2113 (vahni+māruta), aff#2119 ('iti kecit ... ity anye vadanti') """ VAHNI_MARUTA = "vahni_maruta" # fire + air combination — aff#2113 KANDA_STHANA = "kanda_sthana" # alt: 'kandasthāna-samuttha samīra' — aff#2119 UNSPECIFIED = "unspecified" # for assertions that don't pin an origin # Per aff#2925 (R_5p3_2036), in the context of nāda-production specifically, # 'guhā' (lit. cave) refers to the navel, not the generic 'cavity of the heart'. NADA_PRODUCTION_GUHA_LOCUS: str = "navel" NADA_PRODUCTION_GUHA_NOTE: str = ( "Guhā in nāda-production context = navel (first point); generic sense " "elsewhere = cavity of the heart. evidence: aff#2925, R_5p3_2036" ) # Things that necessarily depend on nāda — "X cannot exist without nāda". # Closed enumeration from the source verses on p.012. # evidence: aff#2108 (gīta), aff#2109 (svara), aff#2110 (nṛtta), # aff#2111 (jagat), aff#2115 (vāṅmaya) NADA_DEPENDENTS: frozenset[str] = frozenset({ "gita", # music — aff#2108 "svara", # musical notes — aff#2109 "nrtta", # dance — aff#2110 "jagat", # world — aff#2111 ('tasmāt nādātmakam jagat') "vanmaya", # all speech/language — aff#2115, R_524_vanmaya_rel }) # Entities characterized as nāda-rūpa (form-of-nāda). # evidence: R_2017_nada_rupa_four_deities (aff#2916), R_2016_para_sakti_ultimate NADA_RUPA_ENTITIES: tuple[str, ...] = ( "Brahmā", "Śiva", "Viṣṇu", "Parā Śakti", ) # ============================================================================= # RĀGALAKṢAṆA — spécification de rāga # ============================================================================= # evidence: R_656_ragalakshana_chapter, ragalakshana_chapter_iii (aff#1593), # aff#74, aff#860 @dataclass(frozen=True) class RagalakshanaChapterMeta: """Metadata about the Rāgalakṣaṇa chapter (chapter III). The affirmations in this domain give us only the *chapter assignment*, not the internal component list of rāga-lakṣaṇa (that belongs to other domains — jāti, mūrchanā, varṇa, etc.). So `required_components` is deliberately left empty here; component-level rules live in the melodic and structural domains. See UNRESOLVED. evidence: R_656_ragalakshana_chapter, ragalakshana_chapter_iii """ chapter_number: int = 3 name_iast: str = "Rāgalakṣaṇa" topic: str = "nature and definition of rāga" required_components: tuple[str, ...] = () # deliberately empty — UNRESOLVED RAGALAKSHANA_META = RagalakshanaChapterMeta() # ============================================================================= # ŚAṬPADĪ / RAMAṆĪ / SAṄKARAILĀ — formes structurelles méta # ============================================================================= # evidence: R_1170_01 (Saṅkarailā), R_1196_ramani_satpadi, R_1201_nadadhya_ramani class YatiType(str, Enum): """Yati (caesura/pause pattern) types attested for ṣaṭpadī feet. evidence: R_1196_ramani_satpadi (samā yati attested for ramaṇī) Other yati types exist in tradition but are not pinned by domain-10 rules. """ SAMA = "sama" # equal — aff#1473 @dataclass(frozen=True) class Shatpadi: """A six-footed (ṣaṭ-pad-ī) metrical / melodic structure. The treatise enumerates several named ṣaṭpadīs; this dataclass models the generic structure. Specific named subtypes (e.g. ramaṇī) are validated by predicate functions below. evidence: R_1196_ramani_satpadi (six feet stated explicitly) """ feet: tuple[dict, ...] # 6 feet, each described by a dict of attributes def __post_init__(self) -> None: if len(self.feet) != 6: raise ValueError( f"Shatpadi must have exactly 6 feet, got {len(self.feet)}" ) @dataclass(frozen=True) class Ela: """An elā (melodic form) with 4 feet, whose 4th foot's elaboration relative to the 3rd determines its sub-classification. The 4-foot structure is implicit in R_1170_01 (it references "the fourth foot" and "the third foot"). evidence: R_1170_01 (aff#1441), aff#1442 """ feet_elaboration: tuple[str, ...] # length 4 — sketch of each foot's elaboration def __post_init__(self) -> None: if len(self.feet_elaboration) != 4: raise ValueError( f"Ela must have 4 feet, got {len(self.feet_elaboration)}" ) # ============================================================================= # NRTTA — danse pure # ============================================================================= # evidence: nrtta_definition_and_nada_dependency (aff#2909, aff#2910) @dataclass(frozen=True) class NrttaSpec: """Pure dance specification. evidence: nrtta_definition_and_nada_dependency """ pure_dance: bool = True # no artha-of-kāvya intent — aff#2909 depends_on_nada: bool = True # accompanied on instruments — aff#2910 accompaniment_emphasis: tuple[str, ...] = ("drums",) # aff#2910 ('specially drums') NRTTA = NrttaSpec() # ============================================================================= # OPÉRATIONS — questions méta exploitables # ============================================================================= def is_brihaddesi_topic(section: Section) -> bool: """True iff `section` is one of the six canonical Brihaddesi sections. evidence: R_7_brhaddesi_structure, aff#2064 """ return section in CANONICAL_SECTIONS def section_of_chapter(chapter_number: int) -> str | None: """Return chapter name if pinned in the corpus, else None. Note: the Brihaddesi's section/chapter numbering is partly conflated in the apparatus. Only chapters 3, 5, 6 have explicit colophons in the affirmations corpus — see UNRESOLVED for 1, 2, 4, 7. evidence: CHAPTERS dict per-entry. """ entry = CHAPTERS.get(chapter_number) if entry is None: return None return entry["name_iast"] def nada_origin_chain(target: str) -> tuple[str, ...]: """For `target` ∈ NADA_DEPENDENTS, return the upstream chain [target ← nāda ← origin]. Pure declarative chain; does not invent intermediate causes. evidence: R_nada_def, aff#2108..aff#2115 """ if target not in NADA_DEPENDENTS: return () return (target, "nada", NadaOrigin.VAHNI_MARUTA.value) def is_nada_rupa(entity: str) -> bool: """True iff `entity` is one of the four deities characterized as nāda-rūpa per R_2017_nada_rupa_four_deities (aff#2916). """ return entity in NADA_RUPA_ENTITIES def classify_ela(third_elab: str, fourth_elab: str) -> str: """Per R_1170_01 / aff#1441: an elā becomes saṅkara (mixed) when the fourth foot is elaborated like the third foot. Returns 'sankarailā' or 'unmarked_ela'. We do NOT enumerate other elā sub-types here — they belong to other domains. evidence: R_1170_01 """ if third_elab == fourth_elab: return "sankarailā" return "unmarked_ela" def is_ramani(form: Shatpadi) -> bool: """True iff `form` is a ramaṇī: ṣaṭpadī with samā yati in feet 1 and 2 AND every foot adorned with nāda (nādāḍhyā). evidence: R_1196_ramani_satpadi (aff#1473), R_1201_nadadhya_ramani (aff#1480) """ if len(form.feet) != 6: return False f1, f2 = form.feet[0], form.feet[1] if f1.get("yati") != YatiType.SAMA.value: return False if f2.get("yati") != YatiType.SAMA.value: return False return all(f.get("adorned_by_nada") is True for f in form.feet) def pancamanyasa_origin() -> str: """Per R_1280_01 (aff#1608): pañcamanyāsa originates from Kaiśikī jāti. Pure lookup — no inference. """ return "Kaiśikī_jāti" def is_sadjamsa_pancamanyasa_compound(term_a: str, term_b: str) -> bool: """Per R_1281_sadjamsa_pancamanyasa_compound (aff#1609): ṣadjāṁśaḥ pairs as a compound with pañcamanyāsaḥ (textual emendation). """ pair = frozenset({term_a, term_b}) return pair == frozenset({"sadjamsa", "pancamanyasa"}) def has_textual_overlap_with_sadjamadhyama(host_concept: str) -> bool: """Per sadjodicyava_textual_overlap_with_sadjamadhyama (aff#1592): the description of ṣaḍjodīcyavā contains material pertaining to ṣaḍjamadhyamā. Closed assertion — only ṣaḍjodīcyavā is the documented host. """ return host_concept == "sadjodicyava" def requires_ragalakshana_components() -> tuple[str, ...]: """Return the list of components that a rāga-lakṣaṇa specification must contain. Returns empty tuple here: the domain-10 rules pin the *chapter location* of Rāgalakṣaṇa (chapter III) and its *topic* (nature/definition of rāga), but do NOT enumerate its component slots — those live in jāti / mūrchanā / varṇa-alaṅkāra domains. See UNRESOLVED. evidence: R_656_ragalakshana_chapter, ragalakshana_chapter_iii """ return RAGALAKSHANA_META.required_components def cite_treatise(sigil: str) -> TreatiseReference | None: """Look up a treatise cross-reference by short sigil. evidence: CROSS_REFERENCES (per-entry) """ for ref in CROSS_REFERENCES: if ref.sigil == sigil: return ref return None # ============================================================================= # CONTRAINTES — validations # ============================================================================= def is_valid_canonical_section(section: Section) -> bool: """A section is canonical iff it is one of the six in R_7's list. evidence: R_7_brhaddesi_structure """ return section in CANONICAL_SECTIONS def is_valid_nada_dependent(claim_target: str) -> bool: """True iff `claim_target` is in the closed set of nāda-dependents explicitly asserted by the source verses on p.012. evidence: aff#2108..aff#2115 """ return claim_target in NADA_DEPENDENTS def is_valid_nadaprakarana_topic(topic: str) -> bool: """Per R_c1619_nadaprakaranam_struct, only 4 topics are listed for nāda-prakaraṇa: prashamsa, utpatti, lakshanam, bhedah. evidence: R_c1619_nadaprakaranam_struct (aff#2107) """ return topic in NADAPRAKARANA_TOPICS def is_valid_manuscript_witness(label: str) -> bool: """True iff `label` is one of the three manuscript witnesses attested. evidence: aff#2875 """ return label in {m.value for m in ManuscriptWitness} # ============================================================================= # UNRESOLVED — concepts mentionnés mais non formellement contraints # ============================================================================= UNRESOLVED: tuple[dict[str, str], ...] = ( { "concept": "Section VII (Pada-gīti)", "reason": "Attested in editorial apparatus (aff#2068, aff#2868, " "aff#2789) but explicitly OUTSIDE R_7_brhaddesi_structure's " "canonical six-section list. Modelled as EDITORIAL_SECTION_" "PADA_GITI without being added to CANONICAL_SECTIONS.", }, { "concept": "P.t. (manuscript expansion)", "reason": "Attested as a witness sigil (aff#2875, plus apparatus " "footnotes), but the full expansion of 'P.t.' is not given " "in the affirmations corpus.", }, { "concept": "Rāgalakṣaṇa component slots", "reason": "R_656 / ragalakshana_chapter_iii pin chapter III's topic " "as 'nature and definition of rāga' but do NOT enumerate " "the slots a rāga-lakṣaṇa must fill (graha, aṁśa, nyāsa, " "tāra, mandra, alpatva, bahutva, etc. live in other " "domains). requires_ragalakshana_components() returns ().", }, { "concept": "Chapters 1, 2, 4, 7 names", "reason": "Only chapters 3 (Rāgalakṣaṇa), 5 (deśī-rāgādhyāya), 6 " "(unnamed in colophon aff#1525) have explicit name " "assertions in this domain. Others not in CHAPTERS dict.", }, { "concept": "BŖHADDEŚĪ vs BRHADDEŚĪ (cluster 169 vs 2)", "reason": "Two cluster forms — BRHADDEŚĪ (cluster 2, 64 occurrences) " "and BŖHADDEŚĪ (cluster 169, 6 occurrences) — refer to the " "same treatise. Domain 10 keeps both; the canonical IAST is " "Bṛhaddeśī.", }, { "concept": "Nāda — alternative origin (kandasthāna-samīra)", "reason": "aff#2119 explicitly records dissenting authorities: 'iti " "kecit ... ity anye vadanti'. The primary origin " "(vahni+māruta, aff#2113) is what R_nada_def fixes; the " "alternative is modelled in NadaOrigin enum but not in " "nada_origin_chain() output, by design (the rule pins one).", }, { "concept": "Sabdakalpadruma role", "reason": "Sabdakalpadruma (cluster 1983) appears as a concept but no " "affirmation describes its role w.r.t. the Brihaddesi.", }, { "concept": "Other ṣaṭpadī subtypes (padmini, mohini, etc.)", "reason": "Clusters 580 (padmini) and 649 (Mohini) appear with zero " "affirmations in domain 10. ramaṇī is the only ṣaṭpadī " "with a complete structural rule (R_1196).", }, ) # ============================================================================= # Self-test # ============================================================================= if __name__ == "__main__": # Sanity: canonical sections assert len(CANONICAL_SECTIONS) == 6 assert all(is_brihaddesi_topic(s) for s in CANONICAL_SECTIONS) assert is_valid_canonical_section(Section.NADA) # Sanity: chapter lookup assert section_of_chapter(3) == "Rāgalakṣaṇa" assert section_of_chapter(5) == "deśī-rāgādhyāya" assert section_of_chapter(42) is None # not pinned # Sanity: nāda origin chain chain = nada_origin_chain("gita") assert chain == ("gita", "nada", "vahni_maruta"), chain assert nada_origin_chain("svara")[1] == "nada" assert nada_origin_chain("not_a_dependent") == () assert is_valid_nada_dependent("nrtta") assert is_valid_nada_dependent("jagat") assert not is_valid_nada_dependent("raga") # not in the closed list # Sanity: nāda-rūpa assert is_nada_rupa("Brahmā") assert is_nada_rupa("Parā Śakti") assert not is_nada_rupa("Indra") # Sanity: nāda-prakaraṇa topics for t in NADAPRAKARANA_TOPICS: assert is_valid_nadaprakarana_topic(t) assert not is_valid_nadaprakarana_topic("madhura") assert len(NADAPRAKARANA_TOPICS) == 4 # Sanity: ela classification (R_1170_01) assert classify_ela("X", "X") == "sankarailā" assert classify_ela("X", "Y") == "unmarked_ela" # Sanity: ramaṇī predicate ramani_form = Shatpadi(feet=( {"yati": "sama", "adorned_by_nada": True}, {"yati": "sama", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, )) assert is_ramani(ramani_form) not_ramani = Shatpadi(feet=( {"yati": "other", "adorned_by_nada": True}, {"yati": "sama", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, {"yati": "other", "adorned_by_nada": True}, )) assert not is_ramani(not_ramani) # Sanity: Ela structural constraint e = Ela(feet_elaboration=("a", "b", "c", "c")) assert classify_ela(e.feet_elaboration[2], e.feet_elaboration[3]) == "sankarailā" # Sanity: textual overlap assertion assert has_textual_overlap_with_sadjamadhyama("sadjodicyava") assert not has_textual_overlap_with_sadjamadhyama("sadjagrama") # Sanity: pañcamanyāsa origin assert pancamanyasa_origin() == "Kaiśikī_jāti" # Sanity: sadjamsa-pancamanyasa compound assert is_sadjamsa_pancamanyasa_compound("sadjamsa", "pancamanyasa") assert is_sadjamsa_pancamanyasa_compound("pancamanyasa", "sadjamsa") assert not is_sadjamsa_pancamanyasa_compound("sadjamsa", "madhyamsa") # Sanity: cross-references sr = cite_treatise("SR") assert sr is not None and sr.full_name_iast == "Saṅgītaratnākara" ns = cite_treatise("NS") assert ns is not None and "Nāṭyaśāstra" in ns.full_name_iast assert cite_treatise("Foo") is None # Sanity: manuscript witnesses for w in ("MS_A", "MS_B", "P.t."): assert is_valid_manuscript_witness(w) assert not is_valid_manuscript_witness("MS_C") # Sanity: Rāgalakṣaṇa assert RAGALAKSHANA_META.chapter_number == 3 assert requires_ragalakshana_components() == () # deliberately empty # Sanity: Nrtta assert NRTTA.pure_dance is True assert NRTTA.depends_on_nada is True assert "drums" in NRTTA.accompaniment_emphasis # Sanity: shape constraints try: Shatpadi(feet=({},) * 5) raise AssertionError("Shatpadi should require 6 feet") except ValueError: pass try: Ela(feet_elaboration=("a", "b", "c")) raise AssertionError("Ela should require 4 feet") except ValueError: pass print("treatise_meta.py — all self-tests pass")