diff --git a/src/interscript/equivalence.py b/src/interscript/equivalence.py new file mode 100644 index 0000000..d422ab8 --- /dev/null +++ b/src/interscript/equivalence.py @@ -0,0 +1,34 @@ +"""A3 (WO59): mechanical equivalence checking between two systems. + +Mirrors the ruby Interscript::Equivalence contract: probe a sample set +through both systems, report the agreement ratio plus the distinguishing +samples. +""" + +from __future__ import annotations + +from .engine import Engine +from .isc import isc_to_tree + + +def check(first_source: str, second_source: str, samples: list[str]) -> dict: + first = Engine(isc_to_tree(first_source)) + second = Engine(isc_to_tree(second_source)) + distinguishing = [] + tested = 0 + matched = 0 + for sample in samples: + tested += 1 + a = first.transliterate(sample) + b = second.transliterate(sample) + if a == b: + matched += 1 + else: + distinguishing.append({"sample": sample, "first": a, "second": b}) + ratio = 1.0 if tested == 0 else matched / tested + return { + "tested": tested, + "matched": matched, + "ratio": ratio, + "distinguishing": distinguishing, + } diff --git a/tests/test_equivalence.py b/tests/test_equivalence.py new file mode 100644 index 0000000..6ce6ccf --- /dev/null +++ b/tests/test_equivalence.py @@ -0,0 +1,48 @@ +import pytest + +from interscript.engine import Engine +from interscript.equivalence import check +from interscript.isc import isc_to_tree + +A = ( + 'system "TSTA:xx-Latn:Latn:1" {\n' + ' stage main { parallel {\n sub "a" "x"\n } }\n' + "}\n" +) +B = ( + 'system "TSTB:xx-Latn:Latn:2" {\n' + ' stage main { parallel {\n sub "a" "y"\n } }\n' + "}\n" +) +B_EQUIV = ( + 'system "TSTB2:xx-Latn:Latn:3" {\n' + ' stage main { parallel {\n sub "a" "x"\n } }\n' + "}\n" +) + + +def test_equivalent_systems_report_ratio_one(): + r = check(A, B_EQUIV, ["ba", "a", "zzz"]) + assert r["tested"] == 3 + assert r["matched"] == 3 + assert r["ratio"] == 1.0 + assert r["distinguishing"] == [] + + +def test_distinguishing_samples_are_collected(): + r = check(A, B, ["zzz", "ba"]) + assert r["tested"] == 2 + assert r["matched"] == 1 + assert r["ratio"] == 0.5 + assert r["distinguishing"] == [ + {"sample": "ba", "first": "bx", "second": "by"} + ] + + +def test_empty_sample_set_is_vacuously_equivalent(): + assert check(A, B, [])["ratio"] == 1.0 + + +@pytest.mark.parametrize("src", [A, B, B_EQUIV]) +def test_sources_parse(src): + Engine(isc_to_tree(src))