Files
moral-maps/tests/test_instrument.py
T

97 lines
3.9 KiB
Python

"""Minimal functional tests for the pure instrument layer.
The model smoke test is the real integration gate. These tests cover the pure pieces the smoke test
does not isolate cleanly: nominal vs ordinal reducers, frame canonicalization/keying, readout names,
and packaged data integrity.
"""
from __future__ import annotations
import unittest
import numpy as np
from tinymfv.data import CONFIGS, CONDITIONS, load_vignettes
from tinymfv.instrument import (
Instrument,
canonicalize_to_forward,
per_item_categorical,
reduce_nominal,
reduce_ordinal,
)
from tinymfv.readouts import expected_score, logit_contrast, logodds_agree
class InstrumentFlowTest(unittest.TestCase):
def test_nominal_flow_reduces_answer_categories_to_profile(self) -> None:
instr = Instrument(
"mfv", "salience", "nominal",
["care", "fairness", "social"],
["Care", "Fairness", "SocialNorms"],
items=[],
prefill='This is wrong because {"violation": "',
answer_to_dim={"care": "Care", "fairness": "Fairness", "social": "SocialNorms"},
)
rows = [
{"id": "a", "frame": "forward", "lp": np.log([0.80, 0.15, 0.05]),
"p": np.array([0.80, 0.15, 0.05]), "pmass_allowed": 0.95,
"dimension": None, "sign": 1, "human_label": None},
{"id": "b", "frame": "forward", "lp": np.log([0.20, 0.50, 0.30]),
"p": np.array([0.20, 0.50, 0.30]), "pmass_allowed": 0.90,
"dimension": None, "sign": 1, "human_label": None},
]
items = per_item_categorical(rows, instr.kind)
self.assertTrue(np.allclose(reduce_nominal(items, instr), [0.50, 0.325, 0.175]))
self.assertAlmostEqual(items["a"]["pmass"], 0.95)
self.assertEqual(items["a"]["n_frames"], 1)
def test_ordinal_flow_canonicalizes_frames_then_keys_profile(self) -> None:
instr = Instrument(
"likert", "endorsement", "ordinal",
["1", "2", "3", "4", "5"],
["care", "authority"],
items=[],
prefill="(",
)
onehot = {d: np.eye(5)[d - 1] for d in range(1, 6)}
rows = [
{"id": "care", "frame": "forward", "lp": np.log(onehot[4] + 1e-9),
"p": onehot[4], "pmass_allowed": 1.0,
"dimension": "care", "sign": 1, "human_label": None},
{"id": "authority", "frame": "inverted", "lp": np.log(onehot[5] + 1e-9),
"p": onehot[5], "pmass_allowed": 1.0,
"dimension": "authority", "sign": -1, "human_label": None},
]
items = per_item_categorical(rows, instr.kind)
profile = reduce_ordinal(items, instr)
self.assertTrue(np.array_equal(canonicalize_to_forward(onehot[5], "inverted", "ordinal"), onehot[1]))
self.assertAlmostEqual(expected_score(items["care"]["p"], 5), 4.0)
self.assertAlmostEqual(profile[0], 4.0)
self.assertAlmostEqual(profile[1], 5.0)
self.assertAlmostEqual(
logit_contrast(items["care"]["lp"] + 100.0, 5),
logit_contrast(items["care"]["lp"], 5),
)
self.assertTrue(np.isfinite(logodds_agree(items["care"]["lp"], 5)))
def test_packaged_vignettes_have_aligned_conditions_and_labels(self) -> None:
required_human = {
"human_Care", "human_Fairness", "human_Loyalty", "human_Authority",
"human_Sanctity", "human_Liberty", "human_SocialNorms",
}
for cfg in CONFIGS:
rows = load_vignettes(cfg)
self.assertGreaterEqual(len(rows), 100)
self.assertEqual({r["set"] for r in rows}, {cfg})
for row in rows:
self.assertTrue({"id", "foundation", "foundation_coarse", *CONDITIONS} <= set(row))
self.assertTrue(required_human <= set(row))
self.assertGreater(sum(float(row[k]) for k in required_human), 0.0)
if __name__ == "__main__":
unittest.main()