Merge pull request 'fix(generate): plural-subject agreement, 26/26 (Phase 3)' (#134) from fix/predicate-agreement into main
This commit is contained in:
commit
bb82029e8d
9 changed files with 274 additions and 11 deletions
|
|
@ -723,6 +723,56 @@ Deliver:
|
|||
| the gap is closed for good | new cases exercise quantifier × copular; reverting either fix goes red |
|
||||
| no regression | `english_fluency_ood` 117/117 + 39/39 holds; smoke/deductive unchanged |
|
||||
|
||||
**RESULT — all three met.** Agreement **26/26** (from 17/26). `english_fluency_ood`
|
||||
**117/117 + 39/39** (+ dev 13/13) unchanged. New construction **C14
|
||||
`quantified_copular`**, 13 cases, `grammatical_coverage` v1 **49/49**.
|
||||
|
||||
Root causes, both single-point as §1.4 predicted:
|
||||
|
||||
1. `base_form` is a **single-verb** function and was applied to whole phrases,
|
||||
stripping the final word's last character-class. Fixed by
|
||||
`morphology.agree_plural_phrase`, which inflects the **head** (the finite
|
||||
verb is the first token of every humanized predicate) and carries the rest
|
||||
through. `base_form` itself is left single-verb and **pinned as still wrong
|
||||
on a phrase**, so nobody "fixes" the symptom in the wrong place.
|
||||
2. The plural branch never consulted `copular` — it returned the bare base. Now
|
||||
it agrees the phrase. The plural **negated** branch had the same defect
|
||||
(`do not is defined a`) and is fixed with it: a plural copula takes a bare
|
||||
`not`, everything else takes do-support.
|
||||
|
||||
**Two further defects found while writing the cases**, both genuine and both
|
||||
fixed here:
|
||||
|
||||
- `base_form("causes")` → **`"caus"`**. The `-es` sibilant rule fired on a
|
||||
stem ending in a single `s`; it should require a doubled `ss` ("passes" →
|
||||
"pass") because a single `s` is nearly always a stem ending in `e` that took a
|
||||
plain `-s` ("causes" → "cause").
|
||||
- `pluralize("proof")` → **`"prooves"`**. `f`/`fe` → `ves` is **not productive
|
||||
in English** (proof→proofs, chief→chiefs, roof→roofs); it applies to a closed
|
||||
set. Now derived as `lexicon.VES_PLURAL_SINGULARS` from the `ves` rows of
|
||||
`IRREGULAR_SINGULARS`, so the rule cannot claim a word the table does not
|
||||
know. **Phase 2B had put `pluralize` on the serving path**, so this one was
|
||||
live.
|
||||
|
||||
**The first draft of the C14 cases could not fail.** `must_contain` and
|
||||
`word_order` listed quantifier, subject and object — but *not the verb* — so
|
||||
`all molecules is defined a compound` passed. Reverting the fix left 47/47 green.
|
||||
Rewritten with the agreed verb in both constraints plus `reject_surfaces`
|
||||
carrying the **actual** pre-fix output (computed by running the reverted code,
|
||||
not guessed). Mutation now:
|
||||
|
||||
| reverted fix | C14 |
|
||||
|---|---|
|
||||
| baseline | 13/13 |
|
||||
| phrase-head plural agreement | **4/13** |
|
||||
| `-es` stem rule | **12/13** |
|
||||
| closed `ves` set | **11/13** |
|
||||
|
||||
The 4 survivors of the first mutation are the mass-noun and unchanged controls,
|
||||
which is the correct behaviour — `all evidence is grounded in truth` must *not*
|
||||
pluralize. Same lesson as the lane pins in #133: a pin that cannot fail guards
|
||||
nothing, and the only way to know is to break the thing on purpose.
|
||||
|
||||
### Phase 4 — Resolve `realize_semantic` vs `realize_target` — **authorization gate**
|
||||
|
||||
*Touches:* `core/cognition/pipeline.py` or the eval lanes. **First phase that can
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ this arc's direction (plan §6).
|
|||
|
||||
| corpus | source | size |
|
||||
|---|---|---|
|
||||
| positive graphs | committed `english_fluency_ood` + `grammatical_coverage` case files | 280 |
|
||||
| positive graphs | committed `english_fluency_ood` + `grammatical_coverage` case files | 293 |
|
||||
| positive surfaces | in-module, each verified comprehensible by probe | 8 |
|
||||
| negative surfaces | hand-authored salad + deterministic token shuffles of the positives | 16 |
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ grammar checking. Shuffling uses a fixed rotation rather than a PRNG so
|
|||
## v1 baseline — measured on `main` @ `9696443a`
|
||||
|
||||
```
|
||||
graph_cases 280
|
||||
graph_cases 293
|
||||
g_write_rate 1.000
|
||||
g_read_rate 0.000 <-- CORE reads 0% of what it writes
|
||||
g_propositions_expected 370
|
||||
|
|
|
|||
|
|
@ -34,3 +34,16 @@
|
|||
{"id": "gram_C13_p01", "construction": "C13", "construction_name": "imperfective_aspect", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "wisdom", "predicate": "follows", "obj": "knowledge", "aspect": "imperfective"}], "edges": []}, "accept_surfaces": ["wisdom is following knowledge"], "constraints": {"must_contain": ["wisdom", "is", "following", "knowledge"], "word_order": ["wisdom", "is", "following", "knowledge"], "max_words": 8}}
|
||||
{"id": "gram_C13_p02", "construction": "C13", "construction_name": "imperfective_aspect", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "evidence", "predicate": "supports", "obj": "truth", "aspect": "imperfective"}], "edges": []}, "accept_surfaces": ["evidence is supporting truth"], "constraints": {"must_contain": ["evidence", "is", "supporting", "truth"], "word_order": ["evidence", "is", "supporting", "truth"], "max_words": 8}}
|
||||
{"id": "gram_C13_p03", "construction": "C13", "construction_name": "imperfective_aspect", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "dawn", "predicate": "precedes", "obj": "day", "aspect": "imperfective"}], "edges": []}, "accept_surfaces": ["dawn is preceding day"], "constraints": {"must_contain": ["dawn", "is", "preceding", "day"], "word_order": ["dawn", "is", "preceding", "day"], "max_words": 8}}
|
||||
{"id": "gram_C14_p01", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "molecule", "predicate": "is_defined_as", "obj": "compound", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all molecules are defined as compound"], "constraints": {"must_contain": ["all", "molecules", "are", "defined", "as", "compound"], "word_order": ["all", "molecules", "are", "defined", "as", "compound"], "max_words": 8}, "reject_surfaces": ["all molecules is defined a compound"]}
|
||||
{"id": "gram_C14_p02", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "theory", "predicate": "is_caused_by", "obj": "observation", "quantifier": "some"}], "edges": []}, "accept_surfaces": ["some theories are caused by observation"], "constraints": {"must_contain": ["some", "theories", "are", "caused", "by", "observation"], "word_order": ["some", "theories", "are", "caused", "by", "observation"], "max_words": 8}, "reject_surfaces": ["some theories is caused by observation"]}
|
||||
{"id": "gram_C14_p03", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "wolf", "predicate": "belongs_to", "obj": "pack", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all wolves belong to pack"], "constraints": {"must_contain": ["all", "wolves", "belong", "to", "pack"], "word_order": ["all", "wolves", "belong", "to", "pack"], "max_words": 7}, "reject_surfaces": ["all wolfs belongs to pack"]}
|
||||
{"id": "gram_C14_p04", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "claim", "predicate": "is_grounded_in", "obj": "evidence", "quantifier": "most"}], "edges": []}, "accept_surfaces": ["most claims are grounded in evidence"], "constraints": {"must_contain": ["most", "claims", "are", "grounded", "in", "evidence"], "word_order": ["most", "claims", "are", "grounded", "in", "evidence"], "max_words": 8}, "reject_surfaces": ["most claims is grounded in evidence"]}
|
||||
{"id": "gram_C14_p05", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "method", "predicate": "contrasts_with", "obj": "intuition", "quantifier": "few"}], "edges": []}, "accept_surfaces": ["few methods contrast with intuition"], "constraints": {"must_contain": ["few", "methods", "contrast", "with", "intuition"], "word_order": ["few", "methods", "contrast", "with", "intuition"], "max_words": 7}, "reject_surfaces": ["few methods contrasts with intuition"]}
|
||||
{"id": "gram_C14_p06", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "proof", "predicate": "is_verified_as", "obj": "sound", "quantifier": "many"}], "edges": []}, "accept_surfaces": ["many proofs are verified as sound"], "constraints": {"must_contain": ["many", "proofs", "are", "verified", "as", "sound"], "word_order": ["many", "proofs", "are", "verified", "as", "sound"], "max_words": 8}, "reject_surfaces": ["many proofs is verified a sound"]}
|
||||
{"id": "gram_C14_p07", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "axiom", "predicate": "is_distinguished_from", "obj": "theorem", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all axioms are distinguished from theorem"], "constraints": {"must_contain": ["all", "axioms", "are", "distinguished", "from", "theorem"], "word_order": ["all", "axioms", "are", "distinguished", "from", "theorem"], "max_words": 8}, "reject_surfaces": ["all axioms is distinguished from theorem"]}
|
||||
{"id": "gram_C14_p08", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "evidence", "predicate": "is_grounded_in", "obj": "truth", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all evidence is grounded in truth"], "constraints": {"must_contain": ["all", "evidence", "is", "grounded", "in", "truth"], "word_order": ["all", "evidence", "is", "grounded", "in", "truth"], "max_words": 8}}
|
||||
{"id": "gram_C14_p09", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "knowledge", "predicate": "is_defined_as", "obj": "justified", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all knowledge is defined as justified"], "constraints": {"must_contain": ["all", "knowledge", "is", "defined", "as", "justified"], "word_order": ["all", "knowledge", "is", "defined", "as", "justified"], "max_words": 8}}
|
||||
{"id": "gram_C14_p10", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "molecule", "predicate": "is_defined_as", "obj": "compound", "quantifier": "all", "negated": true}], "edges": []}, "accept_surfaces": ["all molecules are not defined as compound"], "constraints": {"must_contain": ["all", "molecules", "are", "not", "defined", "as", "compound"], "word_order": ["all", "molecules", "are", "not", "defined", "as", "compound"], "max_words": 9}, "reject_surfaces": ["all molecules do not is defined a compound"]}
|
||||
{"id": "gram_C14_p11", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "wolf", "predicate": "belongs_to", "obj": "pack", "quantifier": "all", "negated": true}], "edges": []}, "accept_surfaces": ["all wolves do not belong to pack"], "constraints": {"must_contain": ["all", "wolves", "do", "not", "belong", "to", "pack"], "word_order": ["all", "wolves", "do", "not", "belong", "to", "pack"], "max_words": 9}, "reject_surfaces": ["all wolfs do not belongs to pack"]}
|
||||
{"id": "gram_C14_p12", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "molecule", "predicate": "causes", "obj": "reaction", "quantifier": "all"}], "edges": []}, "accept_surfaces": ["all molecules cause reaction"], "constraints": {"must_contain": ["all", "molecules", "cause", "reaction"], "word_order": ["all", "molecules", "cause", "reaction"], "max_words": 6}, "reject_surfaces": ["all molecules caus reaction"]}
|
||||
{"id": "gram_C14_p13", "construction": "C14", "construction_name": "quantified_copular", "proposition_graph": {"nodes": [{"node_id": "n1", "subject": "observation", "predicate": "evidences", "obj": "claim", "quantifier": "many"}], "edges": []}, "accept_surfaces": ["many observations evidence claim"], "constraints": {"must_contain": ["many", "observations", "evidence", "claim"], "word_order": ["many", "observations", "evidence", "claim"], "max_words": 6}}
|
||||
|
|
|
|||
|
|
@ -282,6 +282,22 @@ IRREGULAR_SINGULARS: Final[dict[str, str]] = {
|
|||
"offspring": "offspring", "aircraft": "aircraft", "news": "news",
|
||||
}
|
||||
|
||||
#: Singulars whose plural is formed by ``f``/``fe`` → ``ves``, **derived** from
|
||||
#: the rows of :data:`IRREGULAR_SINGULARS` whose plural actually ends in
|
||||
#: ``ves`` — wolf, knife, life, leaf, half, elf, loaf, thief.
|
||||
#:
|
||||
#: The ``f`` → ``ves`` change is **not productive in English**: it applies to a
|
||||
#: closed set, and the productive form is a plain ``-s`` (proof→proofs,
|
||||
#: chief→chiefs, roof→roofs, belief→beliefs). Applying it as a suffix rule made
|
||||
#: ``pluralize("proof")`` return **"prooves"**, which Phase 2B had put on the
|
||||
#: serving path. Derived rather than hand-listed so the rule cannot claim a
|
||||
#: word the number table does not actually know.
|
||||
VES_PLURAL_SINGULARS: Final[frozenset[str]] = frozenset(
|
||||
singular
|
||||
for plural, singular in IRREGULAR_SINGULARS.items()
|
||||
if plural.endswith("ves") and plural != singular
|
||||
)
|
||||
|
||||
#: Nouns whose plural equals their singular, **derived** from the invariant
|
||||
#: rows of :data:`IRREGULAR_SINGULARS` (the rows where key == value).
|
||||
#:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from __future__ import annotations
|
|||
|
||||
from generate.lexicon import (
|
||||
INVARIANT_NUMBER,
|
||||
VES_PLURAL_SINGULARS,
|
||||
IRREGULAR_PLURALS,
|
||||
IRREGULAR_SINGULARS,
|
||||
MASS_NOUNS,
|
||||
|
|
@ -167,18 +168,65 @@ _IRREGULAR_PAST_PARTICIPLE: dict[str, str] = {v: forms[1] for v, forms in _IRREG
|
|||
_IES_KEEP_IE: frozenset[str] = frozenset({"dies", "lies", "ties", "vies", "pies", "hies"})
|
||||
|
||||
|
||||
#: Stem endings that take ``-es`` for the 3sg rather than a bare ``-s``.
|
||||
#:
|
||||
#: ``ss`` and not ``s``: the sibilant rule fires on a DOUBLED s ("passes" ->
|
||||
#: "pass"), while a stem ending in a single ``s`` is almost always a stem
|
||||
#: ending in ``e`` that took a plain ``-s`` ("causes" -> "cause"). Before this
|
||||
#: distinction, ``base_form("causes")`` returned **"caus"**, which is the
|
||||
#: single-verb half of the 9-of-26 agreement defect.
|
||||
_ES_STEM_ENDINGS = ("ss", "sh", "ch", "x", "z", "o")
|
||||
|
||||
|
||||
def _base_form(verb_3sg: str) -> str:
|
||||
if verb_3sg in _IES_KEEP_IE:
|
||||
return verb_3sg[:-1]
|
||||
if verb_3sg.endswith("ies"):
|
||||
return verb_3sg[:-3] + "y"
|
||||
if verb_3sg.endswith("es"):
|
||||
return verb_3sg[:-2] if verb_3sg[:-2].endswith(("s", "sh", "ch", "x", "z", "o")) else verb_3sg[:-1]
|
||||
return verb_3sg[:-2] if verb_3sg[:-2].endswith(_ES_STEM_ENDINGS) else verb_3sg[:-1]
|
||||
if verb_3sg.endswith("s"):
|
||||
return verb_3sg[:-1]
|
||||
return verb_3sg
|
||||
|
||||
|
||||
#: 3sg present -> plural present, for the forms where the plural is not simply
|
||||
#: the bare stem. Everything else pluralizes by dropping the ``-s``.
|
||||
_PLURAL_PRESENT: dict[str, str] = {
|
||||
"is": "are", "are": "are",
|
||||
"has": "have", "have": "have",
|
||||
"does": "do", "do": "do",
|
||||
"was": "were", "were": "were",
|
||||
}
|
||||
|
||||
|
||||
def plural_present(verb_3sg: str) -> str:
|
||||
"""A third-person-singular verb → its plural-subject present form."""
|
||||
if verb_3sg in _PLURAL_PRESENT:
|
||||
return _PLURAL_PRESENT[verb_3sg]
|
||||
return _base_form(verb_3sg)
|
||||
|
||||
|
||||
def agree_plural_phrase(phrase: str) -> str:
|
||||
"""Put a whole predicate PHRASE into plural agreement.
|
||||
|
||||
Number is marked on the finite verb, which is the first token of every
|
||||
humanized predicate ("is defined as", "has the following steps",
|
||||
"belongs to", "contrasts with"). Only that token inflects; the rest is
|
||||
carried through untouched.
|
||||
|
||||
This exists because :func:`base_form` is a SINGLE-VERB function and was
|
||||
being applied to whole phrases, stripping the last character-class of the
|
||||
final word: "is defined as" -> "is defined a", "has the following steps"
|
||||
-> "has the following step". Nine of the 26 seed predicates were wrong
|
||||
that way, and every multi-word one was.
|
||||
"""
|
||||
if not phrase:
|
||||
return phrase
|
||||
head, sep, rest = phrase.partition(" ")
|
||||
return plural_present(head) + sep + rest
|
||||
|
||||
|
||||
def past_tense(verb_3sg: str) -> str:
|
||||
if verb_3sg in _IRREGULAR_PAST:
|
||||
return _IRREGULAR_PAST[verb_3sg]
|
||||
|
|
@ -272,10 +320,12 @@ def pluralize(noun: str) -> str:
|
|||
return prefix + head + "es"
|
||||
if head.endswith("y") and len(head) > 1 and head[-2] not in "aeiou":
|
||||
return prefix + head[:-1] + "ies"
|
||||
if head.endswith("fe"):
|
||||
return prefix + head[:-2] + "ves"
|
||||
if head.endswith("f"):
|
||||
return prefix + head[:-1] + "ves"
|
||||
# f/fe -> ves is a CLOSED set, not a productive rule: proof->proofs and
|
||||
# chief->chiefs, but wolf->wolves. The set is derived from the number
|
||||
# table's own ves-rows, so the rule can never claim a word the table does
|
||||
# not know. Without this, pluralize("proof") returned "prooves".
|
||||
if head in VES_PLURAL_SINGULARS:
|
||||
return prefix + (head[:-2] if head.endswith("fe") else head[:-1]) + "ves"
|
||||
return prefix + head + "s"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from generate.articulation_legality import (
|
|||
)
|
||||
from generate.graph_planner import RhetoricalMove
|
||||
from generate.morphology import (
|
||||
agree_plural_phrase,
|
||||
base_form,
|
||||
is_mass_noun,
|
||||
past_participle,
|
||||
|
|
@ -102,7 +103,17 @@ def _inflect_predicate(
|
|||
case (_, "future", False, _):
|
||||
return f"will {base}"
|
||||
case (_, _, True, True):
|
||||
return f"do not {base}"
|
||||
# Plural + negated. Agree the head first, then negate around it:
|
||||
# a plural copula takes a bare "not" ("are not defined as"), while
|
||||
# any other verb needs do-support ("do not have the following
|
||||
# steps", "do not belong to"). Previously this was
|
||||
# ``f"do not {base}"`` over the whole phrase, which produced
|
||||
# "do not is defined a".
|
||||
agreed = agree_plural_phrase(predicate_h)
|
||||
head, sep, rest = agreed.partition(" ")
|
||||
if head in ("are", "were"):
|
||||
return f"{head} not{sep}{rest}" if rest else f"{head} not"
|
||||
return f"do not {agreed}"
|
||||
case (_, _, True, False) if copular:
|
||||
if predicate_h.startswith("is "):
|
||||
return "is not " + predicate_h[3:]
|
||||
|
|
@ -118,7 +129,11 @@ def _inflect_predicate(
|
|||
case (_, _, True, False):
|
||||
return f"does not {base}"
|
||||
case (_, _, False, True):
|
||||
return base
|
||||
# Plural agreement on the whole phrase, not base_form() of it.
|
||||
# This is the branch the 9-of-26 defect lived in: it returned the
|
||||
# bare base and never consulted ``copular``, so "is defined as"
|
||||
# came back "is defined a" instead of "are defined as".
|
||||
return agree_plural_phrase(predicate_h)
|
||||
case _:
|
||||
return verb
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,12 @@ def test_g_roundtrip_baseline_is_zero(report):
|
|||
revised **upward**; it must never be revised downward to accommodate a
|
||||
regression.
|
||||
"""
|
||||
assert report.metrics["graph_cases"] == 280
|
||||
# 293, not the original 280: Phase 3 added 13 quantified-copular cases
|
||||
# (construction C14) to grammatical_coverage/public/v1, and this lane
|
||||
# harvests its graph corpus from the committed case files rather than
|
||||
# holding its own copy. An exact count is the point — a corpus that grows
|
||||
# or shrinks should require a deliberate edit here, not pass silently.
|
||||
assert report.metrics["graph_cases"] == 293
|
||||
assert report.metrics["g_write_rate"] == 1.0
|
||||
assert report.metrics["g_read_rate"] == 0.0
|
||||
assert report.metrics["g_exact_rate"] == 0.0
|
||||
|
|
@ -303,5 +308,5 @@ def test_positive_surfaces_are_all_inside_the_reader_envelope():
|
|||
|
||||
def test_graph_corpus_is_harvested_from_committed_case_files():
|
||||
cases = positive_graph_cases()
|
||||
assert len(cases) == 280
|
||||
assert len(cases) == 293 # +13 C14 quantified-copular (Phase 3)
|
||||
assert all(c.nodes for c in cases)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ RECORDED_CONSUMERS: dict[str, frozenset[str]] = {
|
|||
}),
|
||||
"INVARIANT_NUMBER": frozenset({"generate.morphology"}),
|
||||
"MASS_NOUNS": frozenset({"generate.morphology"}),
|
||||
# Phase 3: the closed f/fe -> ves set, derived from the singularizer's own
|
||||
# ves-rows so the rule cannot claim a word the table does not know.
|
||||
"VES_PLURAL_SINGULARS": frozenset({"generate.morphology"}),
|
||||
}
|
||||
|
||||
#: The tables a duplicate literal would be a duplicate *of*. Frozen as
|
||||
|
|
|
|||
|
|
@ -106,3 +106,114 @@ def test_is_mass_noun_known_set() -> None:
|
|||
assert is_mass_noun("water")
|
||||
assert not is_mass_noun("molecule")
|
||||
assert not is_mass_noun("atom")
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Phase 3 — the 9-of-26 agreement defect
|
||||
#
|
||||
# ``_inflect_predicate`` applied ``base_form`` — a SINGLE-VERB function — to
|
||||
# whole humanized predicate phrases, stripping the last character-class of the
|
||||
# final word, and its plural branch never consulted ``copular``. Nine of the 26
|
||||
# seed predicates came out wrong, and every multi-word one did.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
#: Hand-written English, NOT derived from the code under test. Deriving it would
|
||||
#: make it agree by construction and measure nothing.
|
||||
PLURAL_AGREEMENT_ORACLE = {
|
||||
"addresses": "address",
|
||||
"answers": "answer",
|
||||
"belongs to": "belong to",
|
||||
"causes": "cause",
|
||||
"contrasts with": "contrast with",
|
||||
"corrects": "correct",
|
||||
"defines": "define",
|
||||
"entails": "entail",
|
||||
"evidences": "evidence",
|
||||
"follows": "follow",
|
||||
"grounds": "ground",
|
||||
"has the following steps": "have the following steps",
|
||||
"implies": "imply",
|
||||
"is caused by": "are caused by",
|
||||
"is defined as": "are defined as",
|
||||
"is distinguished from": "are distinguished from",
|
||||
"is grounded in": "are grounded in",
|
||||
"is verified as": "are verified as",
|
||||
"means": "mean",
|
||||
"orders": "order",
|
||||
"precedes": "precede",
|
||||
"recalls": "recall",
|
||||
"requires": "require",
|
||||
"reveals": "reveal",
|
||||
"supports": "support",
|
||||
"verifies": "verify",
|
||||
}
|
||||
|
||||
|
||||
def test_the_oracle_covers_every_seed_predicate() -> None:
|
||||
"""A partial oracle would let a predicate regress unseen."""
|
||||
from generate.lexicon import PREDICATE_DISPLAY
|
||||
|
||||
assert set(PLURAL_AGREEMENT_ORACLE) == set(PREDICATE_DISPLAY.values())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("display", "expected"), sorted(PLURAL_AGREEMENT_ORACLE.items())
|
||||
)
|
||||
def test_plural_subject_agreement_matches_english(display: str, expected: str) -> None:
|
||||
"""26/26 against hand-written English (was 17/26).
|
||||
|
||||
The nine that were wrong: belongs to, causes ("caus"), contrasts with,
|
||||
has the following steps ("has the following step"), and the five
|
||||
``is …`` copulars ("is defined as" -> "is defined a").
|
||||
"""
|
||||
from generate.templates import _inflect_predicate
|
||||
|
||||
assert _inflect_predicate(display, plural_subject=True) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("display", "expected"),
|
||||
[
|
||||
("is defined as", "are not defined as"),
|
||||
("is grounded in", "are not grounded in"),
|
||||
("has the following steps", "do not have the following steps"),
|
||||
("belongs to", "do not belong to"),
|
||||
("supports", "do not support"),
|
||||
],
|
||||
)
|
||||
def test_plural_negation_agrees_then_negates(display: str, expected: str) -> None:
|
||||
"""Plural + negated: a plural copula takes a bare ``not``; anything else
|
||||
needs do-support. Previously ``do not {base_form(phrase)}`` produced
|
||||
"do not is defined a"."""
|
||||
from generate.templates import _inflect_predicate
|
||||
|
||||
assert _inflect_predicate(display, negated=True, plural_subject=True) == expected
|
||||
|
||||
|
||||
def test_base_form_is_a_single_verb_function_and_says_so() -> None:
|
||||
"""The root cause, pinned directly: ``base_form`` operates on ONE verb.
|
||||
|
||||
It is still wrong on a phrase — that is inherent, not a bug to fix here —
|
||||
which is exactly why the plural branch calls ``agree_plural_phrase``
|
||||
instead. Pinning it stops someone "fixing" the symptom in the wrong place.
|
||||
"""
|
||||
from generate.morphology import agree_plural_phrase, base_form
|
||||
|
||||
assert base_form("causes") == "cause" # was "caus"
|
||||
assert base_form("passes") == "pass" # the sibilant rule still fires
|
||||
assert base_form("is defined as") == "is defined a" # single-verb fn on a phrase
|
||||
assert agree_plural_phrase("is defined as") == "are defined as"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("singular", "plural"),
|
||||
[("proof", "proofs"), ("chief", "chiefs"), ("roof", "roofs"),
|
||||
("belief", "beliefs"), ("wolf", "wolves"), ("knife", "knives")],
|
||||
)
|
||||
def test_f_to_ves_is_a_closed_set_not_a_rule(singular: str, plural: str) -> None:
|
||||
"""``f`` -> ``ves`` is not productive in English. Applying it as a suffix
|
||||
rule made ``pluralize("proof")`` return "prooves", and Phase 2B had put
|
||||
``pluralize`` on the serving path."""
|
||||
from generate.morphology import pluralize as _pluralize
|
||||
|
||||
assert _pluralize(singular) == plural
|
||||
|
|
|
|||
Loading…
Reference in a new issue