Skip to content

Commit

Permalink
[natural_translit] Atomic functions for PhonologicalObject.Inventory
Browse files Browse the repository at this point in the history
  - or_from_suppl: Add Or expressions of symbol lists as a supplement to the atomics inventory to make it easier to write expressions like (ph.t >> ph.d, preceding=ph.vowel, following=ph.vowel)
  - sync_atomics: Sync atomic inventory with symbol inventory.
    - Make sure atomic features match symbol features.
    - Optionally make or update Or expressions of a list of supplements.

PiperOrigin-RevId: 720529974
  • Loading branch information
isingoo authored and copybara-github committed Jan 28, 2025
1 parent 05a31f6 commit 018a0d5
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def _build_inventory() -> po.Phon.Inventory:
labialized_consonant.update_descriptives(phf.labialization.labialized)
for palatalized_consonant in [ph.sj]:
palatalized_consonant.update_descriptives(phf.palatalization.heavy)
return ph
return ph.sync_atomics()


INVENTORY = _build_inventory()
44 changes: 41 additions & 3 deletions nisaba/scripts/natural_translit/phonology/phonological_symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ class PhonologicalSymbol(sym.Symbol):
def __init__(
self,
alias: str = '',
text: str = '',
raw: str = '',
index: ty.IntOrNothing = ty.UNSPECIFIED,
name: str = '',
features: ft.Feature.ITERABLE = ty.UNSPECIFIED,
):
super().__init__(alias, text, raw, index, name)
super().__init__(alias, raw=raw, index=index, name=name)
self.text = raw if raw else self.alias
self.features.new_profile(
ft.Feature.Profile(self.PH_DESCRIPTIVE_FEATURES, 'new')
)
Expand All @@ -54,6 +54,44 @@ def _add_symbol_and_atomic(self, symbol: 'PhonologicalSymbol') -> bool:
exp.Atomic.get_instance(symbol)
)

def or_from_suppl(self, suppl: ty.IterableThing) -> bool:
"""Makes an Or from a supplement and adds it to the atomics."""
return self.atomics.add_suppl(exp.Or(*suppl, alias=suppl.alias))

def sync_atomics(
self, or_suppls: ty.ListOrNothing = ty.UNSPECIFIED
) -> 'PhonologicalSymbol.Inventory':
"""Syncs the atomic inventory with the symbol inventory.
Updates the features of the atomic instance of each phonological symbol
to match the features of the symbol. Optionally updates the members of
Or supplements in atomics to include all members of the given supplements
in the list. For example, if an `inventory.vowel` iterable and a
corresponding `inventory.atomics.vowel` Or were initiated as `[a, e, i]`
and `(a | e | i)`, and later `[o, u]` was added to `inventory.vowel`,
this function will update `inventory.atomics.vowel` to
`(a | e | i | o | u)`.
Updates the atomic inventory with the supplements.
Args:
or_suppls: Optional list iterable supplements. If specified, the
corresponding Or supplement of the given supplements will be updated to
include all symbols.
Returns:
The inventory.
"""
for atomic in self.atomics:
for profile in atomic.features:
profile.update(atomic.symbol.features.get(profile.inventory))
for suppl in ty.type_check(or_suppls, []):
if suppl.alias not in self.atomics.suppl_aliases:
self.or_from_suppl(suppl)
self.atomics.get(suppl.alias).add(*suppl)
return self


class Phon(PhonologicalSymbol):
"""Class for representing phonemes, phones, and phonological modifiers."""
Expand Down Expand Up @@ -128,5 +166,5 @@ def add_phonemes(
) -> list['Phon']:
phs = [ph for ph in phonemes if self._add_phoneme(ph)]
if list_alias:
self.make_suppl(list_alias, phs)
self.make_iterable_suppl(list_alias, *phs)
return phs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ def _test_inventory() -> po.Phon.Inventory:
ph_inv.i.update_descriptives(
phf.height.close, phf.backness.front, phf.labialization.unrounded
)
return ph_inv
ph_inv.make_iterable_suppl('close_like', ph_inv.e)
ph_inv.or_from_suppl(ph_inv.close_like)
ph_inv.close_like.add(ph_inv.i)
return ph_inv.sync_atomics(or_suppls=[ph_inv.vowel, ph_inv.close_like])


_TEST = _test_inventory()
Expand Down Expand Up @@ -146,5 +149,10 @@ def test_phon_update_descriptives(self):
'| syllabicity | syllabic |\n',
)

def test_sync_atomics(self):
self.AssertStrEqual(_TEST.atomics.a.features, _TEST.a.features)
self.AssertStrEqual(_TEST.atomics.vowel, '(a | e | i)')
self.AssertStrEqual(_TEST.atomics.close_like, '(e | i)')

if __name__ == '__main__':
absltest.main()
2 changes: 1 addition & 1 deletion nisaba/scripts/natural_translit/utils/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ def __init__(self, symbol: sym.Symbol, alias: str = ''):
symbol.raw,
symbol.index,
symbol.name,
symbol.features.copy(),
)
self._item_type = Atomic
self._items = [self]
self.symbol = symbol.symbol if isinstance(symbol, Atomic) else symbol
self.features = symbol.features.copy()

@classmethod
def get_instance(cls, symbol: sym.Symbol) -> 'Atomic':
Expand Down
8 changes: 7 additions & 1 deletion nisaba/scripts/natural_translit/utils/symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,12 @@ def _add_symbol(self, sym: 'Symbol') -> bool:
self._add_to_dicts(sym)
return True

def make_iterable_suppl(self, alias: str = '', *items) -> bool:
"""Makes an iterable supplement with the given symbols."""
return self.add_suppl(
ty.IterableThing(*items, alias=alias)
)

def add_symbols(self, *symbols, list_alias: str = '') -> list['Symbol']:
"""Adds multiple symbols to the inventory.
Expand All @@ -604,7 +610,7 @@ def add_symbols(self, *symbols, list_alias: str = '') -> list['Symbol']:
"""
syms = list(filter(self._add_symbol, [sym for sym in symbols]))
if list_alias:
self.make_suppl(list_alias, syms)
self.make_iterable_suppl(list_alias, *syms)
return syms

def lookup(
Expand Down
10 changes: 10 additions & 0 deletions nisaba/scripts/natural_translit/utils/symbol_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ def _latn_inventory() -> sym.Symbol.Inventory:
sym.Symbol('b', text='b', raw='b', name='LETTER B'),
sym.Symbol('i', text='i', raw='i', name='LETTER I'),
sym.Symbol('i_uc', text='I', raw='I', name='UPPERCASE LETTER I'),
)
syms.make_iterable_suppl('letter', syms.a, syms.b, syms.i, syms.i_uc)
syms.add_symbols(
sym.Symbol('digit_1', text='1', raw='1', name='DIGIT ONE'),
sym.Symbol('digit_2', text='2', raw='2', name='DIGIT TWO'),
list_alias='digit',
)
for symbol in syms:
symbol.features.new_profile(ft.Feature.Profile(_TEST_FTR, 'new'))
Expand Down Expand Up @@ -128,6 +132,12 @@ def test_symbol_inventory_lookup(self):
self.assertEqual(_SYM.text_lookup('🜔'), _SYM.schwa)
self.assertEqual(_SYM.raw_lookup('🜔'), _CTRL.unk)

def test_symbol_inventory_iterable_suppl(self):
self.assertEqual(_LATN.letter.alias, 'letter')
self.assertIn(_LATN.a, _LATN.letter)
self.assertEqual(_LATN.digit.alias, 'digit')
self.assertIn(_LATN.digit_1, _LATN.digit)

def test_controls(self):
self.assertEqual(
sym.Symbol.descriptions(*_CTRL),
Expand Down

0 comments on commit 018a0d5

Please sign in to comment.