The MFV foundation readout is centered log-ratio (clr), not a logit. Renames the
showcase JSON/CSV consumers' keys (dclr/dclr_sem) and the shared reader-space-shift
column to match steering-lite's writers; the survey 'C = logit contrast' readout
keeps its logit naming.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Draw the title and github.com/wassname/moral-maps note in the empty bottom-left
INSIDE the axes so a crop can't strip attribution and there's no whitespace band.
Give the models overlay extra top headroom so the most-secular model stars and
their labels seat inside the frame instead of spilling above it. Regenerate PNG+SVG.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
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>
MFV country norms fail cross-country measurement invariance (Jimenez-Leal 2025: non-invariance + DIF,
'cross-cultural comparisons restricted') and are stitched from 5 studies, so the culture map/quadrant
drew false structure (inverted West vs Latin America). Delete plot_mfv_map + plot_mfv_value and the
value_coords_contrast/axis_contrast helpers; MFV keeps only the range plot, now against ONE pooled
human reference (mean of the 8 z-scored samples), no per-country identity. Add data-dir note + README
caveats citing the sources.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
One shared orient_geographic() helper folds axis signs into the coords (value maps) or into Vt
(ipsative PCA, so dots/haze/steer/compass all inherit one flip). Replaces the WVS-only invert_x hack;
every map now reads left/right the same way. Regenerate instrument showcase maps.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Renders the dream artifact: a 5x5 grid of MFV profile bar charts (one per
(hc, cc) cell), per-foundation dlogit heatmaps for MFV, per-factor C heatmaps
for ordinal instruments, and a pmass coherence heatmap. Reads the 2D-grid
profiles CSVs written by steering-lite's run_2d_grid_showcase.py.
The 17-model IW map now renders through labelplace.allocate_labels; save the artifact
into the repo (png for viewing, svg for hand-editing labels, CI table as companion).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the fling-to-open-space-with-a-leader zone placement with _hull_label_pos:
walk the hull's own boundary vertices, nudge each slightly outward, and put the
label on the vertex whose nearest dot/label is farthest -- so the name sits against
an uncrowded arc of its own outline, no leader line. And flip X (invert_x) so
Self-expression is on the left and Survival on the right: this is a better map than
the Economist's, and it puts the cultural West on the left / East Asia on the right.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Zone labels now get a dedicated open-space search (_open_slot: scan a ring around
the hull centroid, keep the slot whose nearest dot/label is farthest) instead of
adjustText's local minimum -- so Latin America/African-Islamic land in the sparse
margins with a leader to their hull, not the crowded centre. And with 17 models the
names were too many: plot every star but LABEL only the latest per family (highest
version), dropping the redundant 'claude-' so the flagship reads 'opus-4.8'. Colour
+ legend carry the unlabelled siblings.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Computed from raw participant ratings in dat_rep.sav (OSF cmwpv, full 90-item
Clifford MFV, 1-5), using the author's own item->foundation map from the R
notebook. Complete-case N=756 reproduces the paper's abstract exactly. The
paper's headline is a genetic-algorithm-abbreviated MFV; we deliberately used
the FULL 90 items so Australia is comparable to the other full-instrument rows.
The paper's other sample (580 US MTurk) is skipped -- it would duplicate US.
Cloud now 7 -> 8. Fresh-eyes subagent reproduced all six means to <=0.0005.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Models are now stars coloured by lab family instead of one Economist red, hue
chosen to echo the lab's home region: Chinese labs warm (qwen orange, deepseek
pink) near the East-Asia red, US labs cool (claude purple, gpt blue, gemini/gemma
sea blue, grok indigo, llama steel), Europe green (mistral). Legend keys each
family. Also drop the ' (rated)' tag from on-map labels and Nigeria from the
always-on landmarks (Egypt already anchors the African-Islamic corner).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
With a dozen+ models the 95% CI crosses overlap into noise. Remove the whiskers
from plot_value_map (clean red dots) and instead write a widest-first CI table
(wvs_model_ci.md) next to the figure. The table also documents that the wide CIs
are item-disagreement (a model rating different WVS items inconsistently on an
axis), which more samples will not shrink -- not sampling noise.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Clifford MFV wrongness vignettes, 1-5 scale (same as existing LatAm/Japan
rows). Care collapsed from their split physical(4.09)+emotional(3.53) to a
single 3.81; other foundations from their Table 1 with paper 95% CIs.
Grows the human country cloud 5 -> 6.
Note: the Dutch and LatAm authors both flag MFV measurement non-invariance
(DIF) across countries, so between-country means are a rough reference, not
a calibrated ranking. Map is ipsative (within-country z), which is affine-
invariant to the scale, so it reads relative foundation emphasis.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the single forced-choice sampling reader (1 bit/call, truncated verbose
models to empty at max_tokens=32, positionally biased) with a dense readout:
rate every option 1-5 as JSON, N samples, binary items order-balanced to cancel
positional bias, ratings mapped back to canonical order and normalized to a
distribution. All of a model's calls fire concurrently (asyncio.gather over the
async openrouter_wrapper; per_call=1 since providers ignore n>1). Each model
carries a bootstrap 95% CI (over items + samples) drawn as error bars, so mushy
models (deepseek-flash was near coin-flip) read as uncertain, not confident dots.
One flaky provider is skipped, not fatal.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
_zscore now asserts nonzero variance (a flat profile drew a degenerate all-zero
map that looked valid) instead of dividing by std+1e-9. _soft_nll docstring now
states the 1e-12 floor it actually applies (was 'Unbounded'). Trim archaeology
comments (guided/wvs/read_api) to the invariant, dropping the dated model names.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the hand-rolled OpenAI client with openrouter_wrapper.openrouter_request,
which backs off on 429/provider/upstream/malformed errors (a rate-limited model no
longer kills the run), so retry logic doesn't diverge from the rest of the stack.
read_items_sampled now carries the raw sample texts; wvs_map appends every response
to wvs_iw_responses.jsonl (audit trail -- these API calls cost money).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Economist encoding: every model is one bold red dot (bigger than the grey society
dots), told apart by its label, with a two-entry colour legend (AI models / N
societies). Title and caption are now off by default -- the README carries the
headline + sources in a nicer voice than baked figure jargon. WVS map passes
neither.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
plot_ordinal now saves map_value.{png,svg} (societies + AI base/steered projected
onto the instrument's two named value axes) next to map_pca_ipsative. Untested
end-to-end (needs a run_allinstr_showcase profile set); the shared plot_value_map
renderer it calls is verified on human-only renders.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The WVS map's ~70 lines of pole-signpost/hull/textalloc rendering were a copy of
what plot_value_map now does -- call the shared renderer instead (WVS + instruments
one code path). big5 value-axis poles named at both ends (Reserved<->Exploratory,
Volatile<->Stable) so every value map reads as a bipolar contrast, not a unipolar
low<->high.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The cache was written once at the very end, so a killed run (session teardown)
discarded every sampled model. Persist after each model instead, and expand the
model-star palette to 14 so a large model panel doesn't silently drop stars past
the 6th via zip truncation.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the model legend with textalloc-placed on-map labels (leader lines, coloured
to each star), and route country labels through the same textalloc pass so Pakistan/
Nigeria/Egypt no longer collide. outlying_countries now returns the corner-most
society in each diagonal direction (top-right/bottom-left/...) instead of the n
farthest from the centroid, which bunched on one side and left the bottom-left corner
(Egypt) unlabelled.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The E-vector cache keyed on Python's builtin hash(), which is salted per process
(PYTHONHASHSEED), so the signature changed every run and the cache never hit across
processes -- silently re-spending on the OpenRouter API each time. Switch to a
stable hashlib key. LLM stars now go in a lower-right legend instead of colliding
inline labels (they cluster in one corner, which IS the result).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Economist style: drop the axis ticks, move the neutral crosshair from an arbitrary
0.5 to the human-society median on each axis, and anchor each pole signpost's arrow
to its median crosshair (blended data/axes transform) so the four directions read
against the typical society. Pole labels all horizontal.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace hardcoded zone lists with geometric rules that work on any map:
- maps.select_spread_zones: greedy max-coverage on hull areas -- seed the
largest zone, add whichever contributes the most new non-overlapping area.
Drops central/covered zones (Orthodox) and keeps the corner cultures.
- maps.outlying_countries: the n countries farthest from the centroid, unioned
with named landmarks + one representative (most-central member) per drawn zone
so every region has at least one identifiable label.
- draw_zone_hulls: edge-only coloured outline (no fill), contour only for 2+
member groups, label anchored to the hull's top vertex.
WVS map: four arrowed pole signposts (Traditional/Secular-Rational/Survival/
Self-expression) in a padded inner margin so they don't collide with title/ticks;
model stars use a palette disjoint from the zone colours.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace the blind ipsative-PCA projection with two named IW axes built from
GlobalOpinionQA WVS items (tinymfv.iw_axes): X = Survival<->Self-expression
(homosexuality, trust, political action), Y = Traditional<->Secular-Rational
(religion importance+belief, abortion, child autonomy). Each item is oriented to
its axis-positive pole by reading the option order, so a reversed row can't flip a
country. Human anchors land where the published IW map puts them (Sweden top-right,
Nigeria/Pakistan bottom-left, East Asia secular-but-survival top-left).
Models answer the same items via the answer-token reader (single-digit option labels
so the 1-10 justifiable scale stays single-token); coords are the same axis-mean.
Add maps.draw_zone_hulls: tight rounded convex hulls (not inflated disc unions),
zone-coloured dots, outlier-only labels, white-haloed region names -- the Economist
grammar. draw_zone_regions stays for the instrument maps' within-country spread.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Cache model E-vectors keyed by the exact question block, so iterating on the zone
style re-projects + re-draws without re-hitting the API (~20 min of calls). A
question a model didn't answer coherently (NaN) is imputed with the human average
at projection so one collapse doesn't drop the whole model dot.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Replace stacked translucent discs (which darkened at overlaps) with a shapely
union per zone -> one merged shape, uniform alpha. Each country's territory is now
its REAL spread ellipse from its respondent/haze cloud (natural varying shape), not
a fixed disc; sizes are rescaled so the median country ~ a legible radius (raw
within-country spread >> between-country, would fill the plot). Every country sits
in its zone, 2-country zones have area. MFV/WVS (country means only) fall back to a
fixed disc. 16pf turned off (unreadable even at 6 macro zones). Adds shapely to the
maps extra.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
wvs_map.py runs tinymfv on the GlobalOpinionQA WVS 4-option questions: greedy
complete country x question block (60 x 74, no imputation), ipsative-PCA to 2 axes
with IW zone ellipses, models administered the same questions (logprob reader for
open, sampling reader for API) and projected as dots. Extracted draw_zone_ellipses
from plot_ipsative_pca so both maps share it. Human map reproduces the Economist
layout: African-Islamic/South-Asia (survival) left, English-Speaking/Protestant
Europe (self-expression) right, Confucian/Latin America/Orthodox separating.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Second consumer (the WVS map) needs the same curated zone data, so move it out of
the showcase script into the package rather than duplicate it (avoids drift in
research data). Extended IW_ZONE to cover all 90 WVS countries + SAR/name-variant
aliases. Showcase script now imports zones_for from tinymfv.zones.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Per feedback the individual-respondent contours filled the frame (within >>
between variance). Now each IW zone is a ~1.6-sigma covariance ellipse over its
member country-mean dots, with an eigenvalue floor so 1-2 country zones get a
visible blob instead of a dot/line (fixes big5 SG/PK orphans). PCA now fits on the
country means M so the axes are between-country and zones separate. mfq2/big5/mfv
read cleanly; humor still overlaps (real negative result: humor country profiles
don't cluster the IW way).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Probe + artifact: the WVS subset of Anthropic/llm_global_opinions is 353 questions
over 90 countries (212 questions with >=40 countries), matching tinymfv's MC +
human-anchor shape and dense enough for an Economist-scale map. Documents the
selections parse recipe and the open axis-definition fork (literal IW 10-question
factor model vs shared-question ipsative PCA) before model-run compute.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
read_api.read_items_sampled samples N chat completions at temperature and uses the
empirical answer frequency as the per-item categorical p, emitting the same row
shape the logprob reader does -- so E/profile/entropy flow through the identical
per_item_categorical + reducers and a frontier model without logprobs drops onto
the same map. pmass_allowed becomes the parse rate (sampling coherence gate); C/LO
are omitted by design (log of a frequency has -inf zeros). This is the Economist's
'average of ten responses' method.
UAT (docs/reviews/p3_api_sampling_uat.md): E_mc == E_logprob to <=0.01 (unbiased),
llama-3.1-8b sampled E lands on the same [1,5] scale, parse-rate gate flags
off-format draws.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
respondent_profiles now returns countries alongside profiles; plot_ipsative_pca
gains respondent_zones -> a p90 Gaussian ellipse per IW zone of the projected
Atari respondent cloud (edge-only, no scipy). mfq2 uses these real-respondent
ellipses; the other instruments keep country-mean hulls. Journal notes the
finding: individual profiles overlap across cultures (within >> between variance),
so only the country-mean hull reproduces the Economist's clean zone blobs.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Echoes the Economist WVS 'Godless hippies' chart: shaded convex-hull blobs per
IW cultural zone (inline 2D hull, no scipy dep so the maps extra stays
matplotlib-only) and bold-first labels for named outliers. Caller owns the zone
taxonomy + name/ISO2 normalizer, fails loud on unmapped countries; the corrupt
'(nu' big5 row is explicitly excluded with a warning.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
- drop splom/splom_zoom/range_zoom (ordinal) and the MFV dlogit dumbbell; every
instrument now yields exactly map_pca_ipsative + range, uniform.
- range pole label is now 'c=+1'/'c=-1' (the multiplier); the full steer desc
stays in the title only (was crammed into each pole annotation).
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The range plot already shows the per-foundation steer (human cloud + AI base
dot + +C/-C arrows) with the human anchor the dumbbell lacks; dcontrast plotted
the same delta on the C-nats scale and failed the eraser test. C stays the
measurement in the CSV/foundations table, just not a separate panel.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
The E map/range are for human comparison; this new per-instrument foundation_dcontrast
figure shows the steer in the sensitive contrast readout (steered minus base C, +C vs -C),
the ordinal twin of the MFV dlogit dumbbell. read_profiles gains a value_col so it reads
either E ('mean') or C. This is the figure that shows what we steered for; the E range hides it.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
MFV got two bespoke figures (z-emphasis bars + dlogit dumbbell). Per GPT-5.5 code
review, reuse the geometry not the ordinal semantics: a _mfv_zspace adapter feeds the
same plot_ipsative_pca + plot_range the ordinal instruments use, in z-scored relative-
emphasis space (logit-violation and 1-5 wrongness cannot share a raw axis). Keep the
dlogit dumbbell as a raw-magnitude diagnostic; drop the bespoke map_emphasis.
Also fix a real bug the review found: plot_ordinal gated the map/SPLOM trajectory to
coherent c (pmass >= 0.95*base) but still passed ALL cs to range/range_zoom, so the
figures disagreed on which poles were valid. Range/zoom now use the same coh_cs.
plot_range gains an ylabel param (MFV is z-space, not 1-M); draw_steer asserts sorted
cs; MFV asserts only Social Norms is dropped.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
Trajectory coherence gate was absolute (pmass<0.9); make it relative -- keep a
c only if pmass >= 95% of the base (c=0) pmass, else drop it entirely (no hollow
markers). Read vec_label from summary.json so non-authority personas label
correctly instead of the hardcoded "Authority/Care axis".
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
- plot_splom: KxK pairs-plot (mfq2 real joint only -- others ship independent-
marginal haze that would fabricate off-diagonal correlation). lower=joint
scatter w/ AI base->steer trajectory, diag=marginal+AI rules, upper=Pearson r
sized by |r|. Ordered by PC1 loading so binding (authority/loyalty) cluster.
full + AI-zoom (macro/micro). NaN-safe at collapsed poles.
- ipsative map: synthetic haze is far wider than the societies, so it now crops to
societies+steer (haze clips) and adds a 'full space' minimap with a viewport
rectangle -- readable big5/16pf/humor maps with macro context kept.
- compass labels via textalloc (was overlapping); NaN-safe crop.
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>