← 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")