← domain #0

Source : jati_structure.py

data/library/books/brihaddesi_sharma_1992/formal_grammar/jati_structure.py · 1054 lines · 39627 bytes
"""Domaine 0 — jāti / svara-roles structural Synthèse 6c.3 du Brihaddesi (Sharma 1992, vols I & II) depuis : - 207 règles génératives 6b sur 170 concepts du domaine 0 - 488 affirmations sourcées - partition Leiden domain_id=0 Concepts modelés (top-8 par poids opérationnel) : - aṁśa (svara prédominant), graha (svara de départ), nyāsa (svara de conclusion), apanyāsa (cadence intermédiaire), aniśa (non-aṁśa) - jāti (abstraction mélodique, 18 jātis = 7 ṣaḍjagrāma + 11 madhyamagrāma) - ṣāḍava (hexatonique par omission), auduvita (pentatonique par omission) - kākalī (niṣāda altéré), antara (gāndhāra altéré) - śuddhā / vikṛtā (formes pure / altérée) 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 concepts mentionnés mais non formellement épinglés sont listés dans UNRESOLVED en bas de fichier. Python 3.10+. Importable directement, 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 0) # ============================================================================= # Types partagés avec melodic_derivations (domaine 3) — canonicalisés au niveau # package par 6c.4. Import compat dual : relative quand chargé en tant que # membre du package formal_grammar/, absolute quand le module est exécuté # en standalone pour self-test. 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 class SvaraRole(str, Enum): """Structural role of a svara within a jāti, bhāṣā or rāga. Roles attested with rules in domain 0 : - AMSA : svara prédominant (vādī, principal note) - GRAHA : svara de départ de l'exécution - NYASA : svara de conclusion - APANYASA : cadence intermédiaire interne aux vidārīs - ANISA : non-aṁśa, désignation explicite - VINYASA / SAMNYASA : voir UNRESOLVED (mentionnés ailleurs mais aucune règle 6b en domaine 0) - PARYAYAMSA : aṁśa alternatif utilisé tour à tour - KAKALI : niṣāda altéré (vikṛta) - ANTARA : gāndhāra altéré (vikṛta), utilisé en ascension - SADHARANA : svara "commun" (gāndhāra ou niṣāda) evidence: - AMSA : R_36_amsa_definition, R_4p0_012, amsa_definition_vadi_principal - GRAHA : graha_definition, graha_63_varieties - NYASA : R_006_nyasa_definition - APANYASA : R_c17_apanyasa, R_4p0_005 - ANISA : R_4p1_151 - PARYAYAMSA : R_5p2_1417 - KAKALI : R_kakali_role, R_1853_001 - ANTARA : R_113_antara_identification - SADHARANA : R_1789_sadharana_svaras """ AMSA = "amsa" GRAHA = "graha" NYASA = "nyasa" APANYASA = "apanyasa" ANISA = "anisa" PARYAYAMSA = "paryayamsa" KAKALI = "kakali" ANTARA = "antara" SADHARANA = "sadharana" class JatiForm(str, Enum): """Two forms of a jāti, born by samavāya. evidence: jati_suddha_vikrta_duality, R_42_jati_definition, R_619_suddha_jati_definition, R_211_vikrta_transformation """ SUDDHA = "suddha" # pure / unaltered VIKRTA = "vikrta" # altered / modified class ScaleCardinality(str, Enum): """Density of a jāti's svara content. evidence: r_brd_546_sampurna_def, R_shadava_def, R_5p3_1759, shadava_state_lacks_rsabha, R_027_auduvita_definition """ SAMPURNA = "sampurna" # 7 svaras (complete) SADAVA = "sadava" # 6 svaras (one omission) AUDUVA = "auduva" # 5 svaras (two omissions) # ----------------------------------------------------------------------------- # Comptages canoniques des jātis # ----------------------------------------------------------------------------- # evidence: sadjagrama_seven_jatis, madhyamagrama_jati_basis, R_5p2_723 N_JATIS_PER_GRAMA: dict[Grāma, int] = { Grāma.SADJA: 7, # sadjagrama_seven_jatis Grāma.MADHYAMA: 11, # madhyamagrama_jati_basis, R_5p2_723 } # 18 jātis total ; 63 aṁśas répartis ; 63 = nb total de variétés de jāti # (1 variété par aṁśa-svara). # evidence: R_1389_01 (18 jātis × amsa-svaras → 63 varietes), # R_733_sixty_three_amsas (total amsas = 63), # graha_63_varieties (graha has 63 varieties like aṁśa) N_JATIS_TOTAL: int = 18 # R_1389_01 N_AMSA_TOTAL_ACROSS_JATIS: int = 63 # R_733_sixty_three_amsas, R_1389_01 N_GRAHA_VARIETIES: int = 63 # graha_63_varieties N_SAMSARGAJA_VIKRTA: int = 11 # R_1416_samsargaja_vikrta_jatis # ----------------------------------------------------------------------------- # Sets nominaux de jātis par grāma # ----------------------------------------------------------------------------- # Bodies des règles 6b citent des sous-ensembles à 5 (jātis avec 5 svaras) ; # nous gardons ces listes telles que citées, sans les promouvoir au statut # de "liste complète des 7/11". # evidence: R_214_raktagandhari_class (madhyamagrama 5-svara subset), # sadjamadhyama_among_five_sadjagrama_jatis, # nisadavati_arsabhi_five_svara_jatis # 5 jātis basées dans ṣaḍjagrāma avec 5 svaras # evidence: sadjamadhyama_among_five_sadjagrama_jatis, # nisadavati_arsabhi_five_svara_jatis SADJAGRAMA_FIVE_SVARA_JATIS: tuple[str, ...] = ( "niṣādavatī", "ārṣabhī", "dhaivatī", "ṣaḍjamadhyamā", "ṣaḍjodīcyavatī", ) # 5 jātis basées dans madhyamagrāma (sous-ensemble cité par R_214) # evidence: R_214_raktagandhari_class (body: madhyamagrama_jatis := # [gandhari, raktagandhari, madhyama, pancami, kaisiki]) MADHYAMAGRAMA_NAMED_JATIS: tuple[str, ...] = ( "gāndhārī", "raktagāndhārī", "madhyamā", "pañcamī", "kaiśikī", ) # Sapta-svara-jātis : 7 jātis homonymes des 7 svaras (chacune avec son # svara comme nyāsa). Deux formes : śuddhā et vikṛtā. # evidence: svara_jati_nyasa_naming (svara_jati.count = 7, forms = # {suddha, vikrta} ; nyāsa = own svara) SVARA_JATI_COUNT: int = 7 SVARA_JATI_FORMS: tuple[JatiForm, ...] = (JatiForm.SUDDHA, JatiForm.VIKRTA) # 3 jātis "sādhāraṇakṛtā" (utilisant antara + kākalī) # evidence: sadharanakrta_three_jatis, # sadharanakrta_uses_antara_kakali, R_4p1_419 SADHARANAKRTA_JATIS: tuple[str, ...] = ("madhyamā", "pañcamī", "ṣaḍjamadhyamā") # ----------------------------------------------------------------------------- # Svaras altérés (sādhāraṇa svaras) # ----------------------------------------------------------------------------- # evidence: R_1789_sadharana_svaras (sadharana_svaras := {gandhara, nisada}) SADHARANA_SVARAS: frozenset[Svara] = frozenset({Svara.GA, Svara.NI}) # Kākalī = niṣāda altéré ; Antara = gāndhāra altéré # evidence: R_kakali_role, R_1853_001, R_113_antara_identification, # gandhara_identified_as_antara_vikrta KAKALI_BASE_SVARA: Svara = Svara.NI # R_kakali_role ANTARA_BASE_SVARA: Svara = Svara.GA # R_113_antara_identification # ----------------------------------------------------------------------------- # Spécifications des jātis (extraites verbatim des bodies de règles 6b) # ----------------------------------------------------------------------------- # Pour chaque jāti, nous encodons UNIQUEMENT ce que les règles affirment # explicitement. Les champs absents (None / set vide) sont à interpréter # comme "non spécifié par une règle 6b", pas comme "vide". @dataclass(frozen=True) class JatiSpec: """Specification structurelle d'une jāti. Chaque champ est sourcé par une règle 6b citée dans `evidence`. """ name: str grāma: Grāma | None # None si transgrāma / disputé amsas: frozenset[Svara] # peut être tous svaras nyasas: frozenset[Svara] # 1 ou 2 svaras apanyasas: frozenset[Svara] grahas: frozenset[Svara] anisas: frozenset[Svara] # non-aṁśas explicites forbid_for_sadava: frozenset[Svara] # svaras non-omissibles pour ṣāḍava forbid_for_auduva: frozenset[Svara] # svaras non-omissibles pour auduva cardinality: ScaleCardinality | None parents: frozenset[str] # jātis-mères en cas de dérivation n_amsa_varieties: int | None # ex : ārṣabhī = 10, dhaivatī = 7 evidence: tuple[str, ...] # rule_ids # Spécifications fournies UNIQUEMENT pour les jātis dont les bodies de règles # donnent assez de détail (>= 2 champs structuraux explicites). JATI_SPECS: dict[str, JatiSpec] = { "kaiśikī": JatiSpec( name="kaiśikī", grāma=Grāma.MADHYAMA, amsas=frozenset({Svara.MA}), nyasas=frozenset({Svara.PA}), # apanyāsas = seven svaras OR six excluding ṛṣabha (R_4p0_005) apanyasas=frozenset(SVARA_ORDER) - {Svara.RI}, grahas=frozenset(), anisas=frozenset({Svara.RI}), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset({Svara.DHA}), cardinality=None, parents=frozenset(), n_amsa_varieties=None, evidence=("R_4p0_002", "R_364_amsas"), ), "gāndhārī": JatiSpec( name="gāndhārī", grāma=Grāma.MADHYAMA, amsas=frozenset({Svara.GA}), nyasas=frozenset({Svara.MA}), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset({Svara.RI, Svara.DHA}), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=None, evidence=("R_4p0_003",), ), "pañcamī": JatiSpec( name="pañcamī", grāma=Grāma.MADHYAMA, amsas=frozenset(), # règle ne nomme pas l'aṁśa positif nyasas=frozenset(), apanyasas=frozenset({Svara.RI, Svara.PA, Svara.NI}), grahas=frozenset(), anisas=frozenset({Svara.RI, Svara.PA}), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset({Svara.RI}), cardinality=None, parents=frozenset(), n_amsa_varieties=None, evidence=("R_4p0_007",), ), "madhyamā": JatiSpec( name="madhyamā", grāma=Grāma.MADHYAMA, amsas=frozenset({Svara.PA}), nyasas=frozenset(), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=ScaleCardinality.SAMPURNA, parents=frozenset(), n_amsa_varieties=5, # R_4p0_012 : madhyamā count=5 evidence=("BD_6_1_R023", "R_4p0_012"), ), "ṣaḍjamadhyamā": JatiSpec( name="ṣaḍjamadhyamā", grāma=Grāma.SADJA, # "all svaras as aṁśa" — R_720_sadja_madhyama amsas=frozenset(SVARA_ORDER), nyasas=frozenset({Svara.SA, Svara.MA}), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=None, evidence=("R_720_sadja_madhyama", "amsa_constraint_sadjamadhyama_sadava"), ), "ṣāḍjī": JatiSpec( name="ṣāḍjī", grāma=Grāma.SADJA, amsas=frozenset({Svara.SA}), # śuddhā jāti named after its aṁśa nyasas=frozenset({Svara.SA}), # svara-jāti -> nyāsa = own svara apanyasas=frozenset(), grahas=frozenset({Svara.SA}), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=10, # R_4p0_012 : 1 śuddha + 5 vikṛta + 4 ṣāḍava evidence=( "sadji_shuddha_amsa_self_named", "svara_jati_nyasa_naming", "R_4p0_012", ), ), "dhaivatī": JatiSpec( name="dhaivatī", grāma=Grāma.SADJA, amsas=frozenset({Svara.RI, Svara.DHA}), nyasas=frozenset({Svara.DHA}), # svara-jāti apanyasas=frozenset({Svara.RI, Svara.DHA, Svara.MA}), grahas=frozenset({Svara.RI, Svara.DHA}), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=7, # dhaivati_sevenfold_amsa_structure evidence=( "dhaivati_amsa_set", "dhaivati_sevenfold_amsa_structure", "dhaivati_grahas_amsas_structural", "svara_jati_nyasa_naming", ), ), "naiṣādī": JatiSpec( name="naiṣādī", grāma=None, # non spécifié par la règle amsas=frozenset(), nyasas=frozenset({Svara.NI}), apanyasas=frozenset({Svara.NI, Svara.GA, Svara.RI}), grahas=frozenset(), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=None, evidence=("R_4p1_356",), ), "ārṣabhī": JatiSpec( name="ārṣabhī", grāma=Grāma.SADJA, amsas=frozenset({Svara.RI}), # svara-jāti (nyāsa = ṛṣabha) nyasas=frozenset({Svara.RI}), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset(), n_amsa_varieties=10, # R_c752_arsabhi evidence=("R_c752_arsabhi", "svara_jati_nyasa_naming"), ), "raktagāndhārī": JatiSpec( name="raktagāndhārī", grāma=Grāma.MADHYAMA, amsas=frozenset(SVARA_ORDER) - {Svara.DHA, Svara.RI}, nyasas=frozenset(), apanyasas=frozenset({Svara.MA}), grahas=frozenset(), anisas=frozenset(), forbid_for_sadava=frozenset({Svara.RI}), # ṣāḍava omits ṛṣabha forbid_for_auduva=frozenset({Svara.RI, Svara.DHA}), cardinality=None, parents=frozenset({"gāndhārī", "madhyamā", "pañcamī", "naiṣādī"}), n_amsa_varieties=5, # R_4p0_012 raktagāndhārī count=5 evidence=( "R_raktagandhari_jati", "R_214_raktagandhari_class", "R_4p0_012", ), ), "āndhrī": JatiSpec( name="āndhrī", grāma=None, amsas=frozenset(), nyasas=frozenset(), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset({Svara.SA, Svara.MA, Svara.DHA}), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset({"ārṣabhī", "gāndhārī"}), n_amsa_varieties=None, evidence=("R_4p0_026",), ), "kārmāravī": JatiSpec( name="kārmāravī", grāma=Grāma.MADHYAMA, amsas=frozenset(), nyasas=frozenset({Svara.PA}), apanyasas=frozenset(), grahas=frozenset(), anisas=frozenset({Svara.SA, Svara.GA, Svara.MA}), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=ScaleCardinality.SAMPURNA, parents=frozenset({"ārṣabhī", "gāndhārī", "pañcamī"}), n_amsa_varieties=None, evidence=( "karmaravi_formation", "karmaravi_non_amsas_and_nyasa", "R_337_karmaravi_struct", ), ), "nandayantī": JatiSpec( name="nandayantī", grāma=None, amsas=frozenset({Svara.PA}), nyasas=frozenset({Svara.GA}), apanyasas=frozenset({Svara.MA, Svara.PA}), grahas=frozenset({Svara.GA}), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=None, parents=frozenset({"ārṣabhī", "gāndhārī", "pañcamī"}), n_amsa_varieties=None, evidence=("R_224_nandayanti_structural", "R_c83_nandayanti"), ), "sadjakaiśikī": JatiSpec( name="sadjakaiśikī", grāma=Grāma.SADJA, amsas=frozenset({Svara.SA, Svara.GA, Svara.PA}), nyasas=frozenset(), apanyasas=frozenset(), grahas=frozenset({Svara.SA, Svara.GA, Svara.PA}), anisas=frozenset(), forbid_for_sadava=frozenset(), forbid_for_auduva=frozenset(), cardinality=ScaleCardinality.SAMPURNA, parents=frozenset(), n_amsa_varieties=None, evidence=("sadjakaisiki_structure",), ), } # ============================================================================= # TYPES — dataclasses opérationnelles # ============================================================================= @dataclass(frozen=True) class SvaraAssignment: """Affecte un rôle structural à un svara dans le contexte d'une jāti / bhāṣā. evidence: amsa_assignment_per_bhasa (chaque bhāṣā/rāga a un aṁśa assigné, distinct de son nyāsa) """ svara: Svara role: SvaraRole @dataclass(frozen=True) class JatiInstance: """Instance concrète d'une jāti, avec ses svaras et leurs rôles. Une jāti est l'abstraction mélodique fondamentale (R_42_jati_definition). Elle a deux formes : śuddhā (où graha=apanyāsa=aniśa, nyāsa en mandra, completion) et vikṛtā (dérivée par altération). evidence: - R_42_jati_definition (jāti = melodic_abstraction, kinds {suddha,vikrta}) - R_619_suddha_jati_definition (śuddhā = identical graha/apanyāsa/aniśa) - R_211_vikrta_transformation (vikṛtā dérivée de śuddhā par altération de aṁśa/apanyāsa/completeness ; INVARIANTS = nyāsa, alpatva/bahutva) - R_1419_alpatva_bahutva_invariant """ name: str form: JatiForm grāma: Grāma | None svaras: frozenset[Svara] # svaras présents assignments: tuple[SvaraAssignment, ...] cardinality: ScaleCardinality def __post_init__(self) -> None: n = len(self.svaras) if n not in (5, 6, 7): raise ValueError( f"jāti {self.name}: svara count must be 5/6/7, got {n}" ) if n == 7 and self.cardinality != ScaleCardinality.SAMPURNA: raise ValueError( "7 svaras → cardinality must be SAMPURNA " "— see r_brd_546_sampurna_def" ) if n == 6 and self.cardinality != ScaleCardinality.SADAVA: raise ValueError( "6 svaras → cardinality must be SADAVA — see R_shadava_def" ) if n == 5 and self.cardinality != ScaleCardinality.AUDUVA: raise ValueError( "5 svaras → cardinality must be AUDUVA — see R_5p3_1759" ) for a in self.assignments: if a.svara not in self.svaras: raise ValueError( f"assignment for {a.svara.value} not in jāti svaras " f"— role {a.role.value} requires svara presence" ) # ----- accesseurs par rôle ----- def svaras_with_role(self, role: SvaraRole) -> frozenset[Svara]: return frozenset(a.svara for a in self.assignments if a.role == role) @property def amsas(self) -> frozenset[Svara]: """evidence: R_36_amsa_definition, R_4p0_012""" return self.svaras_with_role(SvaraRole.AMSA) @property def grahas(self) -> frozenset[Svara]: """evidence: graha_definition""" return self.svaras_with_role(SvaraRole.GRAHA) @property def nyasas(self) -> frozenset[Svara]: """evidence: R_006_nyasa_definition""" return self.svaras_with_role(SvaraRole.NYASA) @property def apanyasas(self) -> frozenset[Svara]: """evidence: R_c17_apanyasa, R_4p0_005""" return self.svaras_with_role(SvaraRole.APANYASA) @property def anisas(self) -> frozenset[Svara]: """evidence: R_4p1_151""" return self.svaras_with_role(SvaraRole.ANISA) # ============================================================================= # OPÉRATIONS — fonctions pures # ============================================================================= def cardinality_of(n_svaras: int) -> ScaleCardinality: """Map a svara count to its cardinality category. evidence: r_brd_546_sampurna_def (sampūrṇa = 7), R_shadava_def (6), R_5p3_1759 (auduva = 5) """ if n_svaras == 7: return ScaleCardinality.SAMPURNA if n_svaras == 6: return ScaleCardinality.SADAVA if n_svaras == 5: return ScaleCardinality.AUDUVA raise ValueError( f"cardinality undefined for {n_svaras} svaras " "(only 5/6/7 attested) — see R_shadava_def, R_5p3_1759, " "r_brd_546_sampurna_def" ) def is_suddha_jati(j: JatiInstance) -> bool: """Test si une jāti est śuddhā. Condition (R_619_suddha_jati_definition): graha == apanyāsa == aniśa (identité des rôles structuraux) ET nyāsa en registre mandra ET `is_complete` (= sampūrṇa). Nous testons la partie observable depuis la JatiInstance : completion sampūrṇa + identité graha/apanyāsa/aniśa (lorsque ces sets sont définis). evidence: R_619_suddha_jati_definition, suddha_definition_and_giti_rank, R_42_jati_definition """ if j.cardinality != ScaleCardinality.SAMPURNA: return False grahas, apanyas, anisas = j.grahas, j.apanyasas, j.anisas if grahas and apanyas and anisas: # all three sets must coincide return grahas == apanyas == anisas # If any role is not assigned in this instance, we cannot positively # confirm śuddhā — return False conservatively. return False def is_valid_omission_for_sadava(jati_name: str, omit: Svara) -> bool: """Test si l'omission d'un svara pour former ṣāḍava est admise dans cette jāti. Une jāti admet une omission ssi le svara n'est pas dans `forbid_for_sadava` et n'est pas un aṁśa (R_36_amsa_murchana_relation : l'aṁśa ne s'omet pas, par construction). evidence: R_c25_sadava (samvādī de l'aṁśa non omissible), amsa_samvadi_cannot_be_omitted, R_42_jati_destruction_vivadin """ spec = JATI_SPECS.get(jati_name) if spec is None: raise KeyError( f"jāti {jati_name} unspecified — see JATI_SPECS and UNRESOLVED" ) if omit in spec.forbid_for_sadava: return False if omit in spec.amsas: return False return True def is_valid_omission_for_auduva( jati_name: str, omit_pair: frozenset[Svara] ) -> bool: """Idem pour auduva : aucune des deux svaras omises ne doit être dans `forbid_for_auduva` ni dans les aṁśas. evidence: R_027_auduvita_definition (auduva = devoid of ṛṣabha+dhaivata when prescribed), R_143_auduvita_state """ spec = JATI_SPECS.get(jati_name) if spec is None: raise KeyError( f"jāti {jati_name} unspecified — see JATI_SPECS and UNRESOLVED" ) if len(omit_pair) != 2: raise ValueError("auduva omission must be a pair (size 2)") if omit_pair & spec.forbid_for_auduva: return False if omit_pair & spec.amsas: return False return True def derive_sadava_form(jati_name: str, omit: Svara) -> frozenset[Svara]: """Forme ṣāḍava (6 svaras) d'une jāti par omission d'un svara. Lève ValueError si l'omission n'est pas valide. evidence: R_c25_sadava (forme hexatonique par omission, contrainte non-omission saṃvādī aṁśa), R_shadava_def, shadava_state_lacks_rsabha """ if not is_valid_omission_for_sadava(jati_name, omit): raise ValueError( f"omission de {omit.value} interdite pour {jati_name} " "— voir R_c25_sadava, amsa_samvadi_cannot_be_omitted" ) return frozenset(SVARA_ORDER) - {omit} def derive_auduva_form( jati_name: str, omit_pair: frozenset[Svara] ) -> frozenset[Svara]: """Forme auduva (5 svaras) par omission d'une paire de svaras. evidence: R_027_auduvita_definition, R_143_auduvita_state, R_763_auduva_dhaivati (auduva of dhaivatī omits pa+sa) """ if not is_valid_omission_for_auduva(jati_name, omit_pair): raise ValueError( f"omission {{{', '.join(s.value for s in omit_pair)}}} " f"interdite pour {jati_name} " "— voir R_027_auduvita_definition, R_143_auduvita_state" ) result = frozenset(SVARA_ORDER) - omit_pair if len(result) != 5: raise ValueError("auduva form must yield exactly 5 svaras") return result def assign_kakali_role(jati_name: str, svara: Svara) -> SvaraRole | None: """Détermine le rôle "altéré" (kākalī ou antara) d'un svara dans une jāti sādhāraṇakṛtā. - niṣāda → KAKALI (R_kakali_role, R_1853_001) - gāndhāra → ANTARA (R_113_antara_identification) - autres → None Note : la liste explicite des rāgas où niṣāda devient kākalī (R_kakali_role) n'est pas reproduite ici car ce sont des rāgas, pas des jātis. evidence: R_kakali_role, R_1853_001, R_113_antara_identification, sadharanakrta_uses_antara_kakali, R_4p1_419 """ if jati_name not in SADHARANAKRTA_JATIS: return None if svara == KAKALI_BASE_SVARA: return SvaraRole.KAKALI if svara == ANTARA_BASE_SVARA: return SvaraRole.ANTARA return None def amsa_predecessor(amsa: Svara) -> Svara: """Le svara immédiatement en dessous d'un aṁśa est le mandra de cette jāti. evidence: amsa_predecessor_is_mandra, amsa_role_and_mandra_relation """ idx = SVARA_ORDER.index(amsa) return SVARA_ORDER[(idx - 1) % len(SVARA_ORDER)] def total_amsas_across_jatis() -> int: """Total canonique des aṁśas à travers les 18 jātis = 63. evidence: R_733_sixty_three_amsas, R_1389_01 """ return N_AMSA_TOTAL_ACROSS_JATIS # ============================================================================= # CONTRAINTES — validations # ============================================================================= def validate_sadjamadhyama_amsa(amsa: Svara, n_svaras: int) -> None: """Ṣaḍjamadhyamā ne peut être hexatonique (6 svaras) quand niṣāda est aṁśa. evidence: amsa_constraint_sadjamadhyama_sadava (aff#317) """ if amsa == Svara.NI and n_svaras == 6: raise ValueError( "Ṣaḍjamadhyamā interdite en 6 svaras quand niṣāda est aṁśa " "— voir amsa_constraint_sadjamadhyama_sadava (aff#317)" ) def validate_amsa_samvadi_present( amsa: Svara, samvadi: Svara, present: frozenset[Svara] ) -> None: """Le saṃvādī d'un aṁśa ne peut pas être omis d'une jāti. evidence: amsa_samvadi_cannot_be_omitted (R_c25_sadava recouvrant) """ if amsa not in present: raise ValueError( f"aṁśa {amsa.value} doit être présent — voir R_36_amsa_definition" ) if samvadi not in present: raise ValueError( f"saṃvādī de l'aṁśa ({samvadi.value}) ne peut être omis " "— voir amsa_samvadi_cannot_be_omitted" ) def validate_jati_count_per_grama(grāma: Grāma, count: int) -> bool: """Vérifie qu'un comptage de jātis pour un grāma respecte les totaux canoniques. evidence: sadjagrama_seven_jatis (7), madhyamagrama_jati_basis (11), R_5p2_723 (eleven madhyama jātis) """ return count == N_JATIS_PER_GRAMA[grāma] def validate_vikrta_invariants( suddha: JatiInstance, vikrta_candidate: JatiInstance ) -> None: """Une jāti vikṛtā dérive d'une śuddhā par altération de aṁśa/apanyāsa/completeness, MAIS alpatva-bahutva et nyāsa sont invariants. evidence: R_211_vikrta_transformation, R_1419_alpatva_bahutva_invariant """ if vikrta_candidate.form != JatiForm.VIKRTA: raise ValueError( "candidat must have form=VIKRTA — voir R_211_vikrta_transformation" ) if suddha.form != JatiForm.SUDDHA: raise ValueError("source must have form=SUDDHA") # nyāsa invariant if suddha.nyasas and vikrta_candidate.nyasas: if suddha.nyasas != vikrta_candidate.nyasas: raise ValueError( f"nyāsa doit être invariant dans la dérivation vikṛtā " f"({suddha.nyasas} → {vikrta_candidate.nyasas}) " "— voir R_1419_alpatva_bahutva_invariant" ) # ============================================================================= # UNRESOLVED — concepts cités mais non formalisés par règle 6b en domaine 0 # ============================================================================= UNRESOLVED: tuple[dict[str, str], ...] = ( { "concept": "saṁnyāsa / vinyāsa", "reason": "rôles structuraux mentionnés dans la tradition (avec " "graha/aṁśa/nyāsa/apanyāsa) mais aucune règle 6b en " "domaine 0 ne les pose explicitement ; conservés dans " "SvaraRole comme placeholder commenté mais sans " "assignation opérationnelle.", }, { "concept": "liste complète des 7 jātis ṣaḍjagrāma", "reason": "sadjagrama_seven_jatis affirme n=7 ; seuls 5 sont " "nommés (sadjamadhyama_among_five_sadjagrama_jatis) ; " "les 2 autres ne sont pas isolés par une règle 6b.", }, { "concept": "liste complète des 11 jātis madhyamagrāma", "reason": "madhyamagrama_jati_basis + R_5p2_723 donnent n=11 ; " "R_214 nomme 5 (gandhari/raktagandhari/madhyama/" "pancami/kaisiki) ; les 6 autres (saṁsargajā vikṛtā) " "ne sont pas individuellement isolées par règle 6b.", }, { "concept": "bahutva / alpatva exacts par svara", "reason": "R_1419 affirme l'invariance, sadjakaisiki_structure et " "R_143_auduvita_state donnent des cas, mais aucune " "règle ne calcule alpatva/bahutva opérationnellement.", }, { "concept": "antaramārga (parcours intermédiaire)", "reason": "mentionné par R_1416 (saṁsargajā vikṛtā jātis liés à " "antara-mārga) et sadji_nishada_rsabha_not_paryayamsas, " "mais aucune définition opérationnelle en domaine 0.", }, { "concept": "sangati exhaustif", "reason": "sangati_attested_pairs liste des paires attestées mais " "sangati_definition_concert dit qu'il est melodic_pur " "et indépendant de samvāda — non-exhaustif.", }, { "concept": "śuddhakaiśika-madhyama grāma assignment", "reason": "Disputé en domaine 3 (R_486) ; ici R_846_kaishiki_parent " "dit né de kaiśikī + ṣaḍjamadhyamā, sans grāma fixe.", }, ) # ============================================================================= # Self-test # ============================================================================= if __name__ == "__main__": # ------- Sanity : énumérations ------- assert len(SVARA_ORDER) == 7 assert N_JATIS_PER_GRAMA[Grāma.SADJA] + N_JATIS_PER_GRAMA[Grāma.MADHYAMA] \ == N_JATIS_TOTAL assert SADHARANA_SVARAS == {Svara.GA, Svara.NI} assert KAKALI_BASE_SVARA == Svara.NI assert ANTARA_BASE_SVARA == Svara.GA # ------- Sanity : cardinality ------- assert cardinality_of(7) == ScaleCardinality.SAMPURNA assert cardinality_of(6) == ScaleCardinality.SADAVA assert cardinality_of(5) == ScaleCardinality.AUDUVA try: cardinality_of(4) except ValueError: pass else: raise AssertionError("cardinality_of(4) should raise") # ------- Sanity : amsa predecessor (mandra) ------- assert amsa_predecessor(Svara.MA) == Svara.GA assert amsa_predecessor(Svara.SA) == Svara.NI # wrap-around # ------- Sanity : kākalī / antara ------- assert assign_kakali_role("madhyamā", Svara.NI) == SvaraRole.KAKALI assert assign_kakali_role("madhyamā", Svara.GA) == SvaraRole.ANTARA assert assign_kakali_role("madhyamā", Svara.SA) is None # non-sādhāraṇakṛtā → no altered roles assert assign_kakali_role("kaiśikī", Svara.NI) is None # ------- Sanity : validate_sadjamadhyama_amsa ------- validate_sadjamadhyama_amsa(Svara.SA, 6) # OK validate_sadjamadhyama_amsa(Svara.NI, 7) # OK try: validate_sadjamadhyama_amsa(Svara.NI, 6) except ValueError: pass else: raise AssertionError("should reject Ṣaḍjamadhyamā 6sv with NI as aṁśa") # ------- Sanity : validate_amsa_samvadi_present ------- validate_amsa_samvadi_present( Svara.SA, Svara.PA, frozenset({Svara.SA, Svara.PA, Svara.MA}) ) try: validate_amsa_samvadi_present( Svara.SA, Svara.PA, frozenset({Svara.SA, Svara.MA}) ) except ValueError: pass else: raise AssertionError("missing saṃvādī should raise") # ------- Sanity : validate_jati_count_per_grama ------- assert validate_jati_count_per_grama(Grāma.SADJA, 7) assert validate_jati_count_per_grama(Grāma.MADHYAMA, 11) assert not validate_jati_count_per_grama(Grāma.SADJA, 6) # ------- Sanity : derive_sadava_form / derive_auduva_form ------- # raktagāndhārī : aṁśas exclude {DHA, RI}, forbid_for_sadava = {RI} # → can omit DHA (not aṁśa, not forbidden) form = derive_sadava_form("raktagāndhārī", Svara.DHA) assert len(form) == 6 assert Svara.DHA not in form # → cannot omit RI (forbidden) or any of the amsas try: derive_sadava_form("raktagāndhārī", Svara.RI) except ValueError: pass else: raise AssertionError("RI omission in raktagāndhārī should fail") try: derive_sadava_form("raktagāndhārī", Svara.SA) # SA is aṁśa except ValueError: pass else: raise AssertionError("omitting aṁśa SA should fail") # auduva pair {RI, DHA} : RI is forbidden for raktagāndhārī sādhāva, # and forbid_for_auduva = {RI, DHA} both forbidden; expect failure try: derive_auduva_form("raktagāndhārī", frozenset({Svara.RI, Svara.DHA})) except ValueError: pass else: raise AssertionError("auduva {RI,DHA} in raktagāndhārī should fail " "(both forbidden)") # But pañcamī : amsas = ∅ (unset), forbid_for_auduva = {RI}, so {NI, MA} # should pass (neither RI nor an aṁśa). pair = frozenset({Svara.NI, Svara.MA}) assert is_valid_omission_for_auduva("pañcamī", pair) f5 = derive_auduva_form("pañcamī", pair) assert len(f5) == 5 # ------- Sanity : JatiInstance construction & role accessors ------- inst = JatiInstance( name="ṣaḍjamadhyamā-instance", form=JatiForm.SUDDHA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER), assignments=( SvaraAssignment(Svara.SA, SvaraRole.NYASA), SvaraAssignment(Svara.MA, SvaraRole.NYASA), SvaraAssignment(Svara.SA, SvaraRole.AMSA), SvaraAssignment(Svara.RI, SvaraRole.AMSA), SvaraAssignment(Svara.GA, SvaraRole.AMSA), SvaraAssignment(Svara.MA, SvaraRole.AMSA), SvaraAssignment(Svara.PA, SvaraRole.AMSA), SvaraAssignment(Svara.DHA, SvaraRole.AMSA), SvaraAssignment(Svara.NI, SvaraRole.AMSA), ), cardinality=ScaleCardinality.SAMPURNA, ) assert inst.nyasas == {Svara.SA, Svara.MA} assert inst.amsas == frozenset(SVARA_ORDER) # ------- Sanity : JatiInstance bad construction ------- try: JatiInstance( name="bad", form=JatiForm.SUDDHA, grāma=None, svaras=frozenset({Svara.SA, Svara.RI, Svara.GA}), # 3 → invalid assignments=(), cardinality=ScaleCardinality.AUDUVA, ) except ValueError: pass else: raise AssertionError("3-svara jāti should fail validation") try: JatiInstance( name="mismatch", form=JatiForm.SUDDHA, grāma=None, svaras=frozenset(SVARA_ORDER), assignments=(), cardinality=ScaleCardinality.SADAVA, # mismatch with 7 svaras ) except ValueError: pass else: raise AssertionError("cardinality/count mismatch should fail") # assignment for absent svara try: JatiInstance( name="absent", form=JatiForm.SUDDHA, grāma=None, svaras=frozenset({Svara.SA, Svara.RI, Svara.GA, Svara.MA, Svara.PA}), assignments=(SvaraAssignment(Svara.NI, SvaraRole.AMSA),), cardinality=ScaleCardinality.AUDUVA, ) except ValueError: pass else: raise AssertionError("assignment for absent svara should fail") # ------- Sanity : vikṛtā invariants ------- suddha = JatiInstance( name="ṣāḍjī-śuddhā", form=JatiForm.SUDDHA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER), assignments=(SvaraAssignment(Svara.SA, SvaraRole.NYASA),), cardinality=ScaleCardinality.SAMPURNA, ) vikrta_ok = JatiInstance( name="ṣāḍjī-vikṛtā", form=JatiForm.VIKRTA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER) - {Svara.NI}, assignments=(SvaraAssignment(Svara.SA, SvaraRole.NYASA),), cardinality=ScaleCardinality.SADAVA, ) validate_vikrta_invariants(suddha, vikrta_ok) vikrta_bad = JatiInstance( name="ṣāḍjī-bad-vikṛtā", form=JatiForm.VIKRTA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER) - {Svara.NI}, assignments=(SvaraAssignment(Svara.MA, SvaraRole.NYASA),), # changed cardinality=ScaleCardinality.SADAVA, ) try: validate_vikrta_invariants(suddha, vikrta_bad) except ValueError: pass else: raise AssertionError("nyāsa-changed vikṛtā should fail") # ------- Sanity : is_suddha_jati ------- # ṣaḍja-madhyamā with graha=apanyāsa=aniśa all equal triggers SUDDHA test inst2 = JatiInstance( name="test-suddha", form=JatiForm.SUDDHA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER), assignments=( SvaraAssignment(Svara.SA, SvaraRole.GRAHA), SvaraAssignment(Svara.SA, SvaraRole.APANYASA), SvaraAssignment(Svara.SA, SvaraRole.ANISA), SvaraAssignment(Svara.NI, SvaraRole.NYASA), ), cardinality=ScaleCardinality.SAMPURNA, ) assert is_suddha_jati(inst2) # mismatched sets → not śuddhā inst3 = JatiInstance( name="test-not-suddha", form=JatiForm.SUDDHA, grāma=Grāma.SADJA, svaras=frozenset(SVARA_ORDER), assignments=( SvaraAssignment(Svara.SA, SvaraRole.GRAHA), SvaraAssignment(Svara.MA, SvaraRole.APANYASA), # differs SvaraAssignment(Svara.SA, SvaraRole.ANISA), ), cardinality=ScaleCardinality.SAMPURNA, ) assert not is_suddha_jati(inst3) # ------- Sanity : total_amsas ------- assert total_amsas_across_jatis() == 63 assert N_GRAHA_VARIETIES == 63 # ------- Sanity : JATI_SPECS coverage ------- assert "kaiśikī" in JATI_SPECS assert JATI_SPECS["raktagāndhārī"].n_amsa_varieties == 5 assert JATI_SPECS["ārṣabhī"].n_amsa_varieties == 10 # āndhrī's parents are {ārṣabhī, gāndhārī} (R_4p0_026) assert JATI_SPECS["āndhrī"].parents == {"ārṣabhī", "gāndhārī"} print("jati_structure — all self-tests pass")