skrutable 2.1.3__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {skrutable-2.1.3 → skrutable-2.2.0}/PKG-INFO +1 -1
  2. skrutable-2.2.0/src/skrutable/__init__.py +1 -0
  3. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/config.json +5 -1
  4. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/meter_identification.py +144 -44
  5. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/meter_patterns.py +14 -0
  6. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/scansion.py +1 -0
  7. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable.egg-info/PKG-INFO +1 -1
  8. skrutable-2.1.3/src/skrutable/__init__.py +0 -1
  9. {skrutable-2.1.3 → skrutable-2.2.0}/LICENSE.md +0 -0
  10. {skrutable-2.1.3 → skrutable-2.2.0}/README.md +0 -0
  11. {skrutable-2.1.3 → skrutable-2.2.0}/setup.cfg +0 -0
  12. {skrutable-2.1.3 → skrutable-2.2.0}/setup.py +0 -0
  13. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/config.py +0 -0
  14. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/generate_scheme_vectors.py +0 -0
  15. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/impossible_bigrams.json +0 -0
  16. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/manual.md +0 -0
  17. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/phonemes.py +0 -0
  18. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/run_examples.py +0 -0
  19. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/scheme_detection.py +0 -0
  20. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/scheme_maps.py +0 -0
  21. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/scheme_vectors.json +0 -0
  22. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/scheme_vectors_mbh.py +0 -0
  23. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/splitting.py +0 -0
  24. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/transliteration.py +0 -0
  25. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable/virAma_avoidance.py +0 -0
  26. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable.egg-info/SOURCES.txt +0 -0
  27. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable.egg-info/dependency_links.txt +0 -0
  28. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable.egg-info/requires.txt +0 -0
  29. {skrutable-2.1.3 → skrutable-2.2.0}/src/skrutable.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: skrutable
3
- Version: 2.1.3
3
+ Version: 2.2.0
4
4
  Summary: skrutable library for working with Sanskrit text
5
5
  Home-page: https://github.com/tylergneill/skrutable
6
6
  Author: Tyler Neill
@@ -0,0 +1 @@
1
+ __version__ = "2.2.0"
@@ -12,7 +12,11 @@
12
12
  "max score" : 9,
13
13
  "anuṣṭubh, full, both halves perfect)" : 9,
14
14
  "anuṣṭubh, full, one half perfect, one imperfect)" : 7,
15
+ "anuṣṭubh, full, both halves imperfect)": 5,
16
+ "anuṣṭubh, full, one half perfect, one length error)": 6,
17
+ "anuṣṭubh, full, one half imperfect, one length error)": 4,
15
18
  "anuṣṭubh, half, single half perfect)" : 9,
19
+ "anuṣṭubh, half, single half imperfect)": 5,
16
20
  "samavṛtta, perfect" : 9,
17
21
  "samavṛtta, imperfect (3)" : 6,
18
22
  "samavṛtta, imperfect (2)" : 5,
@@ -22,7 +26,7 @@
22
26
  "viṣamavṛtta, perfect" : 9,
23
27
  "upajāti, perfect" : 7,
24
28
  "upajāti, imperfect" : 6,
25
- "upajāti, non-triṣṭubh, perfect" : 5,
29
+ "upajāti, non-triṣṭubh, perfect" : 4.5,
26
30
  "upajāti, triṣṭubh-jagatī-saṃkara, perfect" : 4,
27
31
  "upajāti, non-triṣṭubh, imperfect" : 3,
28
32
  "jāti, perfect" : 8,
@@ -3,6 +3,8 @@ from skrutable import meter_patterns
3
3
  from skrutable.config import load_config_dict_from_json_file
4
4
  import re
5
5
  from copy import copy
6
+ from dataclasses import dataclass, field
7
+ from typing import Optional
6
8
 
7
9
  # load config variables
8
10
  config = load_config_dict_from_json_file()
@@ -12,6 +14,24 @@ default_resplit_keep_midpoint = config["default_resplit_keep_midpoint"] # e.g.
12
14
  disable_non_trizwuB_upajAti = config["disable_non_trizwuB_upajAti"] # e.g. True
13
15
  meter_scores = config["meter_scores"] # dict
14
16
 
17
+
18
+ @dataclass
19
+ class Diagnostic:
20
+ perfect_id_label: Optional[str] = None # 'pathyā', 'ma-vipulā', etc.; None if imperfect
21
+ imperfect_id_label: Optional[str] = None # 'asamīcīnā ma-vipulā', etc.; None if perfect or unidentified
22
+ failure_code: Optional[str] = None # short internal code, e.g. 'hahn_general_2'; None if perfect
23
+ problem_syllables: dict = field(default_factory=lambda: {'odd': [], 'even': []})
24
+
25
+ def perfect(self):
26
+ return self.perfect_id_label is not None
27
+
28
+ def imperfect(self):
29
+ return self.imperfect_id_label is not None
30
+
31
+ def length_error(self):
32
+ return self.failure_code in ('hypermetric', 'hypometric')
33
+
34
+
15
35
  class VerseTester(object):
16
36
  """
17
37
  Internal agent-style object.
@@ -29,6 +49,7 @@ class VerseTester(object):
29
49
  self.resplit_option = default_resplit_option # string
30
50
  self.resplit_keep_midpoint = default_resplit_keep_midpoint # bool
31
51
  self.identification_attempt_count = 0
52
+ self._anuzwuB_half_cache = {} # cleared per wiggle_identify run
32
53
 
33
54
  def combine_results(self, Vrs, new_label, new_score):
34
55
  old_label = Vrs.meter_label or ''
@@ -55,24 +76,55 @@ class VerseTester(object):
55
76
  """
56
77
  Accepts two strings of syllable weights (e.g. 'llglgllg').
57
78
  Tries to match to known odd-even 'anuṣṭubh' foot pairings:
58
- pathya
79
+ pathyā
59
80
  vipulā (4.5 subtypes: na, ra, ma, bha, and variant bha).
60
- Returns string result if match found, None otherwise.
81
+ Returns Diagnostic with perfect_id_label set if match found, None otherwise.
61
82
 
62
83
  """
63
- # check even pāda
64
- regex = re.compile(meter_patterns.anuzwuB_pAda['even'])
65
- if not re.match(regex, even_pAda_weights):
66
- return None
67
-
68
- # check odd pāda (both 'paTyA' and 'vipulA')
69
- for weights_pattern in meter_patterns.anuzwuB_pAda['odd'].keys():
70
- regex = re.compile(weights_pattern)
71
- if re.match(regex, odd_pAda_weights):
72
- return meter_patterns.anuzwuB_pAda['odd'][weights_pattern]
73
84
 
85
+ cache_key = (odd_pAda_weights, even_pAda_weights)
86
+ if cache_key in self._anuzwuB_half_cache:
87
+ return self._anuzwuB_half_cache[cache_key]
88
+
89
+ # check lengths first; length_error only reported when exactly one pāda is off
90
+ even_len_ok = len(even_pAda_weights) == 8
91
+ odd_len_ok = len(odd_pAda_weights) == 8
92
+ if not even_len_ok and not odd_len_ok:
93
+ result = None # both wrong: bad split, not credible
94
+ elif not even_len_ok:
95
+ code = 'hypermetric' if len(even_pAda_weights) > 8 else 'hypometric'
96
+ result = Diagnostic(failure_code=code, problem_syllables={'odd': [], 'even': list(range(len(even_pAda_weights)))})
97
+ elif not odd_len_ok:
98
+ code = 'hypermetric' if len(odd_pAda_weights) > 8 else 'hypometric'
99
+ result = Diagnostic(failure_code=code, problem_syllables={'odd': list(range(len(odd_pAda_weights))), 'even': []})
74
100
  else:
75
- return None
101
+ # check even pāda
102
+ if not re.match(meter_patterns.anuzwuB_pAda['even'], even_pAda_weights):
103
+ result = None
104
+ for weights_pattern, (label, problem_syls, code) in meter_patterns.anuzwuB_pAda_asamIcIna['even'].items():
105
+ if re.match(weights_pattern, even_pAda_weights):
106
+ result = Diagnostic(imperfect_id_label=label, failure_code=code, problem_syllables={'odd': [], 'even': problem_syls})
107
+ break
108
+ if result is None:
109
+ result = Diagnostic(imperfect_id_label='asamīcīnā, [caturthāt] pathyā yujo j', failure_code='hahn_general_4', problem_syllables={'odd': [], 'even': [4, 5, 6]})
110
+ else:
111
+ # check odd pāda (both 'paTyA' and 'vipulA')
112
+ result = None
113
+ for weights_pattern, label in meter_patterns.anuzwuB_pAda['odd'].items():
114
+ if re.match(weights_pattern, odd_pAda_weights):
115
+ result = Diagnostic(perfect_id_label=label)
116
+ break
117
+ if result is None:
118
+ # check for broken conditioning on odd pāda
119
+ for weights_pattern, (label, problem_syls, code) in meter_patterns.anuzwuB_pAda_asamIcIna['odd'].items():
120
+ if re.match(weights_pattern, odd_pAda_weights):
121
+ result = Diagnostic(imperfect_id_label=label, failure_code=code, problem_syllables={'odd': problem_syls, 'even': []})
122
+ break
123
+ if result is None:
124
+ result = Diagnostic(imperfect_id_label='asamīcīnā, [vipulāyām asatyām] ya[gaṇaḥ] [ayujo] caturthāt [syāt]', failure_code='hahn_paTyA', problem_syllables={'odd': [4, 5, 6], 'even': []})
125
+
126
+ self._anuzwuB_half_cache[cache_key] = result
127
+ return result
76
128
 
77
129
  def test_as_anuzwuB(self, Vrs):
78
130
  # >> def test_as_zloka(self, Vrs):
@@ -81,52 +133,99 @@ class VerseTester(object):
81
133
  Determines whether first four lines of Verse's syllable_weights is anuṣṭubh.
82
134
  Internally sets Verse parameters if identified as such.
83
135
  Tests halves ab and cd independently, reports if either half found to be valid.
84
- Returns 1 if anuṣṭubh, or 0 if not.
136
+ Returns Diagnostic if anuṣṭubh, or None if not.
85
137
  """
86
138
 
87
139
  w_p = Vrs.syllable_weights.split('\n') # weights by pāda
88
140
 
89
141
  # make sure full four pādas
90
142
  try: w_p[3]
91
- except IndexError: return 0
143
+ except IndexError: return None
92
144
 
93
145
  # test each half
94
- pAdas_ab = self.test_as_anuzwuB_half(w_p[0], w_p[1])
95
- pAdas_cd = self.test_as_anuzwuB_half(w_p[2], w_p[3])
146
+ pAdas_ab_result = self.test_as_anuzwuB_half(w_p[0], w_p[1])
147
+ pAdas_cd_result = self.test_as_anuzwuB_half(w_p[2], w_p[3])
148
+
149
+ if pAdas_ab_result is None and pAdas_cd_result is None:
150
+ ardham_eva_result = self.test_as_anuzwuB_half(w_p[0] + w_p[1], w_p[2] + w_p[3])
151
+ if ardham_eva_result is None:
152
+ return None
153
+ if ardham_eva_result.perfect():
154
+ Vrs.meter_label = f"anuṣṭubh (ardham eva: {ardham_eva_result.perfect_id_label})"
155
+ Vrs.identification_score = meter_scores["anuṣṭubh, half, single half perfect)"]
156
+ Vrs.diagnostic = ardham_eva_result
157
+ return ardham_eva_result
158
+ elif ardham_eva_result.imperfect():
159
+ Vrs.meter_label = f"anuṣṭubh (ardham eva: {ardham_eva_result.imperfect_id_label})"
160
+ Vrs.identification_score = meter_scores["anuṣṭubh, half, single half imperfect)"]
161
+ Vrs.diagnostic = ardham_eva_result
162
+ return ardham_eva_result
163
+ else:
164
+ return None
96
165
 
97
- # report results
166
+ if pAdas_ab_result is None or pAdas_cd_result is None:
167
+ return None
98
168
 
99
169
  # both halves perfect
100
170
 
101
- if pAdas_ab != None and pAdas_cd != None:
102
- Vrs.meter_label = "anuṣṭubh (1,2: " + pAdas_ab + ", 3,4: " + pAdas_cd + ")"
171
+ if pAdas_ab_result.perfect() and pAdas_cd_result.perfect():
172
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.perfect_id_label}; 3,4: {pAdas_cd_result.perfect_id_label})"
103
173
  Vrs.identification_score = meter_scores["anuṣṭubh, full, both halves perfect)"]
104
- return 1
174
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
175
+ return pAdas_ab_result
105
176
 
106
177
  # one half imperfect
107
178
 
108
- elif pAdas_ab == None and pAdas_cd != None:
109
- Vrs.meter_label = "anuṣṭubh (1,2: asamīcīna, 3,4: " + pAdas_cd + ")"
179
+ elif pAdas_ab_result.imperfect() and pAdas_cd_result.perfect():
180
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.imperfect_id_label}; 3,4: {pAdas_cd_result.perfect_id_label})"
110
181
  Vrs.identification_score = meter_scores["anuṣṭubh, full, one half perfect, one imperfect)"]
111
- return 1
112
- elif pAdas_ab != None and pAdas_cd == None:
113
- Vrs.meter_label = "anuṣṭubh (1,2: " + pAdas_ab + ", 3,4: asamīcīna)"
182
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
183
+ return pAdas_ab_result
184
+ elif pAdas_ab_result.perfect() and pAdas_cd_result.imperfect():
185
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.perfect_id_label}; 3,4: {pAdas_cd_result.imperfect_id_label})"
114
186
  Vrs.identification_score = meter_scores["anuṣṭubh, full, one half perfect, one imperfect)"]
115
- return 1
116
-
117
- # currently cannot do both halves imperfect
118
-
119
- # also test whether just a single perfect half
120
-
121
- pAdas_ab = self.test_as_anuzwuB_half(w_p[0]+w_p[1], w_p[2]+w_p[3])
122
- if pAdas_ab != None:
123
- Vrs.meter_label = "anuṣṭubh (ardham eva: " + pAdas_ab + ")"
124
- Vrs.identification_score = meter_scores["anuṣṭubh, half, single half perfect)"]
125
- return 1
126
-
127
- # currently cannot do just a single imperfect half
128
-
129
- return 0
187
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
188
+ return pAdas_cd_result
189
+
190
+ # both halves imperfect
191
+
192
+ elif pAdas_ab_result.imperfect() and pAdas_cd_result.imperfect():
193
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.imperfect_id_label}; 3,4: {pAdas_cd_result.imperfect_id_label})"
194
+ Vrs.identification_score = meter_scores["anuṣṭubh, full, both halves imperfect)"]
195
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
196
+ return pAdas_ab_result
197
+
198
+ # one half perfect, one length error
199
+
200
+ elif pAdas_ab_result.length_error() and pAdas_cd_result.perfect():
201
+ code = pAdas_ab_result.failure_code
202
+ Vrs.meter_label = f"anuṣṭubh (1,2: ?? {code}; 3,4: {pAdas_cd_result.perfect_id_label})"
203
+ Vrs.identification_score = meter_scores["anuṣṭubh, full, one half perfect, one length error)"]
204
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
205
+ return pAdas_cd_result
206
+ elif pAdas_ab_result.perfect() and pAdas_cd_result.length_error():
207
+ code = pAdas_cd_result.failure_code
208
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.perfect_id_label}; 3,4: ?? {code})"
209
+ Vrs.identification_score = meter_scores["anuṣṭubh, full, one half perfect, one length error)"]
210
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
211
+ return pAdas_ab_result
212
+
213
+ # one half imperfect, one length error
214
+
215
+ elif pAdas_ab_result.length_error() and pAdas_cd_result.imperfect():
216
+ code = pAdas_ab_result.failure_code
217
+ Vrs.meter_label = f"anuṣṭubh (1,2: ?? {code}; 3,4: {pAdas_cd_result.imperfect_id_label})"
218
+ Vrs.identification_score = meter_scores["anuṣṭubh, full, one half imperfect, one length error)"]
219
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
220
+ return pAdas_cd_result
221
+ elif pAdas_ab_result.imperfect() and pAdas_cd_result.length_error():
222
+ code = pAdas_cd_result.failure_code
223
+ Vrs.meter_label = f"anuṣṭubh (1,2: {pAdas_ab_result.imperfect_id_label}; 3,4: ?? {code})"
224
+ Vrs.identification_score = meter_scores["anuṣṭubh, full, one half imperfect, one length error)"]
225
+ Vrs.diagnostic = {'ab': pAdas_ab_result, 'cd': pAdas_cd_result}
226
+ return pAdas_ab_result
227
+
228
+ return None
130
229
 
131
230
  def count_pAdasamatva(self, Vrs):
132
231
  """
@@ -540,8 +639,8 @@ class VerseTester(object):
540
639
 
541
640
  # anuzwuB
542
641
 
543
- success_anuzwuB = self.test_as_anuzwuB(Vrs) # 1 if successful, 0 if not
544
- if success_anuzwuB and Vrs.identification_score == meter_scores["max score"]:
642
+ anuzwuB_diagnostic = self.test_as_anuzwuB(Vrs) # Diagnostic if successful, None if not
643
+ if anuzwuB_diagnostic and Vrs.identification_score == meter_scores["max score"]:
545
644
  return 1
546
645
 
547
646
  # samavftta, upajAti, vizamavftta, ardhasamavftta
@@ -557,7 +656,7 @@ class VerseTester(object):
557
656
 
558
657
  success_jAti = self.test_as_jAti(Vrs)
559
658
 
560
- if success_anuzwuB or success_samavftta_etc or success_jAti:
659
+ if anuzwuB_diagnostic or success_samavftta_etc or success_jAti:
561
660
  return 1
562
661
  else:
563
662
  return 0
@@ -613,6 +712,7 @@ class MeterIdentifier(object):
613
712
  pAda_brs, quarter_len):
614
713
  """Returns a list for MeterIdentifier.Verses_found"""
615
714
 
715
+ self._anuzwuB_half_cache = {}
616
716
  pos_iterators = {}
617
717
  for k in ['ab', 'bc', 'cd']:
618
718
  if (
@@ -47,6 +47,20 @@ anuzwuB_pAda = {
47
47
  }
48
48
  }
49
49
 
50
+ anuzwuB_pAda_asamIcIna = {
51
+ 'odd' : {
52
+ '^.ll.{5}$' : ('asamīcīnā, na prathamāt snau', [1, 2], 'hahn_general_2'), # syllables 2–3 both light
53
+ '^.{4}ggg.$' : ('asamīcīnā, ma-vipulāyāḥ paścād raḥ syāt', [1, 2, 3], 'hahn_vipulA_3'), # ma-vipulā conditioning violated
54
+ '^.{4}gll.$' : ('asamīcīnā, bha-vipulāyāḥ paścād raḥ syāt', [1, 2, 3], 'hahn_vipulA_2'), # bha-vipulā conditioning violated
55
+ '^.{3}llll.$' : ('asamīcīnā, na-vipulāyāḥ paścād guruḥ syāt', [3], 'hahn_vipulA_1'), # na-vipulā conditioning violated
56
+ '^.{3}lglg.$' : ('asamīcīnā, ra-vipulāyāḥ paścād guruḥ syāt', [3], 'hahn_vipulA_4'), # ra-vipulā conditioning violated
57
+ },
58
+ 'even' : {
59
+ '^.ll.{5}$' : ('asamīcīnā, na prathamāt snau', [1, 2], 'hahn_general_2'), # syllables 2–3 both light
60
+ '^.glg.{4}$' : ('asamīcīnā, [na] dvitīyacaturthayo raḥ', [1, 2, 3], 'hahn_general_3'), # ra-gaṇa at syllables 2–4
61
+ },
62
+ }
63
+
50
64
  """
51
65
  samavṛtta
52
66
 
@@ -34,6 +34,7 @@ class Verse(object):
34
34
  self.gaRa_abbreviations = None # string, may contain newlines
35
35
  self.meter_label = None # string
36
36
  self.identification_score = 0 # int
37
+ self.diagnostic = None # Diagnostic or dict of Diagnostics, set by meter_identification
37
38
 
38
39
  def summarize(self,
39
40
  show_weights=True, show_morae=True, show_gaRas=True, # part_A
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: skrutable
3
- Version: 2.1.3
3
+ Version: 2.2.0
4
4
  Summary: skrutable library for working with Sanskrit text
5
5
  Home-page: https://github.com/tylergneill/skrutable
6
6
  Author: Tyler Neill
@@ -1 +0,0 @@
1
- __version__ = "2.1.3"
File without changes
File without changes
File without changes
File without changes