← domain #2
Source : bhasha_raga_corpus.py
data/library/books/brihaddesi_sharma_1992/formal_grammar/bhasha_raga_corpus.py · 898 lines · 36483 bytes
"""Domaine 2 — bhāṣā / vibhāṣā / grāma-rāga corpus
Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II) depuis :
- 112 règles génératives 6b (domaine 2)
- 261 affirmations sourcées
- partition Leiden domain_id=2 (110 concepts)
Champ couvert : la taxonomie grāma-rāga (śuddha / bhinna / vesara / gauḍa /
sādhāraṇa), les bhāṣā-rāgas, les vibhāṣā-rāgas, l'antara-bhāṣā, et les
rāgas individuels (ṭakkarāga, hindolaka, mālavapañcama, bhinnaṣaḍja…) et
leurs liens aux parents.
Anti-fabrication : chaque type, opération et contrainte cite ≥1 evidence
(rule_id 6b ou affirmation_id). Les concepts non sourcés sont listés dans
UNRESOLVED. Aucune valeur plausible-mais-non-sourcée n'a été introduite.
Python 3.10+. Importable sans 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 du Brihaddesi (domaine bhāṣā/rāga)
# =============================================================================
# Types partagés avec melodic_derivations (domaine 3) — canonicalisés par 6c.4.
try:
from .melodic_derivations import Svara, Grāma, ScaleType
except ImportError:
import os as _os, sys as _sys
_sys.path.insert(0, _os.path.dirname(__file__))
from melodic_derivations import Svara, Grāma, ScaleType # type: ignore
class GītiClass(str, Enum):
"""The five gītis of Durgaśakti — define the five categories of grāma-rāga.
Each grāma-rāga belongs to exactly one of these; the gīti is the
formative principle of its "song-style".
evidence: aff#542 ('Gītis should be known to be five viz. śuddhā, then
bhinnā, vesarā, gauḍī (and) sādhāritā; this is the opinion of
Durgā (Durgaśakti).'),
R_4p0_017 (sādhāraṇa = born of all previously-spoken rāgas;
sung in sādhāraṇa-gīti),
R_869_vesaragiti_category,
R_4p1_388 (text order: gauda before vesarā gīti),
R_1275_gaudaragas_category
"""
SUDDHA = "suddha" # = cokṣa (aff#578, aff#587, aff#591)
BHINNA = "bhinna" # 5 enumerated bhinna rāgas (aff#578, aff#588)
GAUDA = "gauda" # gauḍa-rāgas (R_1275)
VESARA = "vesara" # vesara-rāgas (R_389)
SADHARANA = "sadharana" # 7 sādhāraṇa rāgas (aff#589, R_4p0_017)
class BhasaSubtype(str, Enum):
"""The four-fold classification of bhāṣās per the Brihaddesi.
evidence: R_15_bhasa_classification, aff#874 ('Bhāṣās have been said to be
four-fold viz. mūlā (basic) sankīrṇā (mixed), deśajā (regional)
(and) chāyāmātrāśrayā (based on the chāyā of a rāga)')
"""
MULA = "mula" # basic / root
SANKIRNA = "sankirna" # mixed
DESAJA = "desaja" # regional
CHAYAMATRASRAYA = "chayamatrasraya" # based on chāyā of a rāga
class BhasaTier(str, Enum):
"""The 3-level derivational hierarchy: grāma-rāga → bhāṣā → vibhāṣā →
antara-bhāṣā.
evidence: R_073_bhasas_derivation (bhāṣā derived from grāma-rāga),
R_920_vibhasa_from_bhasa (vibhāṣā derived from bhāṣā),
R_5p2_921 (antara-bhāṣā derived from vibhāṣā),
aff#875, aff#876, aff#877, aff#1964
"""
BHASA = "bhasa"
VIBHASA = "vibhasa"
ANTARA_BHASA = "antara_bhasa"
# -----------------------------------------------------------------------------
# Closed enumerations of grāma-rāgas per gīti class
# -----------------------------------------------------------------------------
# Each entry below is verbatim from a Brihaddesi rule/affirmation listing.
# No name is added by inference.
# Five śuddha (= cokṣa) rāgas — aff#587 vol_II_p052
# evidence: aff#587 ('ṣāḍava, pañcama, kaiśikamadhyama, cokṣasādhārita,
# cokṣakaiśika'), aff#578 ('Five cokṣa (śuddha, pure rāgas) have
# been spoken of, the bhinna ones are of the same number.'),
# aff#591
SUDDHA_RAGAS: tuple[str, ...] = (
"sadava",
"pancama",
"kaisikamadhyama",
"coksasadharita",
"coksakaisika",
)
# Five bhinna rāgas — aff#588 vol_II_p052
# evidence: aff#588 ('bhinna-ṣaḍja, bhinna-tāna, bhinnakaiśikamadhyama,
# bhinnapañcama, bhinnakaiśika'), aff#640 (bhinna-ṣaḍja prominence)
BHINNA_RAGAS: tuple[str, ...] = (
"bhinnasadja",
"bhinnatana",
"bhinnakaisikamadhyama",
"bhinnapancama",
"bhinnakaisika",
)
# Seven sādhāraṇa rāgas — aff#589 vol_II_p052
# evidence: aff#589 ('narta, śaka, kakubha, harmāṇapañcama, rūpasādhārita,
# gāndhārapañcama, ṣaḍjakaiśika'), aff#841 (sapta sādhāraṇā…
# grāmadvayasamāśrayā — "seven sādhāraṇas, resting on both grāmas")
# NB: a parallel list (aff#43 in TOC) names 10 sādhāraṇa rāgas including
# Bhammāṇapañcama, Pañcamaṣāḍava, Revagupta — this is a TOC enumeration
# differing from the body's verse count of seven. Discrepancy logged in
# UNRESOLVED.
SADHARANA_RAGAS: tuple[str, ...] = (
"narta",
"saka",
"kakubha",
"harmanapancama", # var. bhammāṇapañcama (TOC aff#43)
"rupasadharita",
"gandharapancama",
"sadjakaisika",
)
# Vesara rāgas, enumerated per grāma — R_389_vesara_def_enum
# evidence: R_389_vesara_def_enum (body lists members per grāma),
# aff#777 (Sanskrit verse listing the ṣaḍjagrāma vesaras),
# aff#719 ('Vesaras are known to be so because the svaras move
# with speed.')
VESARA_RAGAS: dict[Grāma, tuple[str, ...]] = {
Grāma.SADJA: (
"takkaraga",
"sauvira",
"takkakaisika",
"bottaraga",
"vesarasadava",
),
Grāma.MADHYAMA: (
"hindolaka",
"malavapancama",
"malavakaisika",
),
}
# Gauḍa-rāgas — no closed enumeration rule found in 6b; category is named
# (R_1275_gaudaragas_category) and the corpus mentions individual gauḍa
# rāgas only intermittently. Listed in UNRESOLVED.
# -----------------------------------------------------------------------------
# Grāma-rāga → gīti class (which bucket each named rāga belongs to)
# -----------------------------------------------------------------------------
# Each assignment cites the rule / affirmation that pins it.
GRAMARAGA_GITI: dict[str, GītiClass] = {
# vesara (R_389_vesara_def_enum)
"takkaraga": GītiClass.VESARA,
"sauvira": GītiClass.VESARA,
"takkakaisika": GītiClass.VESARA,
"bottaraga": GītiClass.VESARA,
"vesarasadava": GītiClass.VESARA,
"hindolaka": GītiClass.VESARA,
"malavapancama": GītiClass.VESARA,
"malavakaisika": GītiClass.VESARA,
# bhinna (aff#588)
"bhinnasadja": GītiClass.BHINNA,
"bhinnatana": GītiClass.BHINNA,
"bhinnakaisikamadhyama": GītiClass.BHINNA,
"bhinnapancama": GītiClass.BHINNA,
"bhinnakaisika": GītiClass.BHINNA,
# śuddha (aff#587)
"sadava": GītiClass.SUDDHA,
"pancama": GītiClass.SUDDHA,
"kaisikamadhyama": GītiClass.SUDDHA,
"coksasadharita": GītiClass.SUDDHA,
"coksakaisika": GītiClass.SUDDHA,
# sādhāraṇa (aff#589)
"narta": GītiClass.SADHARANA,
"saka": GītiClass.SADHARANA,
"kakubha": GītiClass.SADHARANA,
"harmanapancama": GītiClass.SADHARANA,
"rupasadharita": GītiClass.SADHARANA,
"gandharapancama": GītiClass.SADHARANA,
"sadjakaisika": GītiClass.SADHARANA,
}
# -----------------------------------------------------------------------------
# Grāma-rāga → parent grāma (which of the two grāmas it belongs to)
# -----------------------------------------------------------------------------
# Only assignments with a specific Brihaddesi rule/affirmation are encoded.
GRAMARAGA_GRAMA: dict[str, Grāma] = {
# vesara, ṣaḍjagrāma — R_389_vesara_def_enum + aff#777
"takkaraga": Grāma.SADJA, # takkaraga_derivation_and_grama, aff#723
"sauvira": Grāma.SADJA, # R_389 + aff#777
"takkakaisika": Grāma.SADJA, # R_389 + aff#777
"bottaraga": Grāma.SADJA, # R_389 + aff#777
"vesarasadava": Grāma.SADJA, # R_389 + aff#777
# vesara, madhyamagrāma
"hindolaka": Grāma.MADHYAMA, # hindolaka_madhyamagrama_origin aff#783
"malavapancama": Grāma.MADHYAMA, # R_389 (in madhyamagrāma list)
"malavakaisika": Grāma.MADHYAMA, # R_malavakaisika_def + aff#775
# bhinna assignments to a grāma
"bhinnakaisika": Grāma.MADHYAMA, # aff#669, aff#676
# other bhinna rāgas: relations to grāma noted at various points but not
# all isolated to single-grāma assignment. See UNRESOLVED.
# sādhāraṇa
"kakubha": Grāma.SADJA, # aff#800, 'related to ṣaḍjagrāma'
"rupasadharita": Grāma.SADJA, # aff#843
"saka": Grāma.SADJA, # aff#843
# pañcamaṣāḍava (TOC variant of sādhāraṇa list): aff#826
# revagupta: aff#827. Both listed only in the 10-name TOC, not the
# 7-name body verse → kept out of SADHARANA_RAGAS but their grāma is
# sourced for cross-reference.
}
# -----------------------------------------------------------------------------
# Bhāṣā counts per grāma-rāga (closed enumerations from 6b rules)
# -----------------------------------------------------------------------------
# Some sources disagree on the count; both values kept when sourced.
# evidence per entry.
@dataclass(frozen=True)
class BhasaCount:
"""A sourced bhāṣā-count for a grāma-rāga, possibly with a disputing
alternative.
evidence: see `evidence_rules` per record below.
"""
primary: int
alternative: int | None = None
evidence_rules: tuple[str, ...] = ()
BHASA_COUNTS: dict[str, BhasaCount] = {
# R_398_takkaraga_features + aff#878 (12, some 16) + R_954_takkaraga_sixteen
"takkaraga": BhasaCount(
primary=12,
alternative=16,
evidence_rules=(
"R_398_takkaraga_features",
"R_117_takkaraga_bhasa_count",
"R_954_takkaraga_sixteen",
),
),
# R_c32_bhinnasadja + aff#882 + R_932_bhinnasadja_enum
"bhinnasadja": BhasaCount(
primary=9,
alternative=15,
evidence_rules=(
"R_c32_bhinnasadja",
"R_932_bhinnasadja_enum",
),
),
# R_077_bhinnapancama_enumeration + aff#51, aff#904
"bhinnapancama": BhasaCount(
primary=4,
evidence_rules=("R_077_bhinnapancama_enumeration",),
),
# R_114_malavakaisika_enumeration + aff#892 ('thus are the eight bhāṣās')
"malavakaisika": BhasaCount(
primary=8,
evidence_rules=(
"R_114_malavakaisika_enumeration",
"R_922_malavakaisika_8_bhasas",
),
),
# R_137_malavapancama + aff#1225
"malavapancama": BhasaCount(
primary=6,
evidence_rules=("R_137_malavapancama",),
),
# hindolaka_five_bhasas + BD_6_1_R016 (says "at least 6": discrepant)
"hindolaka": BhasaCount(
primary=5,
alternative=6,
evidence_rules=("hindolaka_five_bhasas", "BD_6_1_R016"),
),
}
# Named bhāṣā lists per grāma-rāga — only those given by an explicit rule.
# evidence per entry: rule_id named in the comment.
BHASA_NAMES_BY_GRAMARAGA: dict[str, tuple[str, ...]] = {
# R_114_malavakaisika_enumeration (aff#897, aff#892)
"malavakaisika": (
"suddha", "ardhavesarika", "harsapuri", "mangali",
"saindhavi", "abhiri", "khanjari", "gurjari",
),
# R_137_malavapancama (aff#1225)
"malavapancama": (
"vibhavini", "paurali", "vegavanti", "pancami",
"andhri", "gandharika",
),
# R_077_bhinnapancama_enumeration (aff#51, aff#904)
"bhinnapancama": (
"suddhabhinna", "varati", "dhaivatabhusita", "visala",
),
# hindolaka_five_bhasas
"hindolaka": (
"vesari", "manjari", "chevati", "sadjamadhyama", "madhuri",
),
# ṭakkarāga: R_117_takkaraga_bhasa_count names some bhāṣās and some
# vibhāṣās ("potā, Devālavardhanī, paurālī, Trāvaṇī, dohyā") — the rule
# mixes the two tiers, so we do NOT encode a closed bhāṣā name list for
# takkaraga. See UNRESOLVED.
# bhinnaṣaḍja: R_c32_bhinnasadja mentions a set including non-bhāṣā items
# (Suddha, Madhyama) — kept out for tier-purity reasons. See UNRESOLVED.
}
# Total bhāṣā + vibhāṣā count across all grāma-rāgas — Brihaddesi-wide constant.
# evidence: aff#883 ('bhāṣās and vibhāṣās have been said to be seventy-three
# in total'), R_073_bhasas_derivation body ('COUNT(bhasas + vibhasas) = 73')
TOTAL_BHASA_PLUS_VIBHASA: int = 73
# -----------------------------------------------------------------------------
# Structural triples (aṁśa, nyāsa, scale_type) per rāga/bhāṣā
# -----------------------------------------------------------------------------
# A rāga or bhāṣā is structurally pinned by, at minimum, its aṁśa, its
# nyāsa, and the density of its svara set. Other features (weak svaras,
# specific saṁcāra patterns, rasa) are deferred — see structural_features.
@dataclass(frozen=True)
class RagaStructure:
"""Static structural fingerprint of a rāga or bhāṣā / vibhāṣā.
evidence: R_15_bhasa_definition (bhāṣā characterized by aṁśa, nyāsa,
structural relationships),
graha_amsa_nyasa_svara_functions (these are svara-functions
via samavāya).
"""
name: str
tier: BhasaTier | None # None = top-level grāma-rāga
parent: str | None # parent rāga or bhāṣā
amsa: Svara | None
nyasa: Svara | None
scale: ScaleType | None
weak: frozenset[Svara] = frozenset()
omitted: frozenset[Svara] = frozenset()
evidence: tuple[str, ...] = () # rule_ids and/or aff#NNN
notes: str = ""
# Structures pinned by a 6b rule. Each entry cites at least one rule_id.
KNOWN_STRUCTURES: dict[str, RagaStructure] = {
# === GRĀMA-RĀGAS ===
"takkaraga": RagaStructure(
name="takkaraga", tier=None, parent=None,
amsa=Svara.MA, nyasa=Svara.SA, scale=None,
weak=frozenset({Svara.NI, Svara.PA}),
omitted=frozenset({Svara.PA}), # 'devoid of pañcama' aff#916
evidence=("R_398_takkaraga_features", "takkaraga_graha_amsa_nyasa",
"takkaraga_weak_svaras"),
notes="parent_jatis=dhaivatī+ṣaḍjamadhyamā; rasa=yuddhavīra/vīra/adbhuta",
),
# === BHĀṢĀS ===
"pinjari": RagaStructure(
name="pinjari", tier=BhasaTier.VIBHASA, parent="hindola",
amsa=Svara.GA, nyasa=Svara.SA, scale=ScaleType.SADAVA,
omitted=frozenset({Svara.NI}),
evidence=("R_5p2_607", "pinjari_vibhasha_amsa_nyasa"),
),
"drāvidī": RagaStructure(
name="drāvidī", tier=BhasaTier.BHASA, parent="takkakaisika",
amsa=Svara.PA, nyasa=Svara.SA, scale=None,
evidence=("R_252_dravidi", "BD_6_1_R025", "R_1040_dravidi_pancama_amsa"),
notes="third bhāṣā born of ṭakkakaiśika; deśa = drāviḍa",
),
"bhinnapaurali": RagaStructure(
name="bhinnapaurali", tier=BhasaTier.BHASA, parent="hindola",
amsa=Svara.MA, nyasa=Svara.SA, scale=ScaleType.SAMPURNA,
evidence=("BD_6_1_R022", "R_1039_bhinnapaurali_madhyama_amsa"),
),
"paurali": RagaStructure(
name="paurali", tier=BhasaTier.VIBHASA, parent="takkaraga",
amsa=Svara.MA, nyasa=Svara.DHA, scale=None,
weak=frozenset({Svara.RI}),
evidence=("R_996_paurali", "paurali_definition_and_origin"),
notes="dense mutual movement ma-ri-pa",
),
"varati": RagaStructure(
name="varati", tier=BhasaTier.BHASA, parent="bhinnapancama",
amsa=Svara.MA, nyasa=Svara.DHA, scale=ScaleType.SADAVA,
weak=frozenset({Svara.RI}),
evidence=("R_1006_varati_struct",),
),
"visala": RagaStructure(
name="visala", tier=BhasaTier.BHASA, parent="bhinnapancama",
amsa=Svara.PA, nyasa=Svara.DHA, scale=ScaleType.SAMPURNA,
evidence=("R_4p0_024", "R_077_bhinnapancama_enumeration"),
),
"abhiri": RagaStructure(
name="abhiri", tier=BhasaTier.BHASA, parent="malavakaisika",
amsa=Svara.PA, nyasa=Svara.PA, scale=ScaleType.SAMPURNA,
evidence=("abhiri_first_bhasa_from_pancama",
"R_114_malavakaisika_enumeration"),
notes="replete with niṣāda; two structural variants reported",
),
"pota": RagaStructure(
name="pota", tier=BhasaTier.VIBHASA, parent="takkaraga",
amsa=Svara.PA, nyasa=Svara.SA, scale=None,
weak=frozenset({Svara.NI}),
evidence=("pota_definition_bhasa",),
notes="ornamented by madhyama; starts on ṛṣabha, ends on dha",
),
"ardhavesari": RagaStructure(
name="ardhavesari", tier=BhasaTier.BHASA, parent="malavakaisika",
amsa=Svara.MA, nyasa=Svara.SA, scale=ScaleType.SAMPURNA,
weak=frozenset({Svara.NI}),
evidence=("r_brd_413_ardhavesari_structure",
"R_114_malavakaisika_enumeration"),
),
"ravicandrika": RagaStructure(
name="ravicandrika", tier=BhasaTier.BHASA, parent=None, # see notes
amsa=Svara.GA, nyasa=Svara.SA, scale=None,
weak=frozenset({Svara.NI}),
evidence=("ravicandrika_structure",),
notes="classified saṅkīrṇā; parent rāga not pinned in 6b",
),
"vibhavani": RagaStructure(
name="vibhavani", tier=BhasaTier.VIBHASA, parent="malavapancama",
amsa=Svara.PA, nyasa=Svara.PA, scale=ScaleType.SAMPURNA,
evidence=("R_1339_vibhavani_structure", "R_433_vibhavini_struct"),
),
"bhavini": RagaStructure(
name="bhavini", tier=BhasaTier.VIBHASA, parent="malavapancama",
amsa=Svara.GA, nyasa=Svara.PA, scale=None,
weak=frozenset({Svara.DHA}),
evidence=("R_c193_bhavini", "R_1338_bhavani_vibhasa"),
),
"kalingi": RagaStructure(
name="kalingi", tier=BhasaTier.ANTARA_BHASA, parent=None,
amsa=Svara.GA, nyasa=Svara.DHA, scale=ScaleType.AUDUVA,
omitted=frozenset({Svara.PA, Svara.RI}),
evidence=("kalingi_structure", "BD_6_1_R020"),
notes="sung by people of Kalinga (deśajā)",
),
"daksinatya": RagaStructure(
name="daksinatya", tier=BhasaTier.VIBHASA, parent="bhinnasadja",
amsa=Svara.PA, nyasa=Svara.PA, scale=None,
evidence=("R_424_dakshinatya",),
),
"dohya": RagaStructure(
name="dohya", tier=BhasaTier.BHASA, parent="takkaraga",
amsa=Svara.GA, nyasa=Svara.SA, scale=ScaleType.AUDUVA,
omitted=frozenset({Svara.PA, Svara.RI}),
evidence=("BD_6_1_R004",),
),
"travani": RagaStructure(
name="travani", tier=BhasaTier.BHASA, parent="takkaraga",
amsa=Svara.DHA, nyasa=Svara.SA, scale=ScaleType.AUDUVA,
omitted=frozenset({Svara.PA, Svara.RI}),
evidence=("travani_takka_pentatonic",),
),
"madhuri": RagaStructure(
name="madhuri", tier=BhasaTier.BHASA, parent="hindolaka",
amsa=Svara.MA, nyasa=Svara.SA, scale=None,
weak=frozenset({Svara.PA}),
evidence=("madhuri_bhasha_features", "hindolaka_five_bhasas"),
),
"chevati": RagaStructure(
name="chevati", tier=BhasaTier.BHASA, parent="hindolaka",
amsa=Svara.SA, nyasa=Svara.SA, scale=ScaleType.SAMPURNA,
evidence=("chevati_mulabhasha_structural", "R_400_chevati",
"hindolaka_five_bhasas"),
notes="classified mūlabhāṣā (root)",
),
"tanavalitika": RagaStructure(
name="tanavalitika", tier=BhasaTier.VIBHASA, parent="takkaraga",
amsa=Svara.MA, nyasa=Svara.SA, scale=None,
evidence=("tanavalitika_amsa_nyasa_and_vibhasa",),
),
"devalavardhani": RagaStructure(
name="devalavardhani", tier=BhasaTier.BHASA, parent="takkaraga",
amsa=Svara.PA, nyasa=Svara.SA, scale=ScaleType.SAMPURNA,
evidence=("R_5p2_1032",),
),
"madhyamagramika": RagaStructure(
name="madhyamagramika", tier=BhasaTier.BHASA, parent="takkaraga",
amsa=Svara.MA, nyasa=Svara.SA, scale=ScaleType.SAMPURNA,
evidence=("R_4p1_955", "R_956_01"),
notes="always saṅkīrṇā (R_4p1_955)",
),
}
# -----------------------------------------------------------------------------
# Bhāṣā / vibhāṣā / antara-bhāṣā → subtype (mūlā / saṅkīrṇā / deśajā / chāyā)
# -----------------------------------------------------------------------------
# Only entries with explicit Brihaddesi assignments. Many bhāṣās have no
# subtype assigned in the source.
BHASA_SUBTYPES: dict[str, BhasaSubtype] = {
# mūlā (root) — chevāṭī, pañcamī, ṣaḍja-bhāṣā, gāndhārī, sauvīrī
# evidence: chevati_mulabhasha_structural, R_mulabhasha_class,
# sauvira_raga_root_of_sauviri_mulabhasha,
# mulabhasha_bhinnashadja_narada
"chevati": BhasaSubtype.MULA,
"pancami": BhasaSubtype.MULA, # R_mulabhasha_class
"sadjabhasha": BhasaSubtype.MULA, # R_mulabhasha_class
"gandhari": BhasaSubtype.MULA, # R_mulabhasha_class
"sauviri": BhasaSubtype.MULA, # sauvira_raga_root_of_sauviri_mulabhasha
# saṅkīrṇā (mixed)
# evidence: R_4p1_955 (madhyamagrāmikā always saṅkīrṇā),
# ravicandrika_structure, R_5p2_966 (Khañjanī saṅkīrṇā)
"madhyamagramika": BhasaSubtype.SANKIRNA,
"ravicandrika": BhasaSubtype.SANKIRNA,
"khanjani": BhasaSubtype.SANKIRNA,
# deśajā (regional)
# evidence: paurali_definition_and_origin (deśī-born of ṭakkarāga),
# desi_saindhavi_regional_designation,
# kalingi_structure (sung by people of Kalinga)
"paurali": BhasaSubtype.DESAJA,
"saindhavi": BhasaSubtype.DESAJA,
"kalingi": BhasaSubtype.DESAJA,
# chāyāmātrāśrayā: named in the four-fold scheme (aff#874) but no
# individual bhāṣā is assigned to this category by an isolated rule.
# See UNRESOLVED.
}
# -----------------------------------------------------------------------------
# Antara-bhāṣā registry — antara-bhāṣās are explicitly identified
# -----------------------------------------------------------------------------
# evidence: BD_6_1_R020 (Kālingī among antara-bhāṣās),
# antara_bhasha_distinct_type (Niṣādavatikā),
# R_5p2_921 (derivation from vibhāṣās).
ANTARA_BHASA_NAMES: tuple[str, ...] = (
"kalingi",
"nisadavatika",
)
# =============================================================================
# TYPES — dataclasses
# =============================================================================
@dataclass(frozen=True)
class GrāmaRāga:
"""A grāma-rāga — top-level rāga associated to a grāma + a gīti class,
born of one or more jātis (R_266_gramaraga).
evidence: R_gramaragas_def, R_266_gramaraga, grama_raga_origin_and_use,
R_073_bhasas_derivation (bhāṣās born of grāma-rāgas)
"""
name: str
grāma: Grāma | None # may be unsourced for some rāgas
giti: GītiClass
parent_jatis: tuple[str, ...] = ()
bhasa_count: BhasaCount | None = None
structure: RagaStructure | None = None
@dataclass(frozen=True)
class Bhāṣā:
"""A bhāṣā / vibhāṣā / antara-bhāṣā — a derived melodic form located
inside a grāma-rāga.
evidence: R_15_bhasa_definition, R_073_bhasas_derivation,
R_920_vibhasa_from_bhasa, R_5p2_921
"""
name: str
tier: BhasaTier
parent_raga_or_bhasa: str # parent grāma-rāga (if bhāṣā) or bhāṣā (if vibhāṣā)
subtype: BhasaSubtype | None = None
structure: RagaStructure | None = None
# =============================================================================
# OPÉRATIONS — interrogations exécutables
# =============================================================================
def grama_raga_category(name: str) -> GītiClass:
"""Return the gīti class (= grāma-rāga category) for a named rāga.
evidence: GRAMARAGA_GITI dict (each entry sourced)
"""
if name not in GRAMARAGA_GITI:
raise KeyError(
f"{name!r} not in GRAMARAGA_GITI — no Brihaddesi rule pins "
f"its gīti class. See SUDDHA_RAGAS / BHINNA_RAGAS / "
f"SADHARANA_RAGAS / VESARA_RAGAS."
)
return GRAMARAGA_GITI[name]
def parent_grama_raga_of(bhasha_name: str) -> str | None:
"""Return the parent grāma-rāga of a named bhāṣā / vibhāṣā.
Walks up the tier: bhāṣā → grāma-rāga (one hop),
vibhāṣā → bhāṣā → grāma-rāga (two hops),
antara-bhāṣā → vibhāṣā → bhāṣā → grāma-rāga (three).
Returns None if the chain is not sourced.
evidence: R_073_bhasas_derivation (bhāṣā←grāma-rāga),
R_920_vibhasa_from_bhasa (vibhāṣā←bhāṣā),
R_5p2_921 (antara-bhāṣā←vibhāṣā)
"""
s = KNOWN_STRUCTURES.get(bhasha_name)
if s is None or s.parent is None:
# Try the bhāṣā-name lists by-grāma-rāga
for raga, names in BHASA_NAMES_BY_GRAMARAGA.items():
if bhasha_name in names:
return raga
return None
# Walk up
visited: set[str] = set()
cur = s.parent
while cur and cur not in visited:
visited.add(cur)
if cur in GRAMARAGA_GITI:
return cur # we reached a top-level grāma-rāga
nxt = KNOWN_STRUCTURES.get(cur)
if nxt is None or nxt.parent is None:
# Fallback: lookup in BHASA_NAMES_BY_GRAMARAGA
for raga, names in BHASA_NAMES_BY_GRAMARAGA.items():
if cur in names:
return raga
return None
cur = nxt.parent
return None
def bhasas_of(grama_raga: str) -> tuple[str, ...]:
"""Return the closed bhāṣā list of a grāma-rāga (if sourced).
evidence: BHASA_NAMES_BY_GRAMARAGA dict (each entry cites a rule_id)
Returns an empty tuple if no sourced list exists.
"""
return BHASA_NAMES_BY_GRAMARAGA.get(grama_raga, ())
def expected_bhasa_count(grama_raga: str) -> BhasaCount | None:
"""Expected bhāṣā count (primary + optional alternative) for a rāga.
evidence: BHASA_COUNTS dict
"""
return BHASA_COUNTS.get(grama_raga)
def vesara_ragas_of(grama: Grāma) -> tuple[str, ...]:
"""Return the vesara rāgas enumerated for a grāma.
evidence: R_389_vesara_def_enum
"""
return VESARA_RAGAS[grama]
def structural_features(name: str) -> RagaStructure | None:
"""Return the sourced structural fingerprint of a rāga / bhāṣā.
evidence: KNOWN_STRUCTURES (each entry cites its rule_ids)
"""
return KNOWN_STRUCTURES.get(name)
# =============================================================================
# CONTRAINTES — validations
# =============================================================================
def is_valid_bhasa_assignment(
bhasha_name: str, claimed_parent: str
) -> bool:
"""True iff `claimed_parent` is a sourced parent of `bhasha_name`.
Checks the BHASA_NAMES_BY_GRAMARAGA registry first (closed enumerations),
then falls back to KNOWN_STRUCTURES.parent.
evidence: R_073_bhasas_derivation, R_920_vibhasa_from_bhasa,
R_5p2_921, BHASA_NAMES_BY_GRAMARAGA entries (each rule_id'd)
"""
if claimed_parent in BHASA_NAMES_BY_GRAMARAGA:
if bhasha_name in BHASA_NAMES_BY_GRAMARAGA[claimed_parent]:
return True
s = KNOWN_STRUCTURES.get(bhasha_name)
if s is not None and s.parent == claimed_parent:
return True
return False
def is_valid_bhasa_count(grama_raga: str, count: int) -> bool:
"""True iff `count` matches the sourced primary or alternative bhāṣā
count for `grama_raga`. True (under-constrained) if no count is sourced.
evidence: BHASA_COUNTS dict (R_398, R_117, R_954, R_c32, R_932,
R_077, R_114, R_922, R_137, hindolaka_five_bhasas, BD_6_1_R016)
"""
bc = BHASA_COUNTS.get(grama_raga)
if bc is None:
return True
if count == bc.primary:
return True
return bc.alternative is not None and count == bc.alternative
def is_valid_total_bhasa_vibhasa(total: int) -> bool:
"""True iff `total` == 73 — the Brihaddesi-wide bhāṣā+vibhāṣā count.
evidence: aff#883, R_073_bhasas_derivation body
"""
return total == TOTAL_BHASA_PLUS_VIBHASA
def is_valid_tier_chain(child_tier: BhasaTier, parent_tier: BhasaTier | None) -> bool:
"""Check the tier hierarchy is respected:
ANTARA_BHASA ← VIBHASA ← BHASA ← (grāma-rāga, parent_tier=None).
evidence: R_073_bhasas_derivation, R_920_vibhasa_from_bhasa, R_5p2_921
"""
if child_tier is BhasaTier.BHASA:
return parent_tier is None
if child_tier is BhasaTier.VIBHASA:
return parent_tier is BhasaTier.BHASA or parent_tier is None
# R_4p0_001: vibhāṣā derived from a rāga (= grāma-rāga); some
# vibhāṣās in the corpus attach directly to a grāma-rāga rather
# than via a bhāṣā (e.g. potā ← ṭakkarāga). Both forms accepted.
if child_tier is BhasaTier.ANTARA_BHASA:
return parent_tier is BhasaTier.VIBHASA
return False
# =============================================================================
# UNRESOLVED — concepts mentioned but not fully pinned by 6b rules
# =============================================================================
UNRESOLVED: tuple[dict[str, str], ...] = (
{
"concept": "gauḍa-rāga enumeration",
"reason": "R_1275_gaudaragas_category names the category but no 6b "
"rule encloses a finite list. Individual gauḍa rāgas are "
"not enumerated here.",
},
{
"concept": "sādhāraṇa count discrepancy",
"reason": "Body verse (aff#589) gives 7 rāgas; TOC (aff#43) lists "
"10 including Bhammāṇapañcama, Pañcamaṣāḍava, Revagupta. "
"Encoded the 7-count list (per verse) — TOC variants kept "
"in GRAMARAGA_GRAMA cross-reference only.",
},
{
"concept": "ṭakkarāga bhāṣā name list",
"reason": "R_117_takkaraga_bhasa_count mixes bhāṣā and vibhāṣā names "
"without distinguishing tiers; primary count is 12 vs. 16 "
"(Yāṣṭika, R_954). No closed bhāṣā list encoded.",
},
{
"concept": "bhinnaṣaḍja bhāṣā name list",
"reason": "R_c32_bhinnasadja gives 9-15 names blended with non-"
"bhāṣā items (Suddha, Madhyama). Not tier-pure; no closed "
"bhāṣā list encoded. R_932_bhinnasadja_enum confirms 9.",
},
{
"concept": "chāyāmātrāśrayā instances",
"reason": "Named as 4th bhāṣā subtype (aff#874) but no individual "
"bhāṣā is assigned to it by an isolated rule.",
},
{
"concept": "shuddhakaisikamadhyama grāma assignment",
"reason": "Disputed in source (cf. domain 3 UNRESOLVED). "
"Excluded from GRAMARAGA_GRAMA.",
},
{
"concept": "Pārvatī parent rāga",
"reason": "R_253_parvati_structural gives TWO variants — one in "
"hindolaka, one born of bhinna-ṣaḍja. Not pinned.",
},
{
"concept": "saṅkīrṇā assignment criterion",
"reason": "sankirna_criterion_unclear (explicit epistemic open "
"issue in source).",
},
{
"concept": "antara-bhāṣā full registry",
"reason": "Only Kālingī (BD_6_1_R020) and Niṣādavatikā "
"(antara_bhasha_distinct_type) explicitly tagged. The "
"tier exists but its members are not closed-enumerated.",
},
{
"concept": "ganaila classification (4 vs 5)",
"reason": "R_1169_ganaila_fourfold and R_1219_ganaila_fivefold_"
"sankara give two counts (4 / 4+saṅkara). Not encoded.",
},
)
# =============================================================================
# Self-test (executed at import-time only if __main__)
# =============================================================================
if __name__ == "__main__":
# Sanity: each rāga in the 5+5+7+8 lists is in GRAMARAGA_GITI
all_listed = (
set(SUDDHA_RAGAS) | set(BHINNA_RAGAS) | set(SADHARANA_RAGAS)
| set(VESARA_RAGAS[Grāma.SADJA]) | set(VESARA_RAGAS[Grāma.MADHYAMA])
)
for r in all_listed:
assert r in GRAMARAGA_GITI, f"{r} missing from GRAMARAGA_GITI"
# Sanity: count consistency
assert len(SUDDHA_RAGAS) == 5, "śuddha rāgas should be 5 (aff#578, aff#587)"
assert len(BHINNA_RAGAS) == 5, "bhinna rāgas should be 5 (aff#578, aff#588)"
assert len(SADHARANA_RAGAS) == 7, "sādhāraṇa rāgas should be 7 (aff#589, aff#841)"
assert len(VESARA_RAGAS[Grāma.SADJA]) == 5, "5 ṣaḍjagrāma vesaras (aff#777)"
assert len(VESARA_RAGAS[Grāma.MADHYAMA]) == 3, "3 madhyamagrāma vesaras (R_389)"
# Sanity: GītiClass enumeration matches the 5-fold gīti of Durgaśakti (aff#542)
assert {g.value for g in GītiClass} == {
"suddha", "bhinna", "gauda", "vesara", "sadharana"
}
# Sanity: gīti category lookup
assert grama_raga_category("takkaraga") is GītiClass.VESARA
assert grama_raga_category("bhinnapancama") is GītiClass.BHINNA
assert grama_raga_category("rupasadharita") is GītiClass.SADHARANA
# Sanity: parent_grama_raga_of walks the hierarchy
assert parent_grama_raga_of("abhiri") == "malavakaisika" # named list
assert parent_grama_raga_of("vibhavani") == "malavapancama" # via KNOWN_STRUCTURES.parent
assert parent_grama_raga_of("vegavanti") == "malavapancama" # closed name list
assert parent_grama_raga_of("varati") == "bhinnapancama" # name list
assert parent_grama_raga_of("dohya") == "takkaraga" # KNOWN_STRUCTURES
assert parent_grama_raga_of("travani") == "takkaraga"
# bhāṣā with no Brihaddesi assignment → None
assert parent_grama_raga_of("ravicandrika") is None
# Sanity: is_valid_bhasa_assignment
assert is_valid_bhasa_assignment("madhuri", "hindolaka")
assert is_valid_bhasa_assignment("varati", "bhinnapancama")
assert is_valid_bhasa_assignment("dohya", "takkaraga")
assert not is_valid_bhasa_assignment("madhuri", "takkaraga") # wrong parent
assert not is_valid_bhasa_assignment("madhuri", "bhinnapancama")
# Sanity: bhāṣā counts and their alternatives
assert is_valid_bhasa_count("takkaraga", 12)
assert is_valid_bhasa_count("takkaraga", 16) # Yāṣṭika alternative
assert not is_valid_bhasa_count("takkaraga", 14)
assert is_valid_bhasa_count("bhinnapancama", 4)
assert not is_valid_bhasa_count("bhinnapancama", 5)
assert is_valid_bhasa_count("malavapancama", 6)
assert is_valid_bhasa_count("hindolaka", 5)
assert is_valid_bhasa_count("hindolaka", 6) # BD_6_1_R016 alt
assert is_valid_bhasa_count("unknown_raga", 99) # under-constrained → True
# Sanity: global 73 count
assert is_valid_total_bhasa_vibhasa(73)
assert not is_valid_total_bhasa_vibhasa(72)
# Sanity: tier chain
assert is_valid_tier_chain(BhasaTier.BHASA, None)
assert is_valid_tier_chain(BhasaTier.VIBHASA, BhasaTier.BHASA)
assert is_valid_tier_chain(BhasaTier.VIBHASA, None) # direct vibhāṣā of rāga (R_4p0_001)
assert is_valid_tier_chain(BhasaTier.ANTARA_BHASA, BhasaTier.VIBHASA)
assert not is_valid_tier_chain(BhasaTier.BHASA, BhasaTier.BHASA)
assert not is_valid_tier_chain(BhasaTier.ANTARA_BHASA, BhasaTier.BHASA)
# Sanity: structural lookup
s = structural_features("takkaraga")
assert s is not None
assert s.amsa is Svara.MA and s.nyasa is Svara.SA
assert Svara.PA in s.omitted # 'devoid of pañcama' (aff#916)
s2 = structural_features("kalingi")
assert s2 is not None
assert s2.tier is BhasaTier.ANTARA_BHASA
assert s2.scale is ScaleType.AUDUVA
# Sanity: vesara list per grāma
assert "takkaraga" in vesara_ragas_of(Grāma.SADJA)
assert "hindolaka" in vesara_ragas_of(Grāma.MADHYAMA)
assert "hindolaka" not in vesara_ragas_of(Grāma.SADJA)
# Sanity: subtype assignments
assert BHASA_SUBTYPES["chevati"] is BhasaSubtype.MULA
assert BHASA_SUBTYPES["madhyamagramika"] is BhasaSubtype.SANKIRNA
assert BHASA_SUBTYPES["paurali"] is BhasaSubtype.DESAJA
assert BHASA_SUBTYPES["kalingi"] is BhasaSubtype.DESAJA
# Sanity: antara-bhāṣā registry minimally populated
assert "kalingi" in ANTARA_BHASA_NAMES
print("bhasha_raga_corpus.py — all self-tests pass")