skrutable 2.1.3__tar.gz → 2.3.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.3.0}/PKG-INFO +1 -1
  2. skrutable-2.3.0/src/skrutable/__init__.py +1 -0
  3. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/config.json +5 -1
  4. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/meter_identification.py +228 -51
  5. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/meter_patterns.py +14 -0
  6. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/scansion.py +1 -0
  7. {skrutable-2.1.3 → skrutable-2.3.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.3.0}/LICENSE.md +0 -0
  10. {skrutable-2.1.3 → skrutable-2.3.0}/README.md +0 -0
  11. {skrutable-2.1.3 → skrutable-2.3.0}/setup.cfg +0 -0
  12. {skrutable-2.1.3 → skrutable-2.3.0}/setup.py +0 -0
  13. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/config.py +0 -0
  14. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/generate_scheme_vectors.py +0 -0
  15. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/impossible_bigrams.json +0 -0
  16. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/manual.md +0 -0
  17. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/phonemes.py +0 -0
  18. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/run_examples.py +0 -0
  19. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/scheme_detection.py +0 -0
  20. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/scheme_maps.py +0 -0
  21. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/scheme_vectors.json +0 -0
  22. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/scheme_vectors_mbh.py +0 -0
  23. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/splitting.py +0 -0
  24. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/transliteration.py +0 -0
  25. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable/virAma_avoidance.py +0 -0
  26. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable.egg-info/SOURCES.txt +0 -0
  27. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable.egg-info/dependency_links.txt +0 -0
  28. {skrutable-2.1.3 → skrutable-2.3.0}/src/skrutable.egg-info/requires.txt +0 -0
  29. {skrutable-2.1.3 → skrutable-2.3.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.3.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.3.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
  """
@@ -188,23 +287,61 @@ class VerseTester(object):
188
287
  meter_label += ' [%d: %s]' % ( len(w_to_id), g_to_id )
189
288
 
190
289
  score = meter_scores["samavṛtta, perfect"]
290
+ imperfect_note = None
191
291
 
192
292
  if self.pAdasamatva_count == 3:
193
- meter_label += " (? 3 eva pādāḥ yuktāḥ)"
293
+ imperfect_note = "? 3 eva pādāḥ yuktāḥ"
294
+ meter_label += " (%s)" % imperfect_note
194
295
  score = meter_scores["samavṛtta, imperfect (3)"]
195
296
  elif self.pAdasamatva_count == 2:
196
- meter_label += " (? 2 eva pādāḥ yuktāḥ)"
297
+ imperfect_note = "? 2 eva pādāḥ yuktāḥ"
298
+ meter_label += " (%s)" % imperfect_note
197
299
  score = meter_scores["samavṛtta, imperfect (2)"]
198
300
  elif self.pAdasamatva_count == 0:
199
- meter_label += " (1 eva pādaḥ)"
301
+ imperfect_note = "1 eva pādaḥ"
302
+ meter_label += " (%s)" % imperfect_note
200
303
  score = meter_scores["samavṛtta, quarter, perfect"]
201
304
 
202
305
  # experimental penalty, can later incorporate into config meter_scores
203
- if meter_label == "ajñātasamavṛtta":
306
+ if "ajñātasamavṛtta" in meter_label:
204
307
  score -= 2
205
308
 
309
+ # build diagnostic
310
+ problem_syllables = {}
311
+ canonical = w_to_id # includes final anceps
312
+ for pada_num, w in enumerate(wbp[:4], start=1):
313
+ if w == canonical:
314
+ problem_syllables[pada_num] = []
315
+ elif len(w) != len(canonical):
316
+ problem_syllables[pada_num] = list(range(len(w)))
317
+ else:
318
+ # compare position-by-position; final anceps always matches
319
+ problem_syllables[pada_num] = [
320
+ j for j in range(len(w) - 1) if w[j] != canonical[j]
321
+ ]
322
+
323
+ # collect any hyper/hypometric notes for imperfect_id_label
324
+ length_notes = []
325
+ for pada_num, w in enumerate(wbp[:4], start=1):
326
+ if len(w) > len(canonical):
327
+ length_notes.append("pāda %d hypermetric" % pada_num)
328
+ elif len(w) < len(canonical):
329
+ length_notes.append("pāda %d hypometric" % pada_num)
330
+
331
+ if imperfect_note is None:
332
+ base_label = meter_label
333
+ diagnostic = Diagnostic(perfect_id_label=base_label, problem_syllables=problem_syllables)
334
+ else:
335
+ full_imperfect = imperfect_note
336
+ if length_notes:
337
+ full_imperfect += "; " + "; ".join(length_notes)
338
+ diagnostic = Diagnostic(imperfect_id_label=full_imperfect, problem_syllables=problem_syllables)
339
+
206
340
  # may tie with pre-existing result (e.g., upajāti)
341
+ old_score = Vrs.identification_score
207
342
  self.combine_results(Vrs, new_label=meter_label, new_score=score)
343
+ if score >= old_score:
344
+ Vrs.diagnostic = diagnostic
208
345
 
209
346
 
210
347
 
@@ -241,19 +378,25 @@ class VerseTester(object):
241
378
  Vrs.identification_score = meter_scores["ardhasamavṛtta, perfect, unknown"]
242
379
 
243
380
  Vrs.meter_label = meter_label
381
+ Vrs.diagnostic = Diagnostic(perfect_id_label=meter_label, problem_syllables={1: [], 2: [], 3: [], 4: []})
244
382
 
245
383
 
246
384
  def evaluate_upajAti(self, Vrs):
247
385
  # sufficient length similarity already assured, now just evaluate
248
386
 
249
387
  wbp = Vrs.syllable_weights.split('\n') # weights by pāda
250
- wbp_lens = [ len(line) for line in wbp ]
388
+ wbp_lens_orig = [ len(line) for line in wbp ]
389
+ wbp_lens = list(wbp_lens_orig)
251
390
  gs_to_id = Vrs.gaRa_abbreviations.split('\n')
252
391
 
253
392
  # special exception for triṣṭubh-jagatī mix
254
393
  # see Karashima 2016 "The Triṣṭubh-Jagatī Verses in the Saddharmapuṇḍarīka"
255
394
  unique_sorted_lens = list(set(wbp_lens))
256
395
  unique_sorted_lens.sort()
396
+
397
+ # track which original pada indices (0-based) are excluded
398
+ excluded_indices = []
399
+
257
400
  if unique_sorted_lens != [11, 12]:
258
401
 
259
402
  # if imperfect, exclude all info for lines of non-majority lengths
@@ -266,6 +409,7 @@ class VerseTester(object):
266
409
  for i, weights in enumerate(wbp):
267
410
  if len(weights) != most_freq_pAda_len:
268
411
  to_exclude.append(i)
412
+ excluded_indices = list(to_exclude)
269
413
  for i in reversed(to_exclude): # delete in descending index order, avoid index errors
270
414
  del wbp[i]
271
415
  del wbp_lens[i]
@@ -339,7 +483,7 @@ class VerseTester(object):
339
483
  else:
340
484
  score = meter_scores["none found"]
341
485
 
342
-
486
+ imperfect_note = None
343
487
  overall_meter_label = "upajāti %s: %s" % (
344
488
  family,
345
489
  combined_meter_labels
@@ -349,9 +493,39 @@ class VerseTester(object):
349
493
  len(wbp_lens) != 4 and
350
494
  unique_sorted_lens != [11, 12]
351
495
  ): # not perfect and also not triṣṭubh-jagatī-saṃkara
352
- overall_meter_label += " (? %d eva pādāḥ yuktāḥ)" % len(wbp_lens)
496
+ imperfect_note = "? %d eva pādāḥ yuktāḥ" % len(wbp_lens)
497
+ overall_meter_label += " (%s)" % imperfect_note
498
+
499
+ # build diagnostic: problem_syllables keyed 1–4
500
+ # included pādas: no positional errors (upajāti pādas are heterogeneous by design)
501
+ # excluded pādas: hyper/hypometric
502
+ most_freq_len = wbp_lens[0] if wbp_lens else None
503
+ problem_syllables = {}
504
+ length_notes = []
505
+ for pada_num in range(1, 5):
506
+ orig_len = wbp_lens_orig[pada_num - 1] if pada_num - 1 < len(wbp_lens_orig) else None
507
+ if pada_num - 1 in excluded_indices:
508
+ problem_syllables[pada_num] = list(range(orig_len)) if orig_len is not None else []
509
+ if orig_len is not None and most_freq_len is not None:
510
+ if orig_len > most_freq_len:
511
+ length_notes.append("pāda %d hypermetric" % pada_num)
512
+ else:
513
+ length_notes.append("pāda %d hypometric" % pada_num)
514
+ else:
515
+ problem_syllables[pada_num] = []
516
+
517
+ if imperfect_note is None:
518
+ diagnostic = Diagnostic(perfect_id_label=overall_meter_label, problem_syllables=problem_syllables)
519
+ else:
520
+ full_imperfect = imperfect_note
521
+ if length_notes:
522
+ full_imperfect += "; " + "; ".join(length_notes)
523
+ diagnostic = Diagnostic(imperfect_id_label=full_imperfect, problem_syllables=problem_syllables)
353
524
 
525
+ old_score = Vrs.identification_score
354
526
  self.combine_results(Vrs, overall_meter_label, score)
527
+ if score >= old_score:
528
+ Vrs.diagnostic = diagnostic
355
529
 
356
530
 
357
531
  def is_vizamavftta(self, Vrs):
@@ -363,6 +537,7 @@ class VerseTester(object):
363
537
  if (gs_to_id[0],gs_to_id[1],gs_to_id[2],gs_to_id[3]) == (a, b, c, d):
364
538
  Vrs.identification_score = meter_scores["viṣamavṛtta, perfect"]
365
539
  Vrs.meter_label = meter_patterns.vizamavftta_by_4_tuple[(a, b, c, d)]
540
+ Vrs.diagnostic = Diagnostic(perfect_id_label=Vrs.meter_label, problem_syllables={1: [], 2: [], 3: [], 4: []})
366
541
  return True
367
542
 
368
543
  else:
@@ -494,6 +669,7 @@ class VerseTester(object):
494
669
  else: # if all four pAdas proven valid, i.e., if no breaks
495
670
  Vrs.meter_label = jAti_name + " (%s)" % str(std_pattern)[1:-1]
496
671
  Vrs.identification_score = meter_scores["jāti, perfect"]
672
+ Vrs.diagnostic = Diagnostic(perfect_id_label=Vrs.meter_label, problem_syllables={1: [], 2: [], 3: [], 4: []})
497
673
 
498
674
  # should be combining results in case of previous match
499
675
 
@@ -540,8 +716,8 @@ class VerseTester(object):
540
716
 
541
717
  # anuzwuB
542
718
 
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"]:
719
+ anuzwuB_diagnostic = self.test_as_anuzwuB(Vrs) # Diagnostic if successful, None if not
720
+ if anuzwuB_diagnostic and Vrs.identification_score == meter_scores["max score"]:
545
721
  return 1
546
722
 
547
723
  # samavftta, upajAti, vizamavftta, ardhasamavftta
@@ -557,7 +733,7 @@ class VerseTester(object):
557
733
 
558
734
  success_jAti = self.test_as_jAti(Vrs)
559
735
 
560
- if success_anuzwuB or success_samavftta_etc or success_jAti:
736
+ if anuzwuB_diagnostic or success_samavftta_etc or success_jAti:
561
737
  return 1
562
738
  else:
563
739
  return 0
@@ -613,6 +789,7 @@ class MeterIdentifier(object):
613
789
  pAda_brs, quarter_len):
614
790
  """Returns a list for MeterIdentifier.Verses_found"""
615
791
 
792
+ self._anuzwuB_half_cache = {}
616
793
  pos_iterators = {}
617
794
  for k in ['ab', 'bc', 'cd']:
618
795
  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.3.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