---
title: "nMonte explained: the R script behind every G25 admixture fit"
description: "Ger Huijbregts's nMonte turned Global25 rows into ancestry percentages and founded a whole tool tradition. How the Monte-Carlo search works, what nMonte3 changed, what the fit distance is, and the discipline the script never enforces."
canonical: https://www.ancestrify.io/blog/nmonte-explained
date: 2026-08-31T09:24:00+00:00
author: "Andi Thomaj"
---

# nMonte explained: the R script behind every G25 admixture fit

Every Global25 admixture percentage you have ever seen — in
[Vahaduo](/blog/vahaduo-g25-tutorial), in [our calculators](/lab/admixture), in a decade of forum
arguments — descends from one R script. Ger Huijbregts wrote **nMonte** in the mid-2010s, the
[Eurogenes blog](/blog/eurogenes-global25-history) adopted it as the standard companion to its
coordinate sheets, and its core idea has been reimplemented so many times that "nMonte-style" is
now simply the name of the method. This is what the script actually does, and what it deliberately
does not.

## The problem it solves

Given a target row and a set of source rows in
[the same coordinate space](/blog/what-are-global25-coordinates), find non-negative percentages
summing to 100% whose weighted average of the sources lands as close to the target as possible.
That is a constrained least-squares problem, and nMonte solves it the pragmatic way: **Monte-Carlo
descent**. Start from some allocation of weight across sources; repeatedly propose a small random
reallocation (move a slice of weight from one source to another); keep the proposal if the
mixture's Euclidean distance to the target shrinks; stop when proposals stop helping. The final
allocation is the breakdown, and the residual gap is the
[fit distance](/blog/g25-fit-distance-explained).

Randomised descent has two properties worth knowing. It handles any panel size without matrix
algebra, which is why it ports so easily to browsers. And **it is a local search**: with
near-[collinear sources](/blog/g25-source-selection-overfitting), different random runs settle on
different splits of the shared signal — same fit, different story. When two sources trade ten
points between runs, the instability *is* information: the panel, not the arithmetic, cannot tell
them apart.

## nMonte versus nMonte3, and the dials

The original script fits the target as pasted. **nMonte3** added the option everyone now argues
about: a penalty term (`pen`) that trades a slightly worse fit for a sparser, less scattered
model, damping the script's tendency to sprinkle 1–2% across many sources. Batch mode, "1 outcome
per line" runs and sheet conventions accumulated around it. Every dial is a modelling *choice*:
penalty on and off can move percentages by real amounts with near-identical fits — which is not a
bug but the method telling you those models are not distinguishable by distance alone.

Our production engine is a seeded descendant of the same family, with the choices fixed and
stated: 500 weight slots (0.2% granularity), five independent restarts, pruning of sub-1.5%
components followed by re-solving, a hard cap of eight sources, and a deterministic seed so a
published result reproduces byte for byte. None of that changes the mathematics; it changes
whether two people running "the same model" get the same answer.

## What the script never enforces

nMonte computes exactly what you asked and nothing about whether the question was sound. The
discipline lives outside the script, and forgetting that is the whole failure mode of the genre:

- **[Scaled and unscaled rows must never mix](/blog/g25-scaled-vs-unscaled)** — the script fits
  either happily, meaningfully fits neither mixed.
- **Sources decide the answer.** Any panel returns percentages; only
  [panel construction](/blog/g25-source-selection-overfitting) decides whether they mean anything.
- **[Fit distance is not a p-value](/blog/g25-fit-distance-explained).** Adding sources lowers it
  mechanically; a lower fit is not a better model past the point where the panel stays defensible.
- **No rejection exists.** A coordinate fit cannot fail. The method that can —
  [qpAdm](/blog/qpadm-vs-global25) — works from allele-frequency statistics, not coordinates, and
  that difference is [what the paid formal analysis buys](/qpadm).

## Do you need to run the R script?

Only if you want the dials or scriptable batch runs. For everything else the browser
implementations are the same mathematics with the bookkeeping handled:
[Vahaduo](/blog/vahaduo-g25-tutorial) for bring-your-own-sheet freedom, our
[free calculators](/lab/admixture) for curated era-scoped panels with the fit stated — and the
[full toolbox is here](/blog/free-g25-tools). If you do run it: R installed, `nMonte3.R` plus a
`data` and `target` file in the working directory, `source('nMonte3.R')`, and the sheet
discipline above observed with the seriousness the script itself will never demand.

A ten-year-old R script with no test, no errors bars and no opinions became the load-bearing tool
of an entire hobby — which is exactly why knowing its shape matters. The percentages were never
the script's claim. They were always yours.

Terms used here are defined in the [glossary](/glossary).
