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>
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>
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>
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>
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>
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>
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>