aline.py 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. # -*- coding: utf-8 -*-
  2. # Natural Language Toolkit: ALINE
  3. #
  4. # Copyright (C) 2001-2019 NLTK Project
  5. # Author: Greg Kondrak <gkondrak@ualberta.ca>
  6. # Geoff Bacon <bacon@berkeley.edu> (Python port)
  7. # URL: <http://nltk.org/>
  8. # For license information, see LICENSE.TXT
  9. """
  10. ALINE
  11. http://webdocs.cs.ualberta.ca/~kondrak/
  12. Copyright 2002 by Grzegorz Kondrak.
  13. ALINE is an algorithm for aligning phonetic sequences, described in [1].
  14. This module is a port of Kondrak's (2002) ALINE. It provides functions for
  15. phonetic sequence alignment and similarity analysis. These are useful in
  16. historical linguistics, sociolinguistics and synchronic phonology.
  17. ALINE has parameters that can be tuned for desired output. These parameters are:
  18. - C_skip, C_sub, C_exp, C_vwl
  19. - Salience weights
  20. - Segmental features
  21. In this implementation, some parameters have been changed from their default
  22. values as described in [1], in order to replicate published results. All changes
  23. are noted in comments.
  24. Example usage
  25. -------------
  26. # Get optimal alignment of two phonetic sequences
  27. >>> align('θin', 'tenwis') # doctest: +SKIP
  28. [[('θ', 't'), ('i', 'e'), ('n', 'n'), ('-', 'w'), ('-', 'i'), ('-', 's')]]
  29. [1] G. Kondrak. Algorithms for Language Reconstruction. PhD dissertation,
  30. University of Toronto.
  31. """
  32. from __future__ import unicode_literals
  33. try:
  34. import numpy as np
  35. except ImportError:
  36. np = None
  37. # === Constants ===
  38. inf = float('inf')
  39. # Default values for maximum similarity scores (Kondrak 2002: 54)
  40. C_skip = 10 # Indels
  41. C_sub = 35 # Substitutions
  42. C_exp = 45 # Expansions/compressions
  43. C_vwl = 5 # Vowel/consonant relative weight (decreased from 10)
  44. consonants = [
  45. 'B',
  46. 'N',
  47. 'R',
  48. 'b',
  49. 'c',
  50. 'd',
  51. 'f',
  52. 'g',
  53. 'h',
  54. 'j',
  55. 'k',
  56. 'l',
  57. 'm',
  58. 'n',
  59. 'p',
  60. 'q',
  61. 'r',
  62. 's',
  63. 't',
  64. 'v',
  65. 'x',
  66. 'z',
  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. 'w',
  105. ]
  106. # Relevant features for comparing consonants and vowels
  107. R_c = [
  108. 'aspirated',
  109. 'lateral',
  110. 'manner',
  111. 'nasal',
  112. 'place',
  113. 'retroflex',
  114. 'syllabic',
  115. 'voice',
  116. ]
  117. # 'high' taken out of R_v because same as manner
  118. R_v = [
  119. 'back',
  120. 'lateral',
  121. 'long',
  122. 'manner',
  123. 'nasal',
  124. 'place',
  125. 'retroflex',
  126. 'round',
  127. 'syllabic',
  128. 'voice',
  129. ]
  130. # Flattened feature matrix (Kondrak 2002: 56)
  131. similarity_matrix = {
  132. # place
  133. 'bilabial': 1.0,
  134. 'labiodental': 0.95,
  135. 'dental': 0.9,
  136. 'alveolar': 0.85,
  137. 'retroflex': 0.8,
  138. 'palato-alveolar': 0.75,
  139. 'palatal': 0.7,
  140. 'velar': 0.6,
  141. 'uvular': 0.5,
  142. 'pharyngeal': 0.3,
  143. 'glottal': 0.1,
  144. 'labiovelar': 1.0,
  145. 'vowel': -1.0, # added 'vowel'
  146. # manner
  147. 'stop': 1.0,
  148. 'affricate': 0.9,
  149. 'fricative': 0.85, # increased fricative from 0.8
  150. 'trill': 0.7,
  151. 'tap': 0.65,
  152. 'approximant': 0.6,
  153. 'high vowel': 0.4,
  154. 'mid vowel': 0.2,
  155. 'low vowel': 0.0,
  156. 'vowel2': 0.5, # added vowel
  157. # high
  158. 'high': 1.0,
  159. 'mid': 0.5,
  160. 'low': 0.0,
  161. # back
  162. 'front': 1.0,
  163. 'central': 0.5,
  164. 'back': 0.0,
  165. # binary features
  166. 'plus': 1.0,
  167. 'minus': 0.0,
  168. }
  169. # Relative weights of phonetic features (Kondrak 2002: 55)
  170. salience = {
  171. 'syllabic': 5,
  172. 'place': 40,
  173. 'manner': 50,
  174. 'voice': 5, # decreased from 10
  175. 'nasal': 20, # increased from 10
  176. 'retroflex': 10,
  177. 'lateral': 10,
  178. 'aspirated': 5,
  179. 'long': 0, # decreased from 1
  180. 'high': 3, # decreased from 5
  181. 'back': 2, # decreased from 5
  182. 'round': 2, # decreased from 5
  183. }
  184. # (Kondrak 2002: 59-60)
  185. feature_matrix = {
  186. # Consonants
  187. 'p': {
  188. 'place': 'bilabial',
  189. 'manner': 'stop',
  190. 'syllabic': 'minus',
  191. 'voice': 'minus',
  192. 'nasal': 'minus',
  193. 'retroflex': 'minus',
  194. 'lateral': 'minus',
  195. 'aspirated': 'minus',
  196. },
  197. 'b': {
  198. 'place': 'bilabial',
  199. 'manner': 'stop',
  200. 'syllabic': 'minus',
  201. 'voice': 'plus',
  202. 'nasal': 'minus',
  203. 'retroflex': 'minus',
  204. 'lateral': 'minus',
  205. 'aspirated': 'minus',
  206. },
  207. 't': {
  208. 'place': 'alveolar',
  209. 'manner': 'stop',
  210. 'syllabic': 'minus',
  211. 'voice': 'minus',
  212. 'nasal': 'minus',
  213. 'retroflex': 'minus',
  214. 'lateral': 'minus',
  215. 'aspirated': 'minus',
  216. },
  217. 'd': {
  218. 'place': 'alveolar',
  219. 'manner': 'stop',
  220. 'syllabic': 'minus',
  221. 'voice': 'plus',
  222. 'nasal': 'minus',
  223. 'retroflex': 'minus',
  224. 'lateral': 'minus',
  225. 'aspirated': 'minus',
  226. },
  227. 'ʈ': {
  228. 'place': 'retroflex',
  229. 'manner': 'stop',
  230. 'syllabic': 'minus',
  231. 'voice': 'minus',
  232. 'nasal': 'minus',
  233. 'retroflex': 'plus',
  234. 'lateral': 'minus',
  235. 'aspirated': 'minus',
  236. },
  237. 'ɖ': {
  238. 'place': 'retroflex',
  239. 'manner': 'stop',
  240. 'syllabic': 'minus',
  241. 'voice': 'plus',
  242. 'nasal': 'minus',
  243. 'retroflex': 'plus',
  244. 'lateral': 'minus',
  245. 'aspirated': 'minus',
  246. },
  247. 'c': {
  248. 'place': 'palatal',
  249. 'manner': 'stop',
  250. 'syllabic': 'minus',
  251. 'voice': 'minus',
  252. 'nasal': 'minus',
  253. 'retroflex': 'minus',
  254. 'lateral': 'minus',
  255. 'aspirated': 'minus',
  256. },
  257. 'ɟ': {
  258. 'place': 'palatal',
  259. 'manner': 'stop',
  260. 'syllabic': 'minus',
  261. 'voice': 'plus',
  262. 'nasal': 'minus',
  263. 'retroflex': 'minus',
  264. 'lateral': 'minus',
  265. 'aspirated': 'minus',
  266. },
  267. 'k': {
  268. 'place': 'velar',
  269. 'manner': 'stop',
  270. 'syllabic': 'minus',
  271. 'voice': 'minus',
  272. 'nasal': 'minus',
  273. 'retroflex': 'minus',
  274. 'lateral': 'minus',
  275. 'aspirated': 'minus',
  276. },
  277. 'g': {
  278. 'place': 'velar',
  279. 'manner': 'stop',
  280. 'syllabic': 'minus',
  281. 'voice': 'plus',
  282. 'nasal': 'minus',
  283. 'retroflex': 'minus',
  284. 'lateral': 'minus',
  285. 'aspirated': 'minus',
  286. },
  287. 'q': {
  288. 'place': 'uvular',
  289. 'manner': 'stop',
  290. 'syllabic': 'minus',
  291. 'voice': 'minus',
  292. 'nasal': 'minus',
  293. 'retroflex': 'minus',
  294. 'lateral': 'minus',
  295. 'aspirated': 'minus',
  296. },
  297. 'ɢ': {
  298. 'place': 'uvular',
  299. 'manner': 'stop',
  300. 'syllabic': 'minus',
  301. 'voice': 'plus',
  302. 'nasal': 'minus',
  303. 'retroflex': 'minus',
  304. 'lateral': 'minus',
  305. 'aspirated': 'minus',
  306. },
  307. 'ʔ': {
  308. 'place': 'glottal',
  309. 'manner': 'stop',
  310. 'syllabic': 'minus',
  311. 'voice': 'minus',
  312. 'nasal': 'minus',
  313. 'retroflex': 'minus',
  314. 'lateral': 'minus',
  315. 'aspirated': 'minus',
  316. },
  317. 'm': {
  318. 'place': 'bilabial',
  319. 'manner': 'stop',
  320. 'syllabic': 'minus',
  321. 'voice': 'plus',
  322. 'nasal': 'plus',
  323. 'retroflex': 'minus',
  324. 'lateral': 'minus',
  325. 'aspirated': 'minus',
  326. },
  327. 'ɱ': {
  328. 'place': 'labiodental',
  329. 'manner': 'stop',
  330. 'syllabic': 'minus',
  331. 'voice': 'plus',
  332. 'nasal': 'plus',
  333. 'retroflex': 'minus',
  334. 'lateral': 'minus',
  335. 'aspirated': 'minus',
  336. },
  337. 'n': {
  338. 'place': 'alveolar',
  339. 'manner': 'stop',
  340. 'syllabic': 'minus',
  341. 'voice': 'plus',
  342. 'nasal': 'plus',
  343. 'retroflex': 'minus',
  344. 'lateral': 'minus',
  345. 'aspirated': 'minus',
  346. },
  347. 'ɳ': {
  348. 'place': 'retroflex',
  349. 'manner': 'stop',
  350. 'syllabic': 'minus',
  351. 'voice': 'plus',
  352. 'nasal': 'plus',
  353. 'retroflex': 'plus',
  354. 'lateral': 'minus',
  355. 'aspirated': 'minus',
  356. },
  357. 'ɲ': {
  358. 'place': 'palatal',
  359. 'manner': 'stop',
  360. 'syllabic': 'minus',
  361. 'voice': 'plus',
  362. 'nasal': 'plus',
  363. 'retroflex': 'minus',
  364. 'lateral': 'minus',
  365. 'aspirated': 'minus',
  366. },
  367. 'ŋ': {
  368. 'place': 'velar',
  369. 'manner': 'stop',
  370. 'syllabic': 'minus',
  371. 'voice': 'plus',
  372. 'nasal': 'plus',
  373. 'retroflex': 'minus',
  374. 'lateral': 'minus',
  375. 'aspirated': 'minus',
  376. },
  377. 'ɴ': {
  378. 'place': 'uvular',
  379. 'manner': 'stop',
  380. 'syllabic': 'minus',
  381. 'voice': 'plus',
  382. 'nasal': 'plus',
  383. 'retroflex': 'minus',
  384. 'lateral': 'minus',
  385. 'aspirated': 'minus',
  386. },
  387. 'N': {
  388. 'place': 'uvular',
  389. 'manner': 'stop',
  390. 'syllabic': 'minus',
  391. 'voice': 'plus',
  392. 'nasal': 'plus',
  393. 'retroflex': 'minus',
  394. 'lateral': 'minus',
  395. 'aspirated': 'minus',
  396. },
  397. 'ʙ': {
  398. 'place': 'bilabial',
  399. 'manner': 'trill',
  400. 'syllabic': 'minus',
  401. 'voice': 'plus',
  402. 'nasal': 'minus',
  403. 'retroflex': 'minus',
  404. 'lateral': 'minus',
  405. 'aspirated': 'minus',
  406. },
  407. 'B': {
  408. 'place': 'bilabial',
  409. 'manner': 'trill',
  410. 'syllabic': 'minus',
  411. 'voice': 'plus',
  412. 'nasal': 'minus',
  413. 'retroflex': 'minus',
  414. 'lateral': 'minus',
  415. 'aspirated': 'minus',
  416. },
  417. 'r': {
  418. 'place': 'alveolar',
  419. 'manner': 'trill',
  420. 'syllabic': 'minus',
  421. 'voice': 'plus',
  422. 'nasal': 'minus',
  423. 'retroflex': 'plus',
  424. 'lateral': 'minus',
  425. 'aspirated': 'minus',
  426. },
  427. 'ʀ': {
  428. 'place': 'uvular',
  429. 'manner': 'trill',
  430. 'syllabic': 'minus',
  431. 'voice': 'plus',
  432. 'nasal': 'minus',
  433. 'retroflex': 'minus',
  434. 'lateral': 'minus',
  435. 'aspirated': 'minus',
  436. },
  437. 'R': {
  438. 'place': 'uvular',
  439. 'manner': 'trill',
  440. 'syllabic': 'minus',
  441. 'voice': 'plus',
  442. 'nasal': 'minus',
  443. 'retroflex': 'minus',
  444. 'lateral': 'minus',
  445. 'aspirated': 'minus',
  446. },
  447. 'ɾ': {
  448. 'place': 'alveolar',
  449. 'manner': 'tap',
  450. 'syllabic': 'minus',
  451. 'voice': 'plus',
  452. 'nasal': 'minus',
  453. 'retroflex': 'minus',
  454. 'lateral': 'minus',
  455. 'aspirated': 'minus',
  456. },
  457. 'ɽ': {
  458. 'place': 'retroflex',
  459. 'manner': 'tap',
  460. 'syllabic': 'minus',
  461. 'voice': 'plus',
  462. 'nasal': 'minus',
  463. 'retroflex': 'plus',
  464. 'lateral': 'minus',
  465. 'aspirated': 'minus',
  466. },
  467. 'ɸ': {
  468. 'place': 'bilabial',
  469. 'manner': 'fricative',
  470. 'syllabic': 'minus',
  471. 'voice': 'minus',
  472. 'nasal': 'minus',
  473. 'retroflex': 'minus',
  474. 'lateral': 'minus',
  475. 'aspirated': 'minus',
  476. },
  477. 'β': {
  478. 'place': 'bilabial',
  479. 'manner': 'fricative',
  480. 'syllabic': 'minus',
  481. 'voice': 'plus',
  482. 'nasal': 'minus',
  483. 'retroflex': 'minus',
  484. 'lateral': 'minus',
  485. 'aspirated': 'minus',
  486. },
  487. 'f': {
  488. 'place': 'labiodental',
  489. 'manner': 'fricative',
  490. 'syllabic': 'minus',
  491. 'voice': 'minus',
  492. 'nasal': 'minus',
  493. 'retroflex': 'minus',
  494. 'lateral': 'minus',
  495. 'aspirated': 'minus',
  496. },
  497. 'v': {
  498. 'place': 'labiodental',
  499. 'manner': 'fricative',
  500. 'syllabic': 'minus',
  501. 'voice': 'plus',
  502. 'nasal': 'minus',
  503. 'retroflex': 'minus',
  504. 'lateral': 'minus',
  505. 'aspirated': 'minus',
  506. },
  507. 'θ': {
  508. 'place': 'dental',
  509. 'manner': 'fricative',
  510. 'syllabic': 'minus',
  511. 'voice': 'minus',
  512. 'nasal': 'minus',
  513. 'retroflex': 'minus',
  514. 'lateral': 'minus',
  515. 'aspirated': 'minus',
  516. },
  517. 'ð': {
  518. 'place': 'dental',
  519. 'manner': 'fricative',
  520. 'syllabic': 'minus',
  521. 'voice': 'plus',
  522. 'nasal': 'minus',
  523. 'retroflex': 'minus',
  524. 'lateral': 'minus',
  525. 'aspirated': 'minus',
  526. },
  527. 's': {
  528. 'place': 'alveolar',
  529. 'manner': 'fricative',
  530. 'syllabic': 'minus',
  531. 'voice': 'minus',
  532. 'nasal': 'minus',
  533. 'retroflex': 'minus',
  534. 'lateral': 'minus',
  535. 'aspirated': 'minus',
  536. },
  537. 'z': {
  538. 'place': 'alveolar',
  539. 'manner': 'fricative',
  540. 'syllabic': 'minus',
  541. 'voice': 'plus',
  542. 'nasal': 'minus',
  543. 'retroflex': 'minus',
  544. 'lateral': 'minus',
  545. 'aspirated': 'minus',
  546. },
  547. 'ʃ': {
  548. 'place': 'palato-alveolar',
  549. 'manner': 'fricative',
  550. 'syllabic': 'minus',
  551. 'voice': 'minus',
  552. 'nasal': 'minus',
  553. 'retroflex': 'minus',
  554. 'lateral': 'minus',
  555. 'aspirated': 'minus',
  556. },
  557. 'ʒ': {
  558. 'place': 'palato-alveolar',
  559. 'manner': 'fricative',
  560. 'syllabic': 'minus',
  561. 'voice': 'plus',
  562. 'nasal': 'minus',
  563. 'retroflex': 'minus',
  564. 'lateral': 'minus',
  565. 'aspirated': 'minus',
  566. },
  567. 'ʂ': {
  568. 'place': 'retroflex',
  569. 'manner': 'fricative',
  570. 'syllabic': 'minus',
  571. 'voice': 'minus',
  572. 'nasal': 'minus',
  573. 'retroflex': 'plus',
  574. 'lateral': 'minus',
  575. 'aspirated': 'minus',
  576. },
  577. 'ʐ': {
  578. 'place': 'retroflex',
  579. 'manner': 'fricative',
  580. 'syllabic': 'minus',
  581. 'voice': 'plus',
  582. 'nasal': 'minus',
  583. 'retroflex': 'plus',
  584. 'lateral': 'minus',
  585. 'aspirated': 'minus',
  586. },
  587. 'ç': {
  588. 'place': 'palatal',
  589. 'manner': 'fricative',
  590. 'syllabic': 'minus',
  591. 'voice': 'minus',
  592. 'nasal': 'minus',
  593. 'retroflex': 'minus',
  594. 'lateral': 'minus',
  595. 'aspirated': 'minus',
  596. },
  597. 'ʝ': {
  598. 'place': 'palatal',
  599. 'manner': 'fricative',
  600. 'syllabic': 'minus',
  601. 'voice': 'plus',
  602. 'nasal': 'minus',
  603. 'retroflex': 'minus',
  604. 'lateral': 'minus',
  605. 'aspirated': 'minus',
  606. },
  607. 'x': {
  608. 'place': 'velar',
  609. 'manner': 'fricative',
  610. 'syllabic': 'minus',
  611. 'voice': 'minus',
  612. 'nasal': 'minus',
  613. 'retroflex': 'minus',
  614. 'lateral': 'minus',
  615. 'aspirated': 'minus',
  616. },
  617. 'ɣ': {
  618. 'place': 'velar',
  619. 'manner': 'fricative',
  620. 'syllabic': 'minus',
  621. 'voice': 'plus',
  622. 'nasal': 'minus',
  623. 'retroflex': 'minus',
  624. 'lateral': 'minus',
  625. 'aspirated': 'minus',
  626. },
  627. 'χ': {
  628. 'place': 'uvular',
  629. 'manner': 'fricative',
  630. 'syllabic': 'minus',
  631. 'voice': 'minus',
  632. 'nasal': 'minus',
  633. 'retroflex': 'minus',
  634. 'lateral': 'minus',
  635. 'aspirated': 'minus',
  636. },
  637. 'ʁ': {
  638. 'place': 'uvular',
  639. 'manner': 'fricative',
  640. 'syllabic': 'minus',
  641. 'voice': 'plus',
  642. 'nasal': 'minus',
  643. 'retroflex': 'minus',
  644. 'lateral': 'minus',
  645. 'aspirated': 'minus',
  646. },
  647. 'ħ': {
  648. 'place': 'pharyngeal',
  649. 'manner': 'fricative',
  650. 'syllabic': 'minus',
  651. 'voice': 'minus',
  652. 'nasal': 'minus',
  653. 'retroflex': 'minus',
  654. 'lateral': 'minus',
  655. 'aspirated': 'minus',
  656. },
  657. 'ʕ': {
  658. 'place': 'pharyngeal',
  659. 'manner': 'fricative',
  660. 'syllabic': 'minus',
  661. 'voice': 'plus',
  662. 'nasal': 'minus',
  663. 'retroflex': 'minus',
  664. 'lateral': 'minus',
  665. 'aspirated': 'minus',
  666. },
  667. 'h': {
  668. 'place': 'glottal',
  669. 'manner': 'fricative',
  670. 'syllabic': 'minus',
  671. 'voice': 'minus',
  672. 'nasal': 'minus',
  673. 'retroflex': 'minus',
  674. 'lateral': 'minus',
  675. 'aspirated': 'minus',
  676. },
  677. 'ɦ': {
  678. 'place': 'glottal',
  679. 'manner': 'fricative',
  680. 'syllabic': 'minus',
  681. 'voice': 'plus',
  682. 'nasal': 'minus',
  683. 'retroflex': 'minus',
  684. 'lateral': 'minus',
  685. 'aspirated': 'minus',
  686. },
  687. 'ɬ': {
  688. 'place': 'alveolar',
  689. 'manner': 'fricative',
  690. 'syllabic': 'minus',
  691. 'voice': 'minus',
  692. 'nasal': 'minus',
  693. 'retroflex': 'minus',
  694. 'lateral': 'plus',
  695. 'aspirated': 'minus',
  696. },
  697. 'ɮ': {
  698. 'place': 'alveolar',
  699. 'manner': 'fricative',
  700. 'syllabic': 'minus',
  701. 'voice': 'plus',
  702. 'nasal': 'minus',
  703. 'retroflex': 'minus',
  704. 'lateral': 'plus',
  705. 'aspirated': 'minus',
  706. },
  707. 'ʋ': {
  708. 'place': 'labiodental',
  709. 'manner': 'approximant',
  710. 'syllabic': 'minus',
  711. 'voice': 'plus',
  712. 'nasal': 'minus',
  713. 'retroflex': 'minus',
  714. 'lateral': 'minus',
  715. 'aspirated': 'minus',
  716. },
  717. 'ɹ': {
  718. 'place': 'alveolar',
  719. 'manner': 'approximant',
  720. 'syllabic': 'minus',
  721. 'voice': 'plus',
  722. 'nasal': 'minus',
  723. 'retroflex': 'minus',
  724. 'lateral': 'minus',
  725. 'aspirated': 'minus',
  726. },
  727. 'ɻ': {
  728. 'place': 'retroflex',
  729. 'manner': 'approximant',
  730. 'syllabic': 'minus',
  731. 'voice': 'plus',
  732. 'nasal': 'minus',
  733. 'retroflex': 'plus',
  734. 'lateral': 'minus',
  735. 'aspirated': 'minus',
  736. },
  737. 'j': {
  738. 'place': 'palatal',
  739. 'manner': 'approximant',
  740. 'syllabic': 'minus',
  741. 'voice': 'plus',
  742. 'nasal': 'minus',
  743. 'retroflex': 'minus',
  744. 'lateral': 'minus',
  745. 'aspirated': 'minus',
  746. },
  747. 'ɰ': {
  748. 'place': 'velar',
  749. 'manner': 'approximant',
  750. 'syllabic': 'minus',
  751. 'voice': 'plus',
  752. 'nasal': 'minus',
  753. 'retroflex': 'minus',
  754. 'lateral': 'minus',
  755. 'aspirated': 'minus',
  756. },
  757. 'l': {
  758. 'place': 'alveolar',
  759. 'manner': 'approximant',
  760. 'syllabic': 'minus',
  761. 'voice': 'plus',
  762. 'nasal': 'minus',
  763. 'retroflex': 'minus',
  764. 'lateral': 'plus',
  765. 'aspirated': 'minus',
  766. },
  767. 'w': {
  768. 'place': 'labiovelar',
  769. 'manner': 'approximant',
  770. 'syllabic': 'minus',
  771. 'voice': 'plus',
  772. 'nasal': 'minus',
  773. 'retroflex': 'minus',
  774. 'lateral': 'minus',
  775. 'aspirated': 'minus',
  776. },
  777. # Vowels
  778. 'i': {
  779. 'place': 'vowel',
  780. 'manner': 'vowel2',
  781. 'syllabic': 'plus',
  782. 'voice': 'plus',
  783. 'nasal': 'minus',
  784. 'retroflex': 'minus',
  785. 'lateral': 'minus',
  786. 'high': 'high',
  787. 'back': 'front',
  788. 'round': 'minus',
  789. 'long': 'minus',
  790. 'aspirated': 'minus',
  791. },
  792. 'y': {
  793. 'place': 'vowel',
  794. 'manner': 'vowel2',
  795. 'syllabic': 'plus',
  796. 'voice': 'plus',
  797. 'nasal': 'minus',
  798. 'retroflex': 'minus',
  799. 'lateral': 'minus',
  800. 'high': 'high',
  801. 'back': 'front',
  802. 'round': 'plus',
  803. 'long': 'minus',
  804. 'aspirated': 'minus',
  805. },
  806. 'e': {
  807. 'place': 'vowel',
  808. 'manner': 'vowel2',
  809. 'syllabic': 'plus',
  810. 'voice': 'plus',
  811. 'nasal': 'minus',
  812. 'retroflex': 'minus',
  813. 'lateral': 'minus',
  814. 'high': 'mid',
  815. 'back': 'front',
  816. 'round': 'minus',
  817. 'long': 'minus',
  818. 'aspirated': 'minus',
  819. },
  820. 'E': {
  821. 'place': 'vowel',
  822. 'manner': 'vowel2',
  823. 'syllabic': 'plus',
  824. 'voice': 'plus',
  825. 'nasal': 'minus',
  826. 'retroflex': 'minus',
  827. 'lateral': 'minus',
  828. 'high': 'mid',
  829. 'back': 'front',
  830. 'round': 'minus',
  831. 'long': 'plus',
  832. 'aspirated': 'minus',
  833. },
  834. 'ø': {
  835. 'place': 'vowel',
  836. 'manner': 'vowel2',
  837. 'syllabic': 'plus',
  838. 'voice': 'plus',
  839. 'nasal': 'minus',
  840. 'retroflex': 'minus',
  841. 'lateral': 'minus',
  842. 'high': 'mid',
  843. 'back': 'front',
  844. 'round': 'plus',
  845. 'long': 'minus',
  846. 'aspirated': 'minus',
  847. },
  848. 'ɛ': {
  849. 'place': 'vowel',
  850. 'manner': 'vowel2',
  851. 'syllabic': 'plus',
  852. 'voice': 'plus',
  853. 'nasal': 'minus',
  854. 'retroflex': 'minus',
  855. 'lateral': 'minus',
  856. 'high': 'mid',
  857. 'back': 'front',
  858. 'round': 'minus',
  859. 'long': 'minus',
  860. 'aspirated': 'minus',
  861. },
  862. 'œ': {
  863. 'place': 'vowel',
  864. 'manner': 'vowel2',
  865. 'syllabic': 'plus',
  866. 'voice': 'plus',
  867. 'nasal': 'minus',
  868. 'retroflex': 'minus',
  869. 'lateral': 'minus',
  870. 'high': 'mid',
  871. 'back': 'front',
  872. 'round': 'plus',
  873. 'long': 'minus',
  874. 'aspirated': 'minus',
  875. },
  876. 'æ': {
  877. 'place': 'vowel',
  878. 'manner': 'vowel2',
  879. 'syllabic': 'plus',
  880. 'voice': 'plus',
  881. 'nasal': 'minus',
  882. 'retroflex': 'minus',
  883. 'lateral': 'minus',
  884. 'high': 'low',
  885. 'back': 'front',
  886. 'round': 'minus',
  887. 'long': 'minus',
  888. 'aspirated': 'minus',
  889. },
  890. 'a': {
  891. 'place': 'vowel',
  892. 'manner': 'vowel2',
  893. 'syllabic': 'plus',
  894. 'voice': 'plus',
  895. 'nasal': 'minus',
  896. 'retroflex': 'minus',
  897. 'lateral': 'minus',
  898. 'high': 'low',
  899. 'back': 'front',
  900. 'round': 'minus',
  901. 'long': 'minus',
  902. 'aspirated': 'minus',
  903. },
  904. 'A': {
  905. 'place': 'vowel',
  906. 'manner': 'vowel2',
  907. 'syllabic': 'plus',
  908. 'voice': 'plus',
  909. 'nasal': 'minus',
  910. 'retroflex': 'minus',
  911. 'lateral': 'minus',
  912. 'high': 'low',
  913. 'back': 'front',
  914. 'round': 'minus',
  915. 'long': 'plus',
  916. 'aspirated': 'minus',
  917. },
  918. 'ɨ': {
  919. 'place': 'vowel',
  920. 'manner': 'vowel2',
  921. 'syllabic': 'plus',
  922. 'voice': 'plus',
  923. 'nasal': 'minus',
  924. 'retroflex': 'minus',
  925. 'lateral': 'minus',
  926. 'high': 'high',
  927. 'back': 'central',
  928. 'round': 'minus',
  929. 'long': 'minus',
  930. 'aspirated': 'minus',
  931. },
  932. 'ʉ': {
  933. 'place': 'vowel',
  934. 'manner': 'vowel2',
  935. 'syllabic': 'plus',
  936. 'voice': 'plus',
  937. 'nasal': 'minus',
  938. 'retroflex': 'minus',
  939. 'lateral': 'minus',
  940. 'high': 'high',
  941. 'back': 'central',
  942. 'round': 'plus',
  943. 'long': 'minus',
  944. 'aspirated': 'minus',
  945. },
  946. 'ə': {
  947. 'place': 'vowel',
  948. 'manner': 'vowel2',
  949. 'syllabic': 'plus',
  950. 'voice': 'plus',
  951. 'nasal': 'minus',
  952. 'retroflex': 'minus',
  953. 'lateral': 'minus',
  954. 'high': 'mid',
  955. 'back': 'central',
  956. 'round': 'minus',
  957. 'long': 'minus',
  958. 'aspirated': 'minus',
  959. },
  960. 'u': {
  961. 'place': 'vowel',
  962. 'manner': 'vowel2',
  963. 'syllabic': 'plus',
  964. 'voice': 'plus',
  965. 'nasal': 'minus',
  966. 'retroflex': 'minus',
  967. 'lateral': 'minus',
  968. 'high': 'high',
  969. 'back': 'back',
  970. 'round': 'plus',
  971. 'long': 'minus',
  972. 'aspirated': 'minus',
  973. },
  974. 'U': {
  975. 'place': 'vowel',
  976. 'manner': 'vowel2',
  977. 'syllabic': 'plus',
  978. 'voice': 'plus',
  979. 'nasal': 'minus',
  980. 'retroflex': 'minus',
  981. 'lateral': 'minus',
  982. 'high': 'high',
  983. 'back': 'back',
  984. 'round': 'plus',
  985. 'long': 'plus',
  986. 'aspirated': 'minus',
  987. },
  988. 'o': {
  989. 'place': 'vowel',
  990. 'manner': 'vowel2',
  991. 'syllabic': 'plus',
  992. 'voice': 'plus',
  993. 'nasal': 'minus',
  994. 'retroflex': 'minus',
  995. 'lateral': 'minus',
  996. 'high': 'mid',
  997. 'back': 'back',
  998. 'round': 'plus',
  999. 'long': 'minus',
  1000. 'aspirated': 'minus',
  1001. },
  1002. 'O': {
  1003. 'place': 'vowel',
  1004. 'manner': 'vowel2',
  1005. 'syllabic': 'plus',
  1006. 'voice': 'plus',
  1007. 'nasal': 'minus',
  1008. 'retroflex': 'minus',
  1009. 'lateral': 'minus',
  1010. 'high': 'mid',
  1011. 'back': 'back',
  1012. 'round': 'plus',
  1013. 'long': 'plus',
  1014. 'aspirated': 'minus',
  1015. },
  1016. 'ɔ': {
  1017. 'place': 'vowel',
  1018. 'manner': 'vowel2',
  1019. 'syllabic': 'plus',
  1020. 'voice': 'plus',
  1021. 'nasal': 'minus',
  1022. 'retroflex': 'minus',
  1023. 'lateral': 'minus',
  1024. 'high': 'mid',
  1025. 'back': 'back',
  1026. 'round': 'plus',
  1027. 'long': 'minus',
  1028. 'aspirated': 'minus',
  1029. },
  1030. 'ɒ': {
  1031. 'place': 'vowel',
  1032. 'manner': 'vowel2',
  1033. 'syllabic': 'plus',
  1034. 'voice': 'plus',
  1035. 'nasal': 'minus',
  1036. 'retroflex': 'minus',
  1037. 'lateral': 'minus',
  1038. 'high': 'low',
  1039. 'back': 'back',
  1040. 'round': 'minus',
  1041. 'long': 'minus',
  1042. 'aspirated': 'minus',
  1043. },
  1044. 'I': {
  1045. 'place': 'vowel',
  1046. 'manner': 'vowel2',
  1047. 'syllabic': 'plus',
  1048. 'voice': 'plus',
  1049. 'nasal': 'minus',
  1050. 'retroflex': 'minus',
  1051. 'lateral': 'minus',
  1052. 'high': 'high',
  1053. 'back': 'front',
  1054. 'round': 'minus',
  1055. 'long': 'plus',
  1056. 'aspirated': 'minus',
  1057. },
  1058. }
  1059. # === Algorithm ===
  1060. def align(str1, str2, epsilon=0):
  1061. """
  1062. Compute the alignment of two phonetic strings.
  1063. :type str1, str2: str
  1064. :param str1, str2: Two strings to be aligned
  1065. :type epsilon: float (0.0 to 1.0)
  1066. :param epsilon: Adjusts threshold similarity score for near-optimal alignments
  1067. :rtpye: list(list(tuple(str, str)))
  1068. :return: Alignment(s) of str1 and str2
  1069. (Kondrak 2002: 51)
  1070. """
  1071. if np is None:
  1072. raise ImportError('You need numpy in order to use the align function')
  1073. assert 0.0 <= epsilon <= 1.0, "Epsilon must be between 0.0 and 1.0."
  1074. m = len(str1)
  1075. n = len(str2)
  1076. # This includes Kondrak's initialization of row 0 and column 0 to all 0s.
  1077. S = np.zeros((m + 1, n + 1), dtype=float)
  1078. # If i <= 1 or j <= 1, don't allow expansions as it doesn't make sense,
  1079. # and breaks array and string indices. Make sure they never get chosen
  1080. # by setting them to -inf.
  1081. for i in range(1, m + 1):
  1082. for j in range(1, n + 1):
  1083. edit1 = S[i - 1, j] + sigma_skip(str1[i - 1])
  1084. edit2 = S[i, j - 1] + sigma_skip(str2[j - 1])
  1085. edit3 = S[i - 1, j - 1] + sigma_sub(str1[i - 1], str2[j - 1])
  1086. if i > 1:
  1087. edit4 = S[i - 2, j - 1] + sigma_exp(str2[j - 1], str1[i - 2 : i])
  1088. else:
  1089. edit4 = -inf
  1090. if j > 1:
  1091. edit5 = S[i - 1, j - 2] + sigma_exp(str1[i - 1], str2[j - 2 : j])
  1092. else:
  1093. edit5 = -inf
  1094. S[i, j] = max(edit1, edit2, edit3, edit4, edit5, 0)
  1095. T = (1 - epsilon) * np.amax(S) # Threshold score for near-optimal alignments
  1096. alignments = []
  1097. for i in range(1, m + 1):
  1098. for j in range(1, n + 1):
  1099. if S[i, j] >= T:
  1100. alignments.append(_retrieve(i, j, 0, S, T, str1, str2, []))
  1101. return alignments
  1102. def _retrieve(i, j, s, S, T, str1, str2, out):
  1103. """
  1104. Retrieve the path through the similarity matrix S starting at (i, j).
  1105. :rtype: list(tuple(str, str))
  1106. :return: Alignment of str1 and str2
  1107. """
  1108. if S[i, j] == 0:
  1109. return out
  1110. else:
  1111. if j > 1 and S[i - 1, j - 2] + sigma_exp(str1[i - 1], str2[j - 2 : j]) + s >= T:
  1112. out.insert(0, (str1[i - 1], str2[j - 2 : j]))
  1113. _retrieve(
  1114. i - 1,
  1115. j - 2,
  1116. s + sigma_exp(str1[i - 1], str2[j - 2 : j]),
  1117. S,
  1118. T,
  1119. str1,
  1120. str2,
  1121. out,
  1122. )
  1123. elif (
  1124. i > 1 and S[i - 2, j - 1] + sigma_exp(str2[j - 1], str1[i - 2 : i]) + s >= T
  1125. ):
  1126. out.insert(0, (str1[i - 2 : i], str2[j - 1]))
  1127. _retrieve(
  1128. i - 2,
  1129. j - 1,
  1130. s + sigma_exp(str2[j - 1], str1[i - 2 : i]),
  1131. S,
  1132. T,
  1133. str1,
  1134. str2,
  1135. out,
  1136. )
  1137. elif S[i, j - 1] + sigma_skip(str2[j - 1]) + s >= T:
  1138. out.insert(0, ('-', str2[j - 1]))
  1139. _retrieve(i, j - 1, s + sigma_skip(str2[j - 1]), S, T, str1, str2, out)
  1140. elif S[i - 1, j] + sigma_skip(str1[i - 1]) + s >= T:
  1141. out.insert(0, (str1[i - 1], '-'))
  1142. _retrieve(i - 1, j, s + sigma_skip(str1[i - 1]), S, T, str1, str2, out)
  1143. elif S[i - 1, j - 1] + sigma_sub(str1[i - 1], str2[j - 1]) + s >= T:
  1144. out.insert(0, (str1[i - 1], str2[j - 1]))
  1145. _retrieve(
  1146. i - 1,
  1147. j - 1,
  1148. s + sigma_sub(str1[i - 1], str2[j - 1]),
  1149. S,
  1150. T,
  1151. str1,
  1152. str2,
  1153. out,
  1154. )
  1155. return out
  1156. def sigma_skip(p):
  1157. """
  1158. Returns score of an indel of P.
  1159. (Kondrak 2002: 54)
  1160. """
  1161. return C_skip
  1162. def sigma_sub(p, q):
  1163. """
  1164. Returns score of a substitution of P with Q.
  1165. (Kondrak 2002: 54)
  1166. """
  1167. return C_sub - delta(p, q) - V(p) - V(q)
  1168. def sigma_exp(p, q):
  1169. """
  1170. Returns score of an expansion/compression.
  1171. (Kondrak 2002: 54)
  1172. """
  1173. q1 = q[0]
  1174. q2 = q[1]
  1175. return C_exp - delta(p, q1) - delta(p, q2) - V(p) - max(V(q1), V(q2))
  1176. def delta(p, q):
  1177. """
  1178. Return weighted sum of difference between P and Q.
  1179. (Kondrak 2002: 54)
  1180. """
  1181. features = R(p, q)
  1182. total = 0
  1183. for f in features:
  1184. total += diff(p, q, f) * salience[f]
  1185. return total
  1186. def diff(p, q, f):
  1187. """
  1188. Returns difference between phonetic segments P and Q for feature F.
  1189. (Kondrak 2002: 52, 54)
  1190. """
  1191. p_features, q_features = feature_matrix[p], feature_matrix[q]
  1192. return abs(similarity_matrix[p_features[f]] - similarity_matrix[q_features[f]])
  1193. def R(p, q):
  1194. """
  1195. Return relevant features for segment comparsion.
  1196. (Kondrak 2002: 54)
  1197. """
  1198. if p in consonants or q in consonants:
  1199. return R_c
  1200. return R_v
  1201. def V(p):
  1202. """
  1203. Return vowel weight if P is vowel.
  1204. (Kondrak 2002: 54)
  1205. """
  1206. if p in consonants:
  1207. return 0
  1208. return C_vwl
  1209. # === Test ===
  1210. def demo():
  1211. """
  1212. A demonstration of the result of aligning phonetic sequences
  1213. used in Kondrak's (2002) dissertation.
  1214. """
  1215. data = [pair.split(',') for pair in cognate_data.split('\n')]
  1216. for pair in data:
  1217. alignment = align(pair[0], pair[1])[0]
  1218. alignment = ['({}, {})'.format(a[0], a[1]) for a in alignment]
  1219. alignment = ' '.join(alignment)
  1220. print('{} ~ {} : {}'.format(pair[0], pair[1], alignment))
  1221. cognate_data = """jo,ʒə
  1222. tu,ty
  1223. nosotros,nu
  1224. kjen,ki
  1225. ke,kwa
  1226. todos,tu
  1227. una,ən
  1228. dos,dø
  1229. tres,trwa
  1230. ombre,om
  1231. arbol,arbrə
  1232. pluma,plym
  1233. kabeθa,kap
  1234. boka,buʃ
  1235. pje,pje
  1236. koraθon,kœr
  1237. ber,vwar
  1238. benir,vənir
  1239. deθir,dir
  1240. pobre,povrə
  1241. ðis,dIzes
  1242. ðæt,das
  1243. wat,vas
  1244. nat,nixt
  1245. loŋ,laŋ
  1246. mæn,man
  1247. fleʃ,flajʃ
  1248. bləd,blyt
  1249. feðər,fEdər
  1250. hær,hAr
  1251. ir,Or
  1252. aj,awgə
  1253. nowz,nAzə
  1254. mawθ,munt
  1255. təŋ,tsuŋə
  1256. fut,fys
  1257. nij,knI
  1258. hænd,hant
  1259. hart,herts
  1260. livər,lEbər
  1261. ænd,ante
  1262. æt,ad
  1263. blow,flAre
  1264. ir,awris
  1265. ijt,edere
  1266. fiʃ,piʃkis
  1267. flow,fluere
  1268. staɾ,stella
  1269. ful,plenus
  1270. græs,gramen
  1271. hart,kordis
  1272. horn,korny
  1273. aj,ego
  1274. nij,genU
  1275. məðər,mAter
  1276. mawntən,mons
  1277. nejm,nomen
  1278. njuw,nowus
  1279. wən,unus
  1280. rawnd,rotundus
  1281. sow,suere
  1282. sit,sedere
  1283. θrij,tres
  1284. tuwθ,dentis
  1285. θin,tenwis
  1286. kinwawa,kenuaʔ
  1287. nina,nenah
  1288. napewa,napɛw
  1289. wapimini,wapemen
  1290. namesa,namɛʔs
  1291. okimawa,okemaw
  1292. ʃiʃipa,seʔsep
  1293. ahkohkwa,ahkɛh
  1294. pematesiweni,pematesewen
  1295. asenja,aʔsɛn"""
  1296. if __name__ == '__main__':
  1297. demo()