← domain #6
Source : sruti_theory.py
data/library/books/brihaddesi_sharma_1992/formal_grammar/sruti_theory.py · 904 lines · 37008 bytes
"""Domaine 6 — śruti / microtuning theory (Brihaddesi de Mātaṅga, Sharma 1992)
Synthèse 6c.3 du domaine 6 depuis :
- 76 concepts (cluster śrutis = cluster_id 95 : 12 affirmations, 6 règles)
- 60 règles génératives 6b
- 105 affirmations sourcées (Sharma vol. I §III pp.014-020 + vol. II)
══════════════════════════════════════════════════════════════════════════════
POLITIQUE ANTI-FABRICATION — règle absolue de ce domaine
══════════════════════════════════════════════════════════════════════════════
Le Brihaddesi NE PROPOSE PAS un système de microtuning canonique.
Mātaṅga *rapporte* plusieurs traditions concurrentes :
• mīmāṃsā-stricte / Bharata : 22 śrutis (aff#2154, aff#2158)
• différentialiste : 66 śrutis (aff#2154, aff#2158)
• infinitiste : śrutis innombrables (aff#2154, aff#2158)
• vamsa / instrument à anche : 9 śrutis (aff#2152, aff#2153, R_4p0_013)
• triple-classification : 3 classes par vaiguṇya (aff#2136)
• Tumburu (fourfold humoral) : 4 classes (aff#2144)
• Kohala : autorité citée (aff#2151)
Conséquence pour la modélisation :
- PAS de table cent-grid canonique (aucun système avec valeurs en cents
n'est sourcé dans le BrD ; tout ratio numérique serait fabriqué).
- Chaque énumération de śrutis = constante attribuée à une AUTORITÉ.
- Les opérations comparent des autorités ; elles ne tranchent pas.
- Les śrutis sont des "points de domination" sur un continuum tonal,
sans pitch fixes (aff#... aishvarya_definition_continuum).
Chaque type / constante / opération / contrainte cite ≥ 1 rule_id 6b ou
affirmation_id du pipeline Brihaddesi. Les zones non sourcées sont listées
dans UNRESOLVED.
Python 3.10+. Importable sans dépendance externe.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Literal
# =============================================================================
# AUTORITÉS — qui dit quoi (système rapporté ≠ canon)
# =============================================================================
class Authority(str, Enum):
"""Authorities cited by Mātaṅga as enumerating / classifying śrutis.
These are NOT alternative renderings of one truth — they are distinct
epistemic claims that the Brihaddesi reports side-by-side.
evidence: aff#2154, aff#2158 (the three competing schools 22/66/∞ named
by Mātaṅga); aff#2144 (Tumburu); aff#2151 (Kohala);
aff#2913, aff#3021, aff#2963 (Abhinavagupta commenting NŚ);
aff#2136 (threefold school per indriya-vaiguṇya);
R_208_abhinava_bharati, R_4p1_12 (Abhi Bhā = commentary)
"""
BHARATA = "bharata" # NŚ — 22-school (mīmāṃsā-stricte)
MIMAMSA_STRICT = "mimamsa_strict" # alias of 22-school (aff#2154)
DIFFERENTIATED = "differentiated_66" # 66-school (aff#2154)
INFINITIST = "infinitist" # śrutis innombrables (aff#2154)
VAMSA_TRADITION = "vamsa_tradition" # 9-śruti flute school (aff#2153)
THREEFOLD_VAIGUNYA = "threefold_vaigunya" # aff#2136
TUMBURU = "tumburu" # aff#2144
KOHALA = "kohala" # aff#2151
ABHINAVAGUPTA = "abhinavagupta" # commentator on NŚ (R_208, R_4p1_12)
MATANGA = "matanga" # the BrD author himself
BRD = "brihaddesi" # BrD as text-internal voice
# =============================================================================
# Types partagés (Svara, Grāma, SVARA_ORDER) — importés depuis le domaine 3
# (canonicalisés par 6c.4).
# =============================================================================
try:
from .melodic_derivations import Svara, SVARA_ORDER, Grāma
except ImportError:
import os as _os, sys as _sys
_sys.path.insert(0, _os.path.dirname(__file__))
from melodic_derivations import Svara, SVARA_ORDER, Grāma # type: ignore
# =============================================================================
# CONSTANTES — chaque entrée est attribuée à une autorité
# =============================================================================
# -----------------------------------------------------------------------------
# Comptes totaux de śrutis selon l'autorité
# -----------------------------------------------------------------------------
# evidence: aff#2154 ("twenty-two śrutis / sixty-six / infinity"),
# aff#2158 (idem reformulé), aff#2153 (venu = 9 śruti),
# aff#2136 (threefold)
# Valeurs : int = nombre fini ; None = infini (school infinitiste).
SRUTI_COUNT_BY_AUTHORITY: dict[Authority, int | None] = {
Authority.BHARATA: 22,
Authority.MIMAMSA_STRICT: 22, # aff#2154 explicit aliasing
Authority.DIFFERENTIATED: 66,
Authority.INFINITIST: None, # ananta — aucun nombre fini
Authority.VAMSA_TRADITION: 9, # aff#2153
Authority.THREEFOLD_VAIGUNYA: 3, # aff#2136 (classification, not enum)
Authority.TUMBURU: 4, # aff#2144 (fourfold humoral)
}
# -----------------------------------------------------------------------------
# Définition générique de śruti (mesure d'écart de pañcama)
# -----------------------------------------------------------------------------
# evidence: R_c278_sruti (FUNCTION sruti_count), aff#2155 (definition):
# "Śruti is the measure of difference in pañcama through utkarṣa /
# apakarṣa, through mārdava / āyatatva"
SRUTI_OPERATIONAL_DEFINITION: str = (
"śruti = measure of difference of pañcama through "
"utkarṣa(augmentation) / apakarṣa(diminution) / "
"mārdava(softening) / āyatatva(extension)"
)
# -----------------------------------------------------------------------------
# Structure interne du 22-school (autorité Bharata / Mātaṅga rapporteur)
# -----------------------------------------------------------------------------
# evidence: śruti_total_22 (R), aff#2953 + aff#2954
# "Nine śrutis constituting a triad of svaras of 4 + 3 + 2 śrutis"
# "9 + 9 + 4 = 22"
BHARATA_22_STRUCTURE: dict[str, int | tuple[int, ...]] = {
"total": 22,
"triad_basic": (4, 3, 2), # une triade fondamentale
"triad_sum": 9, # 4+3+2 = 9
"grouping": (9, 9, 4), # 9+9+4 = 22
}
# -----------------------------------------------------------------------------
# Per-svara śruti count dans le 22-school (rapporté par Mātaṅga)
# -----------------------------------------------------------------------------
# evidence: aff#2407 (gāndhāra & niṣāda = 2 śrutis chacun),
# aff#1620 (gāndhāra = 2-śruti note),
# aff#677 (śruti-bhinna : 4-śruti modifié par 2-śruti, gāndhāra=2),
# aff#682 (niṣāda prend 2 śrutis de pañcama qui a 4 śrutis),
# aff#1633 (pañcama caractérisé par 4 śrutis),
# aff#1878 (grāma déterminé par pañcama 4 vs 3 śrutis),
# aff#688 (en madhyamagrāma : pañcama=3 śrutis),
# R_4p0_013 (vamsa : groupes de 2-3-4 śrutis).
# Le 22-school assigne 4-4-3-2 entre les 7 svaras, pas une grille de cents.
SVARA_SRUTI_COUNT_BHARATA_22: dict[tuple[Grāma, Svara], int] = {
# ṣaḍja-grāma — distribution rapportée
(Grāma.SADJA, Svara.SA): 4, # ṣaḍja brāhmaṇa, aff#2323
(Grāma.SADJA, Svara.RI): 3, # ṛṣabha kṣatriya, aff#2323
(Grāma.SADJA, Svara.GA): 2, # gāndhāra, aff#2407, aff#1620
(Grāma.SADJA, Svara.MA): 4, # madhyama brāhmaṇa, aff#2323
(Grāma.SADJA, Svara.PA): 4, # pañcama 4 śrutis dans ṣaḍja-grāma,
# aff#1633, aff#1878, aff#688
(Grāma.SADJA, Svara.DHA): 3, # dhaivata kṣatriya (déduit de la classe
# caste : 3-śruti, R_śruti_count_classes_caste)
(Grāma.SADJA, Svara.NI): 2, # niṣāda, aff#2407
# madhyama-grāma — seul pañcama diffère (3 au lieu de 4),
# tout le reste hérite (aff#1878 : "identity of grāma depends solely on
# whether pañcama comprises four or three śrutis")
(Grāma.MADHYAMA, Svara.SA): 4,
(Grāma.MADHYAMA, Svara.RI): 3,
(Grāma.MADHYAMA, Svara.GA): 2,
(Grāma.MADHYAMA, Svara.MA): 4,
(Grāma.MADHYAMA, Svara.PA): 3, # aff#688 : "pañcama comprised of 3 śrutis"
(Grāma.MADHYAMA, Svara.DHA): 3,
(Grāma.MADHYAMA, Svara.NI): 2,
}
# -----------------------------------------------------------------------------
# Classification caste-śruti (Mātaṅga rapporteur ; 22-school)
# -----------------------------------------------------------------------------
# evidence: śruti_count_classes_caste (R), aff#2323
# "4-śruti svaras = brāhmaṇas, 3-śruti = kṣatriyas, 2-śruti = vaiśyas"
CASTE_BY_SRUTI_COUNT_BHARATA_22: dict[int, str] = {
4: "brāhmaṇa",
3: "kṣatriya",
2: "vaiśya",
}
# -----------------------------------------------------------------------------
# Structure du vamsa (flûte de bambou) — autorité instrumentale
# -----------------------------------------------------------------------------
# evidence: R_4p0_013, aff#2148, aff#2149, aff#2152, aff#2955
VAMSA_STRUCTURE: dict[str, object] = {
"total": 9,
"groups": (2, 3, 4),
"production_methods": ("open_hole", "shake_finger", "half_open"),
"rationale": "clear_description_via_hole_manipulation",
}
# -----------------------------------------------------------------------------
# Vamsa-lignée des svaras (paramparā / lineage non-numérique)
# -----------------------------------------------------------------------------
# evidence: R_1723_vamsa_rsi_ri_dha, aff#2330
# "Ṛṣabha and dhaivata are born in the vaṃśa of ṛṣis"
SVARA_VAMSA_RSI: frozenset[Svara] = frozenset({Svara.RI, Svara.DHA})
# -----------------------------------------------------------------------------
# Théories épistémiques de la relation svara ↔ śruti
# -----------------------------------------------------------------------------
# Mātaṅga présente plusieurs vikalpana (alternatives), aucune n'est tranchée
# comme la vérité. Le module les énumère toutes avec leur statut.
# evidence: R_tadatmya_theory, R_c1651_vivartatva_def, R_1652_001,
# parinamita_shruti_to_svara_transformation, R_1654_01, aff#2198
SVARA_SRUTI_THEORIES: dict[str, dict[str, str]] = {
"tādātmya": {
"claim": "svara ≡ śruti (identity, like jāti ≡ vyakti)",
"analogy": "jāti / vyakti — species / individual",
"status": "refuted by aśraya/āśrayin distinction (aff#2193)",
"evidence": "R_tadatmya_theory, aff#2180, aff#2181, aff#2193",
},
"kāryatva": {
"claim": "svara = effect of śruti",
"analogy": "clay / jar — material cause / product",
"status": "one alternative (vikalpana)",
"evidence": "R_1652_001",
},
"pariṇāmitā": {
"claim": "śruti transforms into svara",
"analogy": "milk → curd",
"status": "one alternative (vikalpana)",
"evidence": "parinamita_shruti_to_svara_transformation",
},
"vivartatva": {
"claim": "svara is reflected in śrutis",
"analogy": "face / mirror",
"status": "one alternative (vikalpana)",
"evidence": "R_c1651_vivartatva_def",
},
"abhivyañjakatā": {
"claim": "śrutis manifest svaras",
"analogy": "candle / objects in darkness",
"status": "one alternative (vikalpana)",
"evidence": "R_1654_01",
},
}
# -----------------------------------------------------------------------------
# Pramāṇa-s invoqués dans les débats svara-śruti
# -----------------------------------------------------------------------------
# evidence: R_537_anumana, R_288_arthapatti, R_2112_vyapti_graha,
# R_1659_vyapti_grahana, R_1673_01
PRAMANAS: dict[str, dict[str, str]] = {
"anumāna": {
"definition": "inference",
"accepted_by": "all Indian systems except Cārvāka",
"prerequisite": "pratyakṣa (perception of vyāpti / concomitance)",
"evidence": "R_537_anumana",
},
"arthāpatti": {
"definition": "postulation / negative inference",
"accepted_by": "Mīmāṃsā, Advaita Vedānta (independent); "
"Nyāya (subsumed under anumāna)",
"evidence": "R_288_arthapatti",
},
"vyāpti-grahaṇa": {
"definition": "apprehension of universal concomitance",
"evidence": "R_2112_vyapti_graha, R_1659_vyapti_grahana",
},
}
# -----------------------------------------------------------------------------
# Techniques de mesure / modification de śruti
# -----------------------------------------------------------------------------
# evidence: R_283_utkarsa, R_4p1_284, R_c128_mardava, ayatatva_definition_extension
SRUTI_MODIFICATION_OPS: dict[str, dict[str, str]] = {
"utkarṣa": {
"direction": "raise pitch",
"use": "measure śruti interval / effect grāma transformation",
"evidence": "R_283_utkarsa, aff#2161",
},
"apakarṣa": {
"direction": "lower pitch",
"use": "counterpart of utkarṣa; vīṇā śruti differentiation",
"evidence": "R_4p1_284, aff#2162",
},
"mārdava": {
"direction": "softening (associated to svara followed by higher one)",
"use": "with āyatatva, to measure śruti of pañcama",
"evidence": "R_c128_mardava, aff#2163, aff#2968",
},
"āyatatva": {
"direction": "extension (associated to svara followed by lower one)",
"use": "with mārdava, to measure śruti of pañcama; "
"replaced by viprakarṣa in BrD Anu. 50",
"evidence": "ayatatva_definition_extension, aff#2164",
},
}
# -----------------------------------------------------------------------------
# Techniques de tāna (omission/inclusion de svaras dans le contexte microtonal)
# -----------------------------------------------------------------------------
# evidence: pravesa_definition, pravesa_mechanism, R_555_nigraha_definition,
# R_2192_tarakriya_def, R_2191_01, R_2189_001,
# apurnasvarata_associated_with_paryagraha
TANA_TECHNIQUES: dict[str, dict[str, str]] = {
"praveśa": {
"definition": "lower (or higher) svara enters/merges into the higher "
"(or lower) svara — viprakarṣa of lower must be omitted",
"mechanism": "via prakarṣa(L) OR mārdava(H)",
"evidence": "pravesa_definition, pravesa_mechanism",
},
"nigraha": {
"definition": "omission / non-touching of the immediate svara",
"alias": "mandrakriyā per Abhinavagupta (R_2191_01)",
"associated": "pūrṇasvaratā — complete use of svaras (R_2189_001)",
"evidence": "R_555_nigraha_definition, R_2191_01, R_2189_001",
},
"paryagraha": {
"definition": "use of high svaras",
"alias": "tārakriyā per Abhinavagupta (R_2192_tarakriya_def)",
"associated": "apūrṇasvaratā — incomplete use of svaras",
"evidence": "R_2192_tarakriya_def, apurnasvarata_associated_with_paryagraha",
},
}
# -----------------------------------------------------------------------------
# Aiśvarya — śrutis comme points de domination, pas pitch fixes
# -----------------------------------------------------------------------------
# evidence: aishvarya_definition_continuum
# "Aiśvarya of śrutis refers to their domination as cognizable points
# on a continuum of tones (no fixed pitch points)"
SRUTI_AISVARYA_PRINCIPLE: str = (
"śrutis = points of supremacy/domination on a tone-continuum; "
"NO fixed pitch points"
)
# -----------------------------------------------------------------------------
# Contrainte de register-preservation
# -----------------------------------------------------------------------------
# evidence: śruti_register_preservation (R), aff#2566
# "A svara should be sounded in three registers with the same
# number of śrutis as originally composed"
SRUTI_REGISTER_PRESERVATION: bool = True
# -----------------------------------------------------------------------------
# Cokṣa-rāgas (rāgas dits "purs" en relation avec śuddhā gīti)
# -----------------------------------------------------------------------------
# evidence: R_235_coksa_enumeration, shuddha_giti_coksha_features
COKSA_RAGAS: tuple[str, ...] = (
"ṣāḍava", "pañcama", "kaiśikamadhyama",
"cokṣasādhārita", "cokṣakaiśika",
)
# =============================================================================
# TYPES — dataclasses
# =============================================================================
@dataclass(frozen=True)
class SrutiClaim:
"""An authority-attributed claim about śruti enumeration / structure.
The Brihaddesi NEVER asserts a count as canonical truth — every count
is delivered as "X says". This dataclass enforces that provenance.
evidence: aff#2154, aff#2158 (multi-school enumeration reported)
"""
authority: Authority
count: int | None # None = infinite (infinitist school)
scope: str # e.g. "total", "per_svara", "vamsa_only"
evidence: str # rule_id or aff#id
def is_finite(self) -> bool:
return self.count is not None
def is_infinite(self) -> bool:
return self.count is None
@dataclass(frozen=True)
class SrutiBhinna:
"""Śruti-bhinna relation: a 4-śruti svara modified by a 2-śruti svara.
Canonical example: niṣāda takes 2 śrutis of pañcama (which has 4),
with gāndhāra (2-śruti) involved.
evidence: R_857_srutibhinna_definition, r_brd_317_sruti_bhinna_def,
aff#677, aff#682
"""
modified_svara: Svara # the 4-śruti svara
modifier_svara: Svara # the 2-śruti svara
grāma: Grāma
borrowed_srutis: int = 2 # canonical: 2 śrutis borrowed (aff#682)
def __post_init__(self) -> None:
modified_count = SVARA_SRUTI_COUNT_BHARATA_22[(self.grāma, self.modified_svara)]
modifier_count = SVARA_SRUTI_COUNT_BHARATA_22[(self.grāma, self.modifier_svara)]
if modified_count != 4:
raise ValueError(
f"śruti-bhinna requires 4-śruti modified svara; "
f"got {self.modified_svara.value} = {modified_count}"
)
if modifier_count != 2:
raise ValueError(
f"śruti-bhinna requires 2-śruti modifier svara; "
f"got {self.modifier_svara.value} = {modifier_count}"
)
@dataclass(frozen=True)
class SvaraSrutiTheory:
"""One of the alternative (vikalpana) theories of how svara relates to
śruti, as enumerated by Mātaṅga without elevating any single one.
evidence: aff#2198 ("alternatives starting with tādātmya have been
spoken of by me")
"""
name: str
claim: str
analogy: str
status: str
evidence: str
@dataclass(frozen=True)
class AuthoritySrutiSystem:
"""A complete śruti-enumeration system as attributed to one authority.
Used by `compare_authorities()` to surface disagreement explicitly
rather than picking a winner.
"""
authority: Authority
count: int | None
classification: tuple[int, ...] | None # subdivisions if any (4-3-2 …)
evidence: str
@dataclass(frozen=True)
class Matrika:
"""Mātṛkā — primary unit of tonal sound, "more subtle than śruti" per
Mātaṅga. Distinct from śruti; subtle basis with numeric correspondence
to śrutis.
evidence: BD_6_1_R017, R_289_matrkas, R_2148_001, R_1673_01,
aff#3002, aff#2202, aff#3032
"""
context: Literal["religious", "linguistic", "musical_matanga"]
role: str
movement: Literal["vakra", "straight"] | None = None # for musical context
evidence: str = "BD_6_1_R017"
@dataclass(frozen=True)
class VaigunyaImpairment:
"""Vaiguṇya — faultiness of the human voice (organ of speech).
Threefold per Abhinavagupta and the BrD.
evidence: R_2050_indriya_vaigunya_threefold, vaigunya_definition,
R_2051_vagindriya_seat_vaigunya
"""
type: Literal["inborn", "humour_imbalance", "accident"]
seat: str = "vāgindriya" # primary seat per Abhinavagupta
# =============================================================================
# OPÉRATIONS — exécutables
# =============================================================================
def get_sruti_count(authority: Authority) -> int | None:
"""Retourne le compte total de śrutis revendiqué par une autorité.
`None` signifie infini (école infinitiste — pas zéro, pas inconnu).
evidence: SRUTI_COUNT_BY_AUTHORITY (chaque entrée a son aff#)
"""
if authority not in SRUTI_COUNT_BY_AUTHORITY:
raise KeyError(
f"Authority {authority.value} not enumerated as a śruti school "
f"in the Brihaddesi rule corpus"
)
return SRUTI_COUNT_BY_AUTHORITY[authority]
def get_svara_sruti_count_bharata_22(grāma: Grāma, svara: Svara) -> int:
"""Per-svara śruti count *within* the 22-school as reported by Mātaṅga.
NE PAS utiliser comme la "vraie" valeur — c'est l'attribution Bharata
rapportée par Mātaṅga ; les autres écoles disent autre chose.
evidence: SVARA_SRUTI_COUNT_BHARATA_22 dict (each entry cited)
"""
return SVARA_SRUTI_COUNT_BHARATA_22[(grāma, svara)]
def caste_of_svara_bharata_22(grāma: Grāma, svara: Svara) -> str:
"""Classe-caste d'un svara selon la classification śruti-count (22-school).
evidence: śruti_count_classes_caste, aff#2323
"""
n = get_svara_sruti_count_bharata_22(grāma, svara)
return CASTE_BY_SRUTI_COUNT_BHARATA_22[n]
def grama_of_pancama_count(n_srutis_pancama: int) -> Grāma:
"""Détermine le grāma à partir du compte śruti de pañcama (4 ou 3).
"The identity of grāma depends solely on whether pañcama comprises
four or three śrutis" — aff#1878.
evidence: aff#1878, aff#688, aff#1633, aff#1632, R_319_gramas
"""
if n_srutis_pancama == 4:
return Grāma.SADJA
if n_srutis_pancama == 3:
return Grāma.MADHYAMA
raise ValueError(
f"pañcama with {n_srutis_pancama} śrutis: no grāma assignment "
f"in the Brihaddesi (only 4 → ṣaḍja, 3 → madhyama)"
)
def is_srutibhinna(modified: Svara, modifier: Svara, grāma: Grāma) -> bool:
"""Vrai ssi (modified=4-śruti) ∧ (modifier=2-śruti) dans le 22-school.
Exemple canonique : niṣāda (2) modifie pañcama (4) → śrutibhinna.
evidence: R_857_srutibhinna_definition, r_brd_317_sruti_bhinna_def,
aff#677 ("Śruti-bhinna defined as svara comprised of 4 śrutis
being modified by one comprised of 2 śrutis")
"""
mod_count = SVARA_SRUTI_COUNT_BHARATA_22[(grāma, modified)]
modr_count = SVARA_SRUTI_COUNT_BHARATA_22[(grāma, modifier)]
return mod_count == 4 and modr_count == 2
def sama_srutikatva(s1: Svara, s2: Svara, grāma: Grāma) -> bool:
"""Sama-śrutikatva = égalité d'intervalle de śruti entre deux svaras
(dans le 22-school).
evidence: R_1696_sama_srutikatva_def
"""
return (
SVARA_SRUTI_COUNT_BHARATA_22[(grāma, s1)]
== SVARA_SRUTI_COUNT_BHARATA_22[(grāma, s2)]
)
def authority_disagrees(a1: Authority, a2: Authority) -> bool:
"""Vrai ssi a1 et a2 donnent des comptes totaux différents.
Cet opérateur EXISTE PRÉCISÉMENT pour empêcher d'effacer le désaccord :
si tādātmya/22 vs infinitistes diffèrent, on le constate, on ne tranche pas.
evidence: aff#2154, aff#2158
"""
c1 = SRUTI_COUNT_BY_AUTHORITY.get(a1)
c2 = SRUTI_COUNT_BY_AUTHORITY.get(a2)
if a1 not in SRUTI_COUNT_BY_AUTHORITY or a2 not in SRUTI_COUNT_BY_AUTHORITY:
raise KeyError(f"Unknown authority: {a1}, {a2}")
return c1 != c2
def compare_authorities() -> list[AuthoritySrutiSystem]:
"""Retourne tous les systèmes śruti côte-à-côte (anti-tranchage).
evidence: aff#2154, aff#2158 (Mātaṅga lui-même les présente côte-à-côte)
"""
return [
AuthoritySrutiSystem(
authority=Authority.BHARATA, count=22,
classification=BHARATA_22_STRUCTURE["grouping"], # type: ignore
evidence="śruti_total_22 + aff#2953 + aff#2954",
),
AuthoritySrutiSystem(
authority=Authority.DIFFERENTIATED, count=66,
classification=None, evidence="aff#2154, aff#2158",
),
AuthoritySrutiSystem(
authority=Authority.INFINITIST, count=None,
classification=None, evidence="aff#2154, aff#2158",
),
AuthoritySrutiSystem(
authority=Authority.VAMSA_TRADITION, count=9,
classification=VAMSA_STRUCTURE["groups"], # type: ignore
evidence="R_4p0_013, aff#2148, aff#2149, aff#2152, aff#2153",
),
AuthoritySrutiSystem(
authority=Authority.THREEFOLD_VAIGUNYA, count=3,
classification=None,
evidence="aff#2136 (threefold per indriya-vaiguṇya)",
),
AuthoritySrutiSystem(
authority=Authority.TUMBURU, count=4,
classification=None, evidence="aff#2144 (fourfold humoral)",
),
]
def list_svara_sruti_theories() -> list[SvaraSrutiTheory]:
"""Énumère les théories vikalpana de la relation svara/śruti, sans
en élire une.
evidence: SVARA_SRUTI_THEORIES dict (each entry cited), aff#2198
"""
return [
SvaraSrutiTheory(
name=name,
claim=t["claim"], analogy=t["analogy"],
status=t["status"], evidence=t["evidence"],
)
for name, t in SVARA_SRUTI_THEORIES.items()
]
def apply_utkarsa(svara: Svara) -> dict[str, object]:
"""Modèle symbolique d'utkarṣa appliqué à un svara.
Ne renvoie PAS de cents (aucune valeur cents n'est sourcée dans le BrD).
Renvoie une description d'effet.
evidence: R_283_utkarsa
"""
return {
"svara": svara,
"operation": "utkarṣa",
"direction": "raise_pitch",
"shifts": "śruti_interval",
"evidence": "R_283_utkarsa, aff#2161",
}
def apply_apakarsa(svara: Svara) -> dict[str, object]:
"""Modèle symbolique d'apakarṣa. Symétrique d'utkarṣa.
evidence: R_4p1_284
"""
return {
"svara": svara,
"operation": "apakarṣa",
"direction": "lower_pitch",
"use": "śruti_difference_measurement",
"evidence": "R_4p1_284, aff#2162",
}
def grāma_transformation_via_pancama(from_grama: Grāma) -> Grāma:
"""ṣaḍja-grāma ↔ madhyama-grāma par modification śruti-count de pañcama
(4 ↔ 3).
evidence: aff#1878, R_283_utkarsa ("APPLY in grama_transformation
WHEN murchana changes WITH utkarsa")
"""
return Grāma.MADHYAMA if from_grama == Grāma.SADJA else Grāma.SADJA
# =============================================================================
# CONTRAINTES — validations
# =============================================================================
def is_valid_sruti_count_claim(count: int | None, authority: Authority) -> bool:
"""Vrai ssi (authority, count) correspond à une revendication sourcée.
evidence: SRUTI_COUNT_BY_AUTHORITY (cf. citations par entrée)
"""
if authority not in SRUTI_COUNT_BY_AUTHORITY:
return False
return SRUTI_COUNT_BY_AUTHORITY[authority] == count
def is_valid_22_school_structure(parts: tuple[int, ...]) -> bool:
"""Vrai ssi (parts) somment à 22 ET correspondent au pattern 9+9+4
rapporté.
evidence: śruti_total_22, aff#2954
"""
return tuple(parts) == BHARATA_22_STRUCTURE["grouping"] and sum(parts) == 22
def is_valid_register_preservation(
n_srutis_original: int, n_srutis_in_register: int
) -> bool:
"""Un svara, dans chacun des 3 registres, conserve son count śruti
d'origine.
evidence: śruti_register_preservation, aff#2566
"""
return n_srutis_original == n_srutis_in_register
def can_perform_shake(svara: Svara, grāma: Grāma, alankara: str | None = None) -> bool:
"""Règle traditionnelle : kampita (shake) admis seulement sur svaras
de 3 śrutis. Règle 'érodée dans certains alaṅkāras' (aff#3148).
evidence: śruti_shake_3sruti_rule_eroded, aff#3148
"""
base_rule = SVARA_SRUTI_COUNT_BHARATA_22[(grāma, svara)] == 3
# Exception : alaṅkāra-spécifique. Le BrD ne nomme pas les alaṅkāras
# exceptés (UNRESOLVED).
return base_rule
def is_valid_vamsa_grouping(groups: tuple[int, ...]) -> bool:
"""Vamsa : 9 śrutis structurés en groupes 2-3-4.
evidence: R_4p0_013, aff#2148, aff#2149, aff#2955
"""
return (
tuple(sorted(groups)) == tuple(sorted(VAMSA_STRUCTURE["groups"])) # type: ignore
and sum(groups) == VAMSA_STRUCTURE["total"]
)
# =============================================================================
# UNRESOLVED — flagged zones
# =============================================================================
# Listed here for traceability ; consumed by the JSON manifest.
UNRESOLVED: tuple[dict[str, str], ...] = (
{
"concept": "cents-grid / numerical ratio for any śruti system",
"reason": "AUCUN ratio numérique (Pythagorean, just-intonation, "
"log2, Daniélou ratios, etc.) n'est sourcé dans le BrD. "
"Le BrD ne fournit que des comptes entiers per-svara "
"et la définition opératoire (utkarṣa/apakarṣa). "
"Imposer une grille de cents serait fabrication.",
},
{
"concept": "jāti-classes of śrutis (dīptā/āyatā/mṛdu/madhyā/karuṇā)",
"reason": "Ces 5 jāti-de-śruti sont attendues du Nāṭyaśāstra, "
"mais aucun rule_id 6b ni affirmation du BrD-Mātaṅga "
"ne les énumère individuellement avec leur assignation "
"par śruti. Seule trace : aff#1849 (mṛdu = synonyme "
"de mandra) et 'dipta' comme cluster vide (cluster_id=500, "
"0 affirmations). NON modélisé.",
},
{
"concept": "66-school internal structure",
"reason": "aff#2154 et aff#2158 nomment l'école 66-différencialiste "
"mais aucune règle 6b ne donne la sous-division. "
"Compte total seulement.",
},
{
"concept": "infinitist school internal description",
"reason": "aff#2154 mentionne ananta-śruti ; aucun mécanisme "
"(densité, distribution) n'est précisé.",
},
{
"concept": "fourfold Tumburu classification (humoral) — content",
"reason": "aff#2144 cite Tumburu comme autorité du fourfold humoral, "
"mais le contenu des 4 classes n'est pas extrait "
"(les 4 humeurs ne sont pas nommées en affirmations 6b).",
},
{
"concept": "Kohala's specific śruti doctrine",
"reason": "aff#2151 cite Kohala comme autorité mais ne donne pas "
"le contenu de sa position.",
},
{
"concept": "alaṅkāras exempting 3-śruti shake-rule",
"reason": "aff#3148 mentionne 'certains alaṅkāras' où la règle "
"est érodée, sans les nommer.",
},
{
"concept": "uttarāyatā / suddhamadhyā cluster ambiguity",
"reason": "clusters 1754, 1815 portent les jāti-śruti nom mais "
"leur domain_id=3/611 et 0 affirmations → relèvent "
"des mūrchanās, non des classes de śruti.",
},
{
"concept": "shuddhakaisikamadhyama grāma assignment",
"reason": "Disputé en source (référencé en domaine 3, "
"shuddhakaisikamadhyama_grama_disputed); non modélisé ici.",
},
)
# =============================================================================
# Self-test
# =============================================================================
if __name__ == "__main__":
# Sanity: chaque autorité doit exposer un count (ou None)
for auth in Authority:
if auth in SRUTI_COUNT_BY_AUTHORITY:
v = get_sruti_count(auth)
assert v is None or isinstance(v, int), (auth, v)
# Sanity: 22-school structure cohérente
assert BHARATA_22_STRUCTURE["total"] == 22
assert sum(BHARATA_22_STRUCTURE["grouping"]) == 22 # type: ignore
assert is_valid_22_school_structure((9, 9, 4))
assert not is_valid_22_school_structure((9, 8, 5))
# Sanity: per-svara count (Bharata 22-school) — pañcama distinct par grāma
assert get_svara_sruti_count_bharata_22(Grāma.SADJA, Svara.PA) == 4
assert get_svara_sruti_count_bharata_22(Grāma.MADHYAMA, Svara.PA) == 3
assert get_svara_sruti_count_bharata_22(Grāma.SADJA, Svara.GA) == 2
assert get_svara_sruti_count_bharata_22(Grāma.SADJA, Svara.NI) == 2
# Sanity: caste classification
assert caste_of_svara_bharata_22(Grāma.SADJA, Svara.SA) == "brāhmaṇa"
assert caste_of_svara_bharata_22(Grāma.SADJA, Svara.RI) == "kṣatriya"
assert caste_of_svara_bharata_22(Grāma.SADJA, Svara.GA) == "vaiśya"
# Sanity: grāma déterminé par pañcama
assert grama_of_pancama_count(4) == Grāma.SADJA
assert grama_of_pancama_count(3) == Grāma.MADHYAMA
try:
grama_of_pancama_count(5)
assert False, "should have raised"
except ValueError:
pass
# Sanity: śrutibhinna canonical case (niṣāda modifying pañcama)
assert is_srutibhinna(Svara.PA, Svara.NI, Grāma.SADJA)
# niṣāda n'est pas 4-śruti → pas modified
assert not is_srutibhinna(Svara.NI, Svara.PA, Grāma.SADJA)
# sama-counts → pas śrutibhinna
assert not is_srutibhinna(Svara.SA, Svara.MA, Grāma.SADJA)
# Sanity: SrutiBhinna dataclass valide
sb = SrutiBhinna(modified_svara=Svara.PA, modifier_svara=Svara.NI,
grāma=Grāma.SADJA)
assert sb.borrowed_srutis == 2
try:
SrutiBhinna(modified_svara=Svara.GA, modifier_svara=Svara.NI,
grāma=Grāma.SADJA)
assert False, "should have raised (GA=2 != 4)"
except ValueError:
pass
# Sanity: sama-śrutikatva
assert sama_srutikatva(Svara.SA, Svara.MA, Grāma.SADJA) # 4 ↔ 4
assert sama_srutikatva(Svara.GA, Svara.NI, Grāma.SADJA) # 2 ↔ 2
assert not sama_srutikatva(Svara.SA, Svara.GA, Grāma.SADJA) # 4 ↔ 2
# Sanity: désaccord d'autorités explicite
assert authority_disagrees(Authority.BHARATA, Authority.DIFFERENTIATED)
assert authority_disagrees(Authority.BHARATA, Authority.INFINITIST)
assert authority_disagrees(Authority.BHARATA, Authority.VAMSA_TRADITION)
assert not authority_disagrees(Authority.BHARATA, Authority.MIMAMSA_STRICT)
# Sanity: compare_authorities renvoie ≥ 5 systèmes distincts
systems = compare_authorities()
assert len(systems) >= 5
assert any(s.count == 22 for s in systems)
assert any(s.count == 66 for s in systems)
assert any(s.count is None for s in systems) # infinitist
assert any(s.count == 9 for s in systems) # vamsa
# Sanity: théories svara-śruti — au moins 5 listées sans hiérarchie
theories = list_svara_sruti_theories()
assert len(theories) >= 5
names = {t.name for t in theories}
assert {"tādātmya", "kāryatva", "pariṇāmitā",
"vivartatva", "abhivyañjakatā"} <= names
# Sanity: register preservation
assert is_valid_register_preservation(4, 4)
assert not is_valid_register_preservation(4, 3)
# Sanity: shake rule
assert can_perform_shake(Svara.RI, Grāma.SADJA) # 3 śrutis → autorisé
assert not can_perform_shake(Svara.SA, Grāma.SADJA) # 4 śrutis → non
assert not can_perform_shake(Svara.GA, Grāma.SADJA) # 2 śrutis → non
# Sanity: vamsa grouping
assert is_valid_vamsa_grouping((2, 3, 4))
assert is_valid_vamsa_grouping((4, 2, 3))
assert not is_valid_vamsa_grouping((1, 3, 5))
# Sanity: utkarṣa / apakarṣa retournent des descriptions sourcées
u = apply_utkarsa(Svara.PA)
assert u["direction"] == "raise_pitch"
assert "R_283_utkarsa" in u["evidence"]
a = apply_apakarsa(Svara.PA)
assert a["direction"] == "lower_pitch"
assert "R_4p1_284" in a["evidence"]
# Sanity: grāma transformation
assert grāma_transformation_via_pancama(Grāma.SADJA) == Grāma.MADHYAMA
assert grāma_transformation_via_pancama(Grāma.MADHYAMA) == Grāma.SADJA
# Sanity: claim validity
assert is_valid_sruti_count_claim(22, Authority.BHARATA)
assert is_valid_sruti_count_claim(66, Authority.DIFFERENTIATED)
assert is_valid_sruti_count_claim(None, Authority.INFINITIST)
assert not is_valid_sruti_count_claim(22, Authority.INFINITIST)
# Sanity: UNRESOLVED non vide et flagge cents-grid
assert len(UNRESOLVED) >= 5
assert any("cents-grid" in u["concept"] for u in UNRESOLVED)
assert any("jāti-classes of śrutis" in u["concept"] for u in UNRESOLVED)
print("sruti_theory.py — all self-tests pass")