1use arcweight::prelude::*;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35#[allow(dead_code)]
36enum PhonologicalFeature {
37 High,
39 Mid,
40 Low,
41 Front,
42 Central,
43 Back,
44 Rounded,
45 Unrounded,
46
47 Voiced,
49 Voiceless,
50 Stop,
51 Fricative,
52 Nasal,
53 Liquid,
54 Labial,
55 Coronal,
56 Dorsal,
57
58 Stressed,
60 Unstressed,
61
62 WordBoundary,
64 SyllableBoundary,
65}
66
67#[derive(Debug, Clone)]
69#[allow(dead_code)] struct PhonologicalSegment {
71 symbol: char,
72 features: Vec<PhonologicalFeature>,
73}
74
75#[derive(Debug, Clone)]
77#[allow(dead_code)] struct PhonologicalRule {
79 name: String,
80 description: String,
81 input_context: Vec<String>,
82 output_context: Vec<String>,
83 environment: Option<String>,
84}
85
86fn build_vowel_harmony_fst() -> VectorFst<TropicalWeight> {
89 let mut fst = VectorFst::new();
90 let start = fst.add_state();
91 let back_state = fst.add_state();
92 let front_state = fst.add_state();
93
94 fst.set_start(start);
95 fst.set_final(start, TropicalWeight::one());
96 fst.set_final(back_state, TropicalWeight::one());
97 fst.set_final(front_state, TropicalWeight::one());
98
99 let back_vowels = ['a', 'o', 'u'];
101 let front_vowels = ['e', 'i'];
102
103 for &vowel in &back_vowels {
104 fst.add_arc(
105 start,
106 Arc::new(
107 vowel as u32,
108 vowel as u32,
109 TropicalWeight::one(),
110 back_state,
111 ),
112 );
113 fst.add_arc(
114 back_state,
115 Arc::new(
116 vowel as u32,
117 vowel as u32,
118 TropicalWeight::one(),
119 back_state,
120 ),
121 );
122 }
123
124 for &vowel in &front_vowels {
125 fst.add_arc(
126 start,
127 Arc::new(
128 vowel as u32,
129 vowel as u32,
130 TropicalWeight::one(),
131 front_state,
132 ),
133 );
134 fst.add_arc(
135 front_state,
136 Arc::new(
137 vowel as u32,
138 vowel as u32,
139 TropicalWeight::one(),
140 front_state,
141 ),
142 );
143 }
144
145 fst.add_arc(
147 back_state,
148 Arc::new('E' as u32, 'a' as u32, TropicalWeight::one(), back_state),
149 );
150
151 fst.add_arc(
152 front_state,
153 Arc::new('E' as u32, 'e' as u32, TropicalWeight::one(), front_state),
154 );
155
156 let consonants = "bcdfghjklmnpqrstvwxyz";
158 for ch in consonants.chars() {
159 fst.add_arc(
160 start,
161 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
162 );
163 fst.add_arc(
164 back_state,
165 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), back_state),
166 );
167 fst.add_arc(
168 front_state,
169 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), front_state),
170 );
171 }
172
173 fst
174}
175
176fn build_cluster_simplification_fst() -> VectorFst<TropicalWeight> {
179 let mut fst = VectorFst::new();
180 let start = fst.add_state();
181 let k_state = fst.add_state();
182
183 fst.set_start(start);
184 fst.set_final(start, TropicalWeight::one());
185
186 fst.add_arc(
188 start,
189 Arc::new(
190 'k' as u32,
191 0, TropicalWeight::one(),
193 k_state,
194 ),
195 );
196
197 fst.add_arc(
198 k_state,
199 Arc::new('t' as u32, 't' as u32, TropicalWeight::one(), start),
200 );
201
202 for ch in b'a'..=b'z' {
204 let ch = ch as char;
205 if ch != 'k' {
206 fst.add_arc(
207 start,
208 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
209 );
210 }
211 }
212
213 for ch in b'a'..=b'z' {
215 let ch = ch as char;
216 if ch != 't' {
217 fst.add_arc(
218 k_state,
219 Arc::new(
220 ch as u32,
221 'k' as u32, TropicalWeight::one(),
223 start,
224 ),
225 );
226 fst.add_arc(
228 start,
229 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
230 );
231 }
232 }
233
234 fst
235}
236
237fn build_epenthesis_fst() -> VectorFst<TropicalWeight> {
240 let mut fst = VectorFst::new();
241 let start = fst.add_state();
242 let consonant_state = fst.add_state();
243
244 fst.set_start(start);
245 fst.set_final(start, TropicalWeight::one());
246 fst.set_final(consonant_state, TropicalWeight::one());
247
248 let vowels = "aeiou";
249 let consonants = "bcdfghjklmnpqrstvwxyz";
250
251 for ch in vowels.chars() {
253 fst.add_arc(
254 start,
255 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
256 );
257 fst.add_arc(
258 consonant_state,
259 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
260 );
261 }
262
263 for ch in consonants.chars() {
265 fst.add_arc(
266 start,
267 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), consonant_state),
268 );
269 }
270
271 for ch in consonants.chars() {
273 fst.add_arc(
274 consonant_state,
275 Arc::new(
276 ch as u32,
277 'i' as u32, TropicalWeight::one(),
279 start,
280 ),
281 );
282 fst.add_arc(
284 start,
285 Arc::new(
286 0, ch as u32,
288 TropicalWeight::one(),
289 consonant_state,
290 ),
291 );
292 }
293
294 fst
295}
296
297fn build_final_devoicing_fst() -> VectorFst<TropicalWeight> {
300 let mut fst = VectorFst::new();
301 let start = fst.add_state();
302 let voiced_state = fst.add_state();
303
304 fst.set_start(start);
305 fst.set_final(start, TropicalWeight::one());
306
307 fst.set_final(voiced_state, TropicalWeight::new(0.0)); let voiced_obstruents = ['b', 'd', 'g', 'z', 'v'];
311 let voiceless_obstruents = ['p', 't', 'k', 's', 'f'];
312
313 for (&voiced, &voiceless) in voiced_obstruents.iter().zip(voiceless_obstruents.iter()) {
315 fst.add_arc(
316 start,
317 Arc::new(
318 voiced as u32,
319 voiceless as u32,
320 TropicalWeight::one(),
321 voiced_state,
322 ),
323 );
324 }
325
326 for ch in b'a'..=b'z' {
328 let ch = ch as char;
329 if !voiced_obstruents.contains(&ch) {
330 fst.add_arc(
331 start,
332 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
333 );
334 }
335 }
336
337 for ch in b'a'..=b'z' {
339 let ch = ch as char;
340 fst.add_arc(
341 voiced_state,
342 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), start),
343 );
344 }
345
346 fst
347}
348
349fn build_word_fst(word: &str) -> VectorFst<TropicalWeight> {
351 let mut fst = VectorFst::new();
352 let mut current = fst.add_state();
353 fst.set_start(current);
354
355 for ch in word.chars() {
356 let next = fst.add_state();
357 fst.add_arc(
358 current,
359 Arc::new(ch as u32, ch as u32, TropicalWeight::one(), next),
360 );
361 current = next;
362 }
363
364 fst.set_final(current, TropicalWeight::one());
365 fst
366}
367
368fn extract_output_string(fst: &VectorFst<TropicalWeight>) -> Option<String> {
370 if let Some(start) = fst.start() {
371 let mut result = String::new();
372 let mut current = start;
373 let mut visited = std::collections::HashSet::new();
374
375 loop {
376 if visited.contains(¤t) {
377 break;
378 }
379 visited.insert(current);
380
381 if fst.is_final(current) {
382 return Some(result);
383 }
384
385 let mut found = false;
386 if let Some(arc) = fst.arcs(current).next() {
387 if arc.olabel != 0 {
388 result.push(arc.olabel as u8 as char);
389 }
390 current = arc.nextstate;
391 found = true;
392 }
393
394 if !found {
395 break;
396 }
397 }
398 }
399
400 None
401}
402
403fn apply_phonological_rules(input: &str, rules: Vec<VectorFst<TropicalWeight>>) -> Result<String> {
405 let mut current_fst = build_word_fst(input);
406
407 println!("Applying phonological rules in sequence:");
408 println!("Input: '{input}'");
409
410 for (i, rule) in rules.iter().enumerate() {
411 let step = i + 1;
412 println!("\nStep {step}: Applying rule {step}");
413
414 current_fst = compose_default(¤t_fst, rule)?;
416
417 if let Some(intermediate) = extract_output_string(¤t_fst) {
419 println!(" Result: '{intermediate}'");
420 } else {
421 println!(" Result: (no output)");
422 }
423 }
424
425 if let Some(output) = extract_output_string(¤t_fst) {
427 Ok(output)
428 } else {
429 Ok("(no output)".to_string())
430 }
431}
432
433fn main() -> Result<()> {
434 println!("Phonological Rules with FSTs");
435 println!("============================");
436 println!("Modeling phonological processes using Finite State Transducers");
437 println!("Based on Kaplan & Kay (1994) and subsequent work\n");
438
439 println!("1. Turkish-style Vowel Harmony");
441 println!("------------------------------");
442 println!("Rule: Suffix vowel 'E' harmonizes with stem vowels");
443 println!(" Front vowels (e, i) → E becomes 'e'");
444 println!(" Back vowels (a, o, u) → E becomes 'a'");
445
446 let harmony_fst = build_vowel_harmony_fst();
447 let harmony_tests = vec!["kitabE", "evE", "adamE", "gelE"];
448
449 for test in harmony_tests {
450 let input_fst = build_word_fst(test);
451 let composed: VectorFst<TropicalWeight> = compose_default(&input_fst, &harmony_fst)?;
452 if let Some(output) = extract_output_string(&composed) {
453 println!(" '{test}' → '{output}'");
454 }
455 }
456
457 println!("\n2. Consonant Cluster Simplification");
459 println!("-----------------------------------");
460 println!("Rule: /kt/ → /t/ (cluster reduction)");
461
462 let cluster_fst = build_cluster_simplification_fst();
463 let cluster_tests = vec!["akt", "ekte", "doktor", "katok"];
464
465 for test in cluster_tests {
466 let input_fst = build_word_fst(test);
467 let composed: VectorFst<TropicalWeight> = compose_default(&input_fst, &cluster_fst)?;
468 if let Some(output) = extract_output_string(&composed) {
469 println!(" '{test}' → '{output}'");
470 }
471 }
472
473 println!("\n3. Vowel Epenthesis");
475 println!("------------------");
476 println!("Rule: Insert 'i' between consonant clusters");
477
478 let epenthesis_fst = build_epenthesis_fst();
479 let epenthesis_tests = vec!["sport", "program", "strong"];
480
481 for test in epenthesis_tests {
482 let input_fst = build_word_fst(test);
483 let composed: VectorFst<TropicalWeight> = compose_default(&input_fst, &epenthesis_fst)?;
484 if let Some(output) = extract_output_string(&composed) {
485 println!(" '{test}' → '{output}'");
486 }
487 }
488
489 println!("\n4. Final Devoicing (German-style)");
491 println!("---------------------------------");
492 println!("Rule: Voiced obstruents become voiceless word-finally");
493
494 let devoicing_fst = build_final_devoicing_fst();
495 let devoicing_tests = vec!["hund", "tag", "lieb", "haus"];
496
497 for test in devoicing_tests {
498 let input_fst = build_word_fst(test);
499 let composed: VectorFst<TropicalWeight> = compose_default(&input_fst, &devoicing_fst)?;
500 if let Some(output) = extract_output_string(&composed) {
501 println!(" '{test}' → '{output}'");
502 }
503 }
504
505 println!("\n5. Rule Interaction and Ordering");
507 println!("--------------------------------");
508 println!("Demonstrating how rule order affects output");
509
510 let test_word = "aktE";
511 println!("Input: '{test_word}'");
512
513 println!("\nOrder 1: Vowel Harmony → Cluster Simplification");
515 let rules1 = vec![harmony_fst.clone(), cluster_fst.clone()];
516 let result1 = apply_phonological_rules(test_word, rules1)?;
517 println!("Final result: '{result1}'");
518
519 println!("\nOrder 2: Cluster Simplification → Vowel Harmony");
521 let rules2 = vec![cluster_fst.clone(), harmony_fst.clone()];
522 let result2 = apply_phonological_rules(test_word, rules2)?;
523 println!("Final result: '{result2}'");
524
525 println!("\n6. Complex Multi-Rule System");
527 println!("----------------------------");
528 println!("Applying multiple rules in sequence");
529
530 let complex_word = "sportE";
531 println!("Input: '{complex_word}'");
532
533 let all_rules = vec![
534 epenthesis_fst, harmony_fst, devoicing_fst, ];
538
539 let final_result = apply_phonological_rules(complex_word, all_rules)?;
540 println!("Final result: '{final_result}'");
541
542 println!("\n7. Theoretical Background and Implications");
544 println!("-----------------------------------------");
545 println!("This example demonstrates key insights from computational phonology:");
546 println!(" • Phonological rules as regular relations (Kaplan & Kay, 1994)");
547 println!(" • Rule application through FST composition");
548 println!(" • Natural emergence of rule ordering effects from composition order");
549 println!(" • Modeling of opacity, transparency, and bleeding/feeding interactions");
550 println!(" • Bidirectional processing: generation ↔ recognition");
551 println!(" • Connection to two-level morphology (Koskenniemi, 1983)");
552
553 println!("\nHistorical development:");
554 println!(" • Johnson (1972): Early formal approaches to phonological rules");
555 println!(" • Koskenniemi (1983): Two-level morphology with FSTs");
556 println!(" • Kaplan & Kay (1994): Regular models of phonological rule systems");
557 println!(" • Modern applications: Finite-state phonology in NLP systems");
558
559 println!("\nApplications in computational linguistics:");
560 println!(" • Morphophonological analysis and generation");
561 println!(" • Text-to-speech synthesis systems");
562 println!(" • Automatic speech recognition");
563 println!(" • Historical linguistics and sound change modeling");
564 println!(" • Language documentation and endangered language preservation");
565 println!(" • Cross-linguistic phonological typology studies");
566 println!(" • Psycholinguistic modeling of phonological processing");
567
568 Ok(())
569}