Files
moral-maps/tests/test_instrument.py
wassnameandClaudypoo 469788c418 rename package tinymfv -> moralmaps (repo -> moral-maps)
Import name tinymfv -> moralmaps, pip name tiny-mfv -> moral-maps, GitHub
URLs wassname/tinymfv -> wassname/moral-maps. HuggingFace dataset id
wassname/tiny-mfv left as-is (separate namespace, published data artifact).
Historical docs/spec/* and RESEARCH_JOURNAL keep their dated paths.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
2026-07-09 10:41:49 +08:00

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 moralmaps.data import CONFIGS, CONDITIONS, load_vignettes
from moralmaps.instrument import (
Instrument,
canonicalize_to_forward,
per_item_categorical,
reduce_nominal,
reduce_ordinal,
)
from moralmaps.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()