-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformula_vocabulary_builder.py
More file actions
360 lines (300 loc) · 13.6 KB
/
Copy pathformula_vocabulary_builder.py
File metadata and controls
360 lines (300 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
"""
Homeric Formula Vocabulary Builder
Automatically extracts repeated patterns around character names + presents them for review and categorisation
"""
import json
from collections import Counter, defaultdict
import re
def load_analysis_results(filepath='homer_analysis.json'):
"""Load results from phase 1"""
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
def extract_ngrams(text, n, position='all'):
"""
Extract n-grams from text
position: 'before', 'after', or 'all'
"""
words = text.split()
ngrams = []
for i in range(len(words) - n + 1):
ngram = ' '.join(words[i:i+n])
ngrams.append(ngram)
return ngrams
def find_patterns_around_character(mentions, character_name, window_before=3, window_after=3):
"""
For a specific character, find all repeated patterns before and after their name
"""
patterns_before = []
patterns_after = []
for mention in mentions:
if mention['character'] != character_name:
continue
line = mention['line'].lower()
form = mention['form'].lower()
# Find position of character name in line
words = line.split()
char_index = None
for i, word in enumerate(words):
if form in word:
char_index = i
break
if char_index is None:
continue
# Extract words before
start_before = max(0, char_index - window_before)
before_words = words[start_before:char_index]
if before_words:
patterns_before.append(' '.join(before_words))
# Extract words after
end_after = min(len(words), char_index + window_after + 1)
after_words = words[char_index+1:end_after]
if after_words:
patterns_after.append(' '.join(after_words))
return patterns_before, patterns_after
def extract_repeated_bigrams_trigrams(patterns, min_frequency=3):
"""
Find bigrams and trigrams that appear frequently
"""
all_bigrams = []
all_trigrams = []
all_fourgrams = []
for pattern in patterns:
words = pattern.split()
# Bigrams
for i in range(len(words) - 1):
all_bigrams.append(f"{words[i]} {words[i+1]}")
# Trigrams
for i in range(len(words) - 2):
all_trigrams.append(f"{words[i]} {words[i+1]} {words[i+2]}")
# 4-grams
for i in range(len(words) - 3):
all_fourgrams.append(f"{words[i]} {words[i+1]} {words[i+2]} {words[i+3]}")
bigram_freq = Counter(all_bigrams)
trigram_freq = Counter(all_trigrams)
fourgram_freq = Counter(all_fourgrams)
# Filter by minimum frequency
frequent_bigrams = {k: v for k, v in bigram_freq.items() if v >= min_frequency}
frequent_trigrams = {k: v for k, v in trigram_freq.items() if v >= min_frequency}
frequent_fourgrams = {k: v for k, v in fourgram_freq.items() if v >= min_frequency}
return frequent_bigrams, frequent_trigrams, frequent_fourgrams
def find_formulaic_phrases_per_character(mentions):
"""
For each character, extract their most common formulaic phrases
"""
characters = set(m['character'] for m in mentions)
character_formulae = {}
for character in characters:
print(f"\nAnalyzing formulae for {character}...")
patterns_before, patterns_after = find_patterns_around_character(
mentions, character, window_before=4, window_after=4
)
print(f" Found {len(patterns_before)} 'before' patterns")
print(f" Found {len(patterns_after)} 'after' patterns")
# Extract n-grams
before_bi, before_tri, before_four = extract_repeated_bigrams_trigrams(patterns_before, min_frequency=3)
after_bi, after_tri, after_four = extract_repeated_bigrams_trigrams(patterns_after, min_frequency=3)
character_formulae[character] = {
'patterns_before': {
'bigrams': before_bi,
'trigrams': before_tri,
'fourgrams': before_four
},
'patterns_after': {
'bigrams': after_bi,
'trigrams': after_tri,
'fourgrams': after_four
},
'total_mentions': len([m for m in mentions if m['character'] == character])
}
return character_formulae
def generate_formula_report(character_formulae, output_file='formula_report.txt'):
"""
Generate a human-readable report of discovered formulae
"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write("="*80 + "\n")
f.write("HOMERIC FORMULAIC PATTERNS - AUTOMATIC EXTRACTION REPORT\n")
f.write("="*80 + "\n\n")
for character in sorted(character_formulae.keys()):
data = character_formulae[character]
f.write(f"\n{'='*80}\n")
f.write(f"{character.upper()} - {data['total_mentions']} total mentions\n")
f.write(f"{'='*80}\n")
# Patterns BEFORE the name
f.write("\n--- PATTERNS BEFORE NAME ---\n\n")
if data['patterns_before']['fourgrams']:
f.write("4-word phrases (most formulaic):\n")
for phrase, count in sorted(data['patterns_before']['fourgrams'].items(),
key=lambda x: x[1], reverse=True):
f.write(f" {count:3d}x {phrase}\n")
if data['patterns_before']['trigrams']:
f.write("\n3-word phrases:\n")
for phrase, count in sorted(data['patterns_before']['trigrams'].items(),
key=lambda x: x[1], reverse=True)[:15]:
f.write(f" {count:3d}x {phrase}\n")
if data['patterns_before']['bigrams']:
f.write("\n2-word phrases:\n")
for phrase, count in sorted(data['patterns_before']['bigrams'].items(),
key=lambda x: x[1], reverse=True)[:15]:
f.write(f" {count:3d}x {phrase}\n")
# Patterns AFTER the name
f.write("\n--- PATTERNS AFTER NAME ---\n\n")
if data['patterns_after']['fourgrams']:
f.write("4-word phrases (most formulaic):\n")
for phrase, count in sorted(data['patterns_after']['fourgrams'].items(),
key=lambda x: x[1], reverse=True):
f.write(f" {count:3d}x {phrase}\n")
if data['patterns_after']['trigrams']:
f.write("\n3-word phrases:\n")
for phrase, count in sorted(data['patterns_after']['trigrams'].items(),
key=lambda x: x[1], reverse=True)[:15]:
f.write(f" {count:3d}x {phrase}\n")
if data['patterns_after']['bigrams']:
f.write("\n2-word phrases:\n")
for phrase, count in sorted(data['patterns_after']['bigrams'].items(),
key=lambda x: x[1], reverse=True)[:15]:
f.write(f" {count:3d}x {phrase}\n")
f.write("\n")
print(f"\nReport saved to {output_file}")
def generate_interactive_review(character_formulae, mentions, output_file='formula_review_template.json'):
"""
Generate a JSON template for manual review and categorization
Format allows you to mark each formula as:
- epithet_type: 'single_word', 'phrase', 'half_line'
- semantic_category: 'physical', 'divine', 'martial', 'relational', etc.
- confirmed: true/false
"""
review_template = {}
for character in sorted(character_formulae.keys()):
data = character_formulae[character]
# Collect all frequent patterns
all_patterns = []
# From before patterns
for phrase, count in data['patterns_before']['fourgrams'].items():
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'before',
'length': 4
})
for phrase, count in data['patterns_before']['trigrams'].items():
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'before',
'length': 3
})
for phrase, count in data['patterns_before']['bigrams'].items():
if count >= 5: # Only include very frequent bigrams
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'before',
'length': 2
})
# From after patterns
for phrase, count in data['patterns_after']['fourgrams'].items():
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'after',
'length': 4
})
for phrase, count in data['patterns_after']['trigrams'].items():
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'after',
'length': 3
})
for phrase, count in data['patterns_after']['bigrams'].items():
if count >= 5:
all_patterns.append({
'pattern': phrase,
'frequency': count,
'position': 'after',
'length': 2
})
# Sort by frequency
all_patterns.sort(key=lambda x: x['frequency'], reverse=True)
# Create review entries
review_entries = []
for pattern_data in all_patterns:
review_entries.append({
'pattern': pattern_data['pattern'],
'frequency': pattern_data['frequency'],
'position': pattern_data['position'],
'length': pattern_data['length'],
# Fields for manual review:
'confirmed': None, # true/false - is this a real formula?
'formula_type': None, # 'epithet', 'verb_phrase', 'half_line', 'noise'
'semantic_category': None, # 'physical', 'divine', 'martial', 'speech', etc.
'notes': ''
})
review_template[character] = {
'total_mentions': data['total_mentions'],
'patterns': review_entries
}
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(review_template, f, ensure_ascii=False, indent=2)
print(f"Review template saved to {output_file}")
print("\nYou can now:")
print("1. Open this JSON file")
print("2. Review each pattern and fill in the fields:")
print(" - confirmed: true/false")
print(" - formula_type: 'epithet', 'verb_phrase', 'half_line', 'noise'")
print(" - semantic_category: describe the meaning")
print("3. Save and we'll use this as our formula database")
def find_example_contexts(mentions, character, pattern, max_examples=5):
"""
Find actual line examples where a pattern appears with a character
"""
examples = []
pattern_lower = pattern.lower()
for mention in mentions:
if mention['character'] == character:
line_lower = mention['line'].lower()
if pattern_lower in line_lower:
examples.append({
'line_num': mention['line_num'],
'line': mention['line'],
'context': mention.get('context', mention['line'])
})
if len(examples) >= max_examples:
break
return examples
if __name__ == "__main__":
print("Homeric Formula Vocabulary Builder")
print("="*80)
print("\nThis script will:")
print("1. Automatically extract repeated patterns around character names")
print("2. Generate a readable report of formulaic phrases")
print("3. Create a review template for you to categorize the formulae")
print("="*80 + "\n")
# Load previous analysis
try:
results = load_analysis_results('homer_analysis.json')
mentions = results['all_mentions']
print(f"Loaded {len(mentions)} character mentions from previous analysis\n")
except FileNotFoundError:
print("Error: homer_analysis.json not found.")
print("Please run homeric_formula_analyser.py first!")
exit(1)
# Extract formulaic patterns
print("Extracting formulaic patterns...")
character_formulae = find_formulaic_phrases_per_character(mentions)
# Generate outputs
print("\nGenerating reports...")
generate_formula_report(character_formulae, 'formula_report.txt')
generate_interactive_review(character_formulae, mentions, 'formula_review_template.json')
print("\n" + "="*80)
print("NEXT STEPS:")
print("="*80)
print("\n1. Read 'formula_report.txt' to see all discovered patterns")
print("2. Open 'formula_review_template.json'")
print("3. Review each pattern and mark:")
print(" - confirmed: true (it's a real formula) or false (it's noise)")
print(" - formula_type: 'epithet', 'verb_phrase', 'half_line', etc.")
print(" - semantic_category: what does it mean?")
print("\n4. Once reviewed, we'll build Phase 2 using your confirmed formulae!")
print("\nThis will give us a comprehensive, accurate formula database.")