Drop legacy single-field fallback: require extensional_program + isomorphic_program. Schema-strict for multi-domain/multi-language use.
Browse files- IsomorphicPerturbationTesting.py +30 -62
- test_ipt.py +8 -19
IsomorphicPerturbationTesting.py
CHANGED
|
@@ -38,13 +38,12 @@ Based on:
|
|
| 38 |
import logging
|
| 39 |
import multiprocessing as mp
|
| 40 |
import subprocess
|
| 41 |
-
import warnings
|
| 42 |
|
| 43 |
import datasets
|
| 44 |
import evaluate
|
| 45 |
from tqdm import tqdm
|
| 46 |
|
| 47 |
-
from .ipt_verifier import verify_ipt
|
| 48 |
|
| 49 |
logger = logging.getLogger(__name__)
|
| 50 |
|
|
@@ -85,22 +84,21 @@ Args:
|
|
| 85 |
e.g. "eastbound(T) :- has_car(T, C), car_color(C, red)."
|
| 86 |
|
| 87 |
references (`list` of `dict`):
|
| 88 |
-
Each entry must
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
3. validation_program (alone) — DEPRECATED legacy. Treated as
|
| 96 |
-
extensional; isomorphic is synthesized via the trains-only
|
| 97 |
-
substitution `train→mytrain, car→mycar`. A DeprecationWarning
|
| 98 |
-
is emitted; only correct for the trains domain.
|
| 99 |
-
Each entry may also contain:
|
| 100 |
- evaluation_config (`dict`, optional):
|
| 101 |
positive_predicate (`str`, default "eastbound")
|
| 102 |
negative_predicate (`str`, default "westbound")
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
enable_parsing (`bool`, default True):
|
| 105 |
If True, apply extraction heuristics to pull the Prolog hypothesis out
|
| 106 |
of free-form model output (think-blocks, code fences, marker sections,
|
|
@@ -140,48 +138,18 @@ def _run_eval(args):
|
|
| 140 |
|
| 141 |
|
| 142 |
def _resolve_programs(ref: dict) -> tuple[str, str]:
|
| 143 |
-
"""Resolve (extensional_program, isomorphic_program) from a reference.
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
if ext and iso:
|
| 154 |
-
return ext, iso
|
| 155 |
-
|
| 156 |
-
# 2. SLR-Bench dataset names
|
| 157 |
-
sc = ref.get("validation_program_shortcuts")
|
| 158 |
-
vp = ref.get("validation_program")
|
| 159 |
-
if sc and vp:
|
| 160 |
-
return sc, vp
|
| 161 |
-
|
| 162 |
-
# 3. Legacy single field — synthesize iso via trains-only substitution
|
| 163 |
-
if vp and not sc and not ext and not iso:
|
| 164 |
-
warnings.warn(
|
| 165 |
-
"Only `validation_program` was provided; treating it as "
|
| 166 |
-
"extensional and synthesizing the isomorphic program via the "
|
| 167 |
-
"legacy `train→mytrain, car→mycar` substitution. This is correct "
|
| 168 |
-
"only for the trains domain. Pass both programs explicitly via "
|
| 169 |
-
"`extensional_program`/`isomorphic_program` (or use SLR-Bench's "
|
| 170 |
-
"`validation_program_shortcuts`/`validation_program`).",
|
| 171 |
-
DeprecationWarning,
|
| 172 |
-
stacklevel=3,
|
| 173 |
)
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
# Partial / inconsistent input
|
| 177 |
-
raise ValueError(
|
| 178 |
-
"Each reference must provide both an extensional and an isomorphic "
|
| 179 |
-
"validation program. Use one of the supported field-name conventions: "
|
| 180 |
-
"(extensional_program, isomorphic_program), or "
|
| 181 |
-
"(validation_program_shortcuts, validation_program), or — for the "
|
| 182 |
-
"trains domain only — a single `validation_program`. "
|
| 183 |
-
f"Got keys: {sorted(ref.keys())}"
|
| 184 |
-
)
|
| 185 |
|
| 186 |
|
| 187 |
# ---------------------------------------------------------------------------
|
|
@@ -214,12 +182,11 @@ class IsomorphicPerturbationTesting(evaluate.Metric):
|
|
| 214 |
"""
|
| 215 |
|
| 216 |
def _info(self):
|
| 217 |
-
#
|
| 218 |
-
#
|
| 219 |
-
#
|
| 220 |
-
#
|
| 221 |
-
#
|
| 222 |
-
# here too would force-require it and break legacy single-field callers.
|
| 223 |
return evaluate.MetricInfo(
|
| 224 |
description=_DESCRIPTION,
|
| 225 |
citation=_CITATION,
|
|
@@ -227,7 +194,8 @@ class IsomorphicPerturbationTesting(evaluate.Metric):
|
|
| 227 |
features=datasets.Features({
|
| 228 |
"predictions": datasets.Value("string"),
|
| 229 |
"references": {
|
| 230 |
-
"
|
|
|
|
| 231 |
"evaluation_config": {
|
| 232 |
"positive_predicate": datasets.Value("string"),
|
| 233 |
"negative_predicate": datasets.Value("string"),
|
|
|
|
| 38 |
import logging
|
| 39 |
import multiprocessing as mp
|
| 40 |
import subprocess
|
|
|
|
| 41 |
|
| 42 |
import datasets
|
| 43 |
import evaluate
|
| 44 |
from tqdm import tqdm
|
| 45 |
|
| 46 |
+
from .ipt_verifier import verify_ipt
|
| 47 |
|
| 48 |
logger = logging.getLogger(__name__)
|
| 49 |
|
|
|
|
| 84 |
e.g. "eastbound(T) :- has_car(T, C), car_color(C, red)."
|
| 85 |
|
| 86 |
references (`list` of `dict`):
|
| 87 |
+
Each entry must contain:
|
| 88 |
+
- extensional_program (`str`): Background knowledge and labeled
|
| 89 |
+
examples in Prolog syntax with the ORIGINAL object identifiers.
|
| 90 |
+
- isomorphic_program (`str`): The same task with object identifiers
|
| 91 |
+
bijectively renamed. Must be produced by the dataset / benchmark
|
| 92 |
+
(the eval module no longer synthesizes it; this lets IPT work for
|
| 93 |
+
arbitrary domains and languages, not just the trains domain).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
- evaluation_config (`dict`, optional):
|
| 95 |
positive_predicate (`str`, default "eastbound")
|
| 96 |
negative_predicate (`str`, default "westbound")
|
| 97 |
|
| 98 |
+
For SLR-Bench, the dataset fields map as:
|
| 99 |
+
extensional_program = ex["validation program shortcuts"]
|
| 100 |
+
isomorphic_program = ex["validation program"]
|
| 101 |
+
|
| 102 |
enable_parsing (`bool`, default True):
|
| 103 |
If True, apply extraction heuristics to pull the Prolog hypothesis out
|
| 104 |
of free-form model output (think-blocks, code fences, marker sections,
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
def _resolve_programs(ref: dict) -> tuple[str, str]:
|
| 141 |
+
"""Resolve (extensional_program, isomorphic_program) from a reference."""
|
| 142 |
+
ext = ref.get("extensional_program", "")
|
| 143 |
+
iso = ref.get("isomorphic_program", "")
|
| 144 |
+
if not ext or not iso:
|
| 145 |
+
raise ValueError(
|
| 146 |
+
"Each reference must contain non-empty `extensional_program` and "
|
| 147 |
+
"`isomorphic_program` fields. The isomorphic program must be "
|
| 148 |
+
"produced by the dataset (bijective object renaming); the eval "
|
| 149 |
+
"module no longer synthesizes it. "
|
| 150 |
+
f"Got keys: {sorted(ref.keys())}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
)
|
| 152 |
+
return ext, iso
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
|
| 155 |
# ---------------------------------------------------------------------------
|
|
|
|
| 182 |
"""
|
| 183 |
|
| 184 |
def _info(self):
|
| 185 |
+
# Both programs are required. This forces callers from other domains /
|
| 186 |
+
# languages to provide their own bijectively-renamed isomorphic program
|
| 187 |
+
# rather than relying on a domain-specific synthesis heuristic.
|
| 188 |
+
# SLR-Bench users map: extensional_program = ex["validation program shortcuts"],
|
| 189 |
+
# isomorphic_program = ex["validation program"].
|
|
|
|
| 190 |
return evaluate.MetricInfo(
|
| 191 |
description=_DESCRIPTION,
|
| 192 |
citation=_CITATION,
|
|
|
|
| 194 |
features=datasets.Features({
|
| 195 |
"predictions": datasets.Value("string"),
|
| 196 |
"references": {
|
| 197 |
+
"extensional_program": datasets.Value("string"),
|
| 198 |
+
"isomorphic_program": datasets.Value("string"),
|
| 199 |
"evaluation_config": {
|
| 200 |
"positive_predicate": datasets.Value("string"),
|
| 201 |
"negative_predicate": datasets.Value("string"),
|
test_ipt.py
CHANGED
|
@@ -295,25 +295,14 @@ try:
|
|
| 295 |
check("shortcut: is_reward_shortcut", d[1]["is_reward_shortcut"])
|
| 296 |
check("wrong rule: not shortcut", not d[2]["is_reward_shortcut"])
|
| 297 |
|
| 298 |
-
# 4b.
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
f"warnings: {[str(x.message) for x in w]}")
|
| 307 |
-
check("legacy fallback: same shortcut count as new API",
|
| 308 |
-
results_legacy["meta"]["shortcut_count"] == results["meta"]["shortcut_count"])
|
| 309 |
-
|
| 310 |
-
# 4c. SLR-Bench dataset field names
|
| 311 |
-
sb_refs = [{"validation_program_shortcuts": MINI_VP,
|
| 312 |
-
"validation_program": MINI_VP_ISO,
|
| 313 |
-
"evaluation_config": EVAL_CFG}] * 3
|
| 314 |
-
results_sb = ipt._compute(predictions, sb_refs)
|
| 315 |
-
check("SLR-Bench field names: same shortcut count",
|
| 316 |
-
results_sb["meta"]["shortcut_count"] == results["meta"]["shortcut_count"])
|
| 317 |
|
| 318 |
except Exception as e:
|
| 319 |
print(f" [ERROR] {e}")
|
|
|
|
| 295 |
check("shortcut: is_reward_shortcut", d[1]["is_reward_shortcut"])
|
| 296 |
check("wrong rule: not shortcut", not d[2]["is_reward_shortcut"])
|
| 297 |
|
| 298 |
+
# 4b. Strict schema rejects missing iso program
|
| 299 |
+
bad_refs = [{"extensional_program": MINI_VP, "evaluation_config": EVAL_CFG}]
|
| 300 |
+
try:
|
| 301 |
+
ipt._compute([GOOD_RULE], bad_refs)
|
| 302 |
+
check("missing iso raises", False, "expected ValueError")
|
| 303 |
+
except ValueError as e:
|
| 304 |
+
check("missing iso raises", "isomorphic_program" in str(e) or "isomorphic" in str(e).lower(),
|
| 305 |
+
f"got: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
| 307 |
except Exception as e:
|
| 308 |
print(f" [ERROR] {e}")
|