# Helium production in the United States
## Microsim
### Live player
<div class="microsim-player">
<iframe src="https://editor.p5js.org/sciencenibber/full/ECYubMBFg" width="100%" height="620" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
<div class="microsim-fallback">
<img src="Microsims/thumbs/Helium_production_in_the_United_States.png" alt="Helium_production_in_the_United_States microsim poster" style="width:100%;border:1px solid #4445;border-radius:6px;">
<p><em>Live microsim (desktop) · <a href="https://editor.p5js.org/sciencenibber/sketches/ECYubMBFg">open sketch in the p5.js editor</a></em></p>
</div>
**Editor URL:** https://editor.p5js.org/sciencenibber/sketches/ECYubMBFg
**Description (100 words):**
The reader sees a five-stage process chain laid out left-to-right: wellhead, gas processing, cryogenic separation, Grade-A purification, and liquefaction, with a magenta side-branch dropping to Cliffside storage. Yellow flow tokens stream between blocks, their [[Density|density]] tracking the live mass balance. Four sliders drive the cascade: helium fraction in feedstock natural gas, NG flow rate, cryogenic separation efficiency, and purifier efficiency. Inter-stage readouts show helium throughput at every interconnect in Mcf per day, while the bottom gauges aggregate to annual output and revenue at $385 per Mcf. The canonical equation He_out = y_He * Q_NG * eta_sep * eta_pur anchors the bottom-right HUD.
```js
// =====================================================================
// Helium_production_in_the_United_States.js -- Wikitube microsim
// Article: Helium production in the United States
// URL: en.wikitube.io/wiki/Helium_production_in_the_United_States
// Room: Helium Pattern: G (block diagram, process chain)
// ---------------------------------------------------------------------
// Idea: an interactive block diagram of the US helium recovery cascade
// -- the five-stage process chain that takes helium-bearing natural gas
// from a Hugoton-Panhandle wellhead all the way to liquid helium in a
// tube trailer. The reader drives four physical parameters with sliders
// and watches the mass balance propagate stage-by-stage through the
// cascade, with animated tokens whose density encodes flow rate.
//
// Process chain (left to right):
//
// [1] WELLHEAD raw NG, He fraction y_He (0.3 - 7 percent)
// | flow Q_NG (Mscf/d)
// v
// [2] GAS PROCESSING CO2 / H2S / H2O removal
// | (no He loss)
// v
// [3] CRYO SEPARATION J-T cycle or turboexpander cascade
// | eta_sep recovery (0.70 - 0.99)
// v
// [4] GRADE-A PURIFY PSA + cryogenic polish
// | eta_pur (0.85 - 0.999) -> 99.997 percent He
// v
// [5] LIQUEFACTION Claude / Collins cycle, T = 4.222 K
// | tube trailers, ISO containers
// v
// OUTPUT Mcf/d He delivered + USD/yr revenue
//
// A side-arrow branches from stage [4] to a CLIFFSIDE STORAGE block --
// the Bush Dome reservoir near Amarillo, TX, sold by the BLM to
// Messer LLC in June 2024 under the Helium Stewardship Act of 2013.
//
// Canonical mass balance (the equation that drives the whole sketch):
//
// He_out = He_in * eta_sep * eta_pur
//
// He_in = y_He * Q_NG (helium in feedstock)
// He_out = He_in * eta_sep * eta_pur (refined Grade-A helium)
//
// At 81 Mm^3/yr and ~$385/Mcf the gauges read out to roughly $1.1B
// annual sales -- matching the 2024 USGS MCS value for US production.
// See USGS Mineral Commodity Summaries 2025, helium chapter.
//
// Visual layout (720 x 520):
// * top: HUD title + en.wikitube.io/wiki/<slug> subtitle
// * upper band: five stage blocks left-to-right, connected by arrows
// with moving tokens; Cliffside branch on the right
// * mid band: live mass-balance readouts at each interconnect
// * lower band: four sliders (y_He, Q_NG, eta_sep, eta_pur)
// plus annual-Mcf and annual-USD gauges
// * bottom-right: canonical equation in ASCII
//
// Conventions (Wikitube Betterfire Standard v0):
// * single ARTICLE constant at the top, single quotes
// * p5.disableFriendlyErrors = true
// * all sliders explicitly .position(x,y).size(w) -- never floating
// * non-ASCII chars live in COMMENTS only; text() literals are ASCII
// * Energy room palette (P5_JS_EDITOR section 4, line 165)
// =====================================================================
const ARTICLE = 'Helium_production_in_the_United_States';
const TITLE = 'Helium production in the United States';
p5.disableFriendlyErrors = true;
// ----- Energy room palette (P5_JS_EDITOR section 4) ------------------
const BG = 18;
const FG = 240;
const DIM = [240, 240, 240, 150];
const HOT = [220, 110, 60]; // gas / source / wellhead
const COLD = [60, 130, 220]; // cryo / liquid stages
const STRUCT = [120, 130, 150]; // block outlines / piping
const TRAJ = [240, 220, 80]; // flow tokens / accent
const GAUGE = [120, 220, 140]; // gauges / outputs
const ACCENT = [200, 100, 220]; // Cliffside / storage branch
// ----- Slider state (read once per frame in draw) --------------------
let yHeSlider; // helium fraction in raw NG (percent, 0.3 - 7.0)
let qNGSlider; // natural-gas feedstock flow (Mscf/d, 100 - 5000)
let etaSepSlider; // cryogenic separation efficiency (0.70 - 0.99)
let etaPurSlider; // purification efficiency (0.85 - 0.999)
// ----- Animated-token state per arrow segment ------------------------
// Each arrow segment carries a phase offset; tokens are drawn at
// (phase + i / N) mod 1 along the segment. Token density scales with
// the local flow rate so the eye reads the cascade as a flow chain.
let tickPhase = 0;
// ----- Process stages (block geometry, set in setup) -----------------
// Each block: { x, y, w, h, title, sub, accent }.
let blocks = [];
let cliffside = null; // Cliffside branch block (drawn separately)
// ----- Layout constants ----------------------------------------------
const BLOCK_W = 100;
const BLOCK_H = 60;
const ROW_Y = 130; // top of stage-block row
const ARROW_GAP = 18; // pixels of clear space between block edges and arrow ends
// =====================================================================
// setup
// =====================================================================
function setup() {
createCanvas(720, 520);
pixelDensity(2);
textFont('system-ui');
// ----- Build the five-stage block list ---------------------------
// Evenly spread across the canvas with a small inset.
const inset = 24;
const usable = width - 2 * inset;
const stride = usable / 5;
const titles = [
['WELLHEAD', 'Hugoton-Panhandle'],
['GAS PROC', 'CO2/H2S/H2O out'],
['CRYO SEP', 'J-T / turboexp'],
['GRADE-A PUR', 'PSA + cryo polish'],
['LIQUEFY', 'Claude/Collins 4.2K']
];
for (let i = 0; i < 5; i++) {
const cx = inset + stride * (i + 0.5);
blocks.push({
x: cx - BLOCK_W / 2,
y: ROW_Y,
w: BLOCK_W,
h: BLOCK_H,
title: titles[i][0],
sub: titles[i][1],
// Color graduates from HOT (raw gas) to COLD (liquid helium).
accent: i <= 1 ? HOT : (i === 2 ? STRUCT : COLD)
});
}
// Cliffside storage branch -- sits below stage [4] (Grade-A).
const g4 = blocks[3];
cliffside = {
x: g4.x + g4.w / 2 - BLOCK_W / 2,
y: ROW_Y + BLOCK_H + 72,
w: BLOCK_W,
h: BLOCK_H - 18,
title: 'CLIFFSIDE',
sub: 'Bush Dome / Messer'
};
// ----- Sliders (Betterfire rule: explicit .position().size()) ----
// All four sliders sit in a tidy column-of-two-rows below the
// process diagram so the reader can scan parameter -> effect.
const sx1 = 24, sx2 = width / 2 + 12;
const sy1 = 360, sy2 = 410;
const SW = 230;
// y_He: helium volume fraction in raw NG, in percent.
yHeSlider = createSlider(0.3, 7.0, 1.8, 0.05).position(sx1, sy1).size(SW);
// Q_NG: feedstock flow in million standard cubic feet per day.
qNGSlider = createSlider(100, 5000, 1500, 25).position(sx2, sy1).size(SW);
// eta_sep: cryogenic separation recovery efficiency, fraction.
etaSepSlider = createSlider(0.70, 0.99, 0.92, 0.005).position(sx1, sy2).size(SW);
// eta_pur: Grade-A purification recovery efficiency, fraction.
etaPurSlider = createSlider(0.85, 0.999, 0.985, 0.001).position(sx2, sy2).size(SW);
textAlign(LEFT, TOP);
}
// =====================================================================
// draw
// =====================================================================
function draw() {
background(BG);
// Read all sliders once -- physics is then expressed in named locals
// rather than .value() calls. (P5_JS_EDITOR section 4 convention.)
const yHe = yHeSlider.value() / 100; // mole fraction He in NG
const qNG = qNGSlider.value(); // Mscf/d feedstock
const etaSep = etaSepSlider.value(); // cryo separation eff.
const etaPur = etaPurSlider.value(); // Grade-A purify eff.
// ----- Stage-by-stage mass balance -------------------------------
// Stage 1 (wellhead) carries y_He * Q_NG raw helium per day.
// Stage 2 (gas processing) is helium-conservative -- assume 0 loss.
// Stage 3 (cryo separation) recovers a fraction eta_sep.
// Stage 4 (Grade-A) trims another (1 - eta_pur).
// Stage 5 (liquefaction) is conservative for the gas balance shown.
const heIn = yHe * qNG; // Mcf/d crude He
const afterGP = heIn; // gas proc passes He
const afterCryo = afterGP * etaSep; // crude He recovered
const afterPur = afterCryo * etaPur; // Grade-A He
const heOut = afterPur; // delivered He
// Annualize: 365 day per year; revenue at 2024 spot ~$385/Mcf.
const heAnnual = heOut * 365; // Mcf/yr
const revenue = heAnnual * 385; // USD/yr
// Token density per inter-stage segment encodes local flow rate.
// We normalize against a "loud" reference flow of 60 Mcf/d so the
// reader gets a clear visual ramp without saturating.
const REF = 60;
const densities = [
constrain(heIn / REF, 0.05, 1.5),
constrain(afterGP / REF, 0.05, 1.5),
constrain(afterCryo / REF, 0.05, 1.5),
constrain(afterPur / REF, 0.05, 1.5)
];
// Advance the global animation phase.
tickPhase = (tickPhase + 0.004) % 1;
// ----- Draw block diagram ----------------------------------------
drawConnectors(densities);
drawCliffsideBranch();
for (let i = 0; i < blocks.length; i++) drawBlock(blocks[i], i + 1);
drawBlock(cliffside, 'B');
// ----- Inter-stage flow labels -----------------------------------
drawFlowLabels(heIn, afterGP, afterCryo, afterPur, heOut);
// ----- Slider labels ---------------------------------------------
drawSliderLabels(yHe, qNG, etaSep, etaPur);
// ----- Output gauges (annual Mcf, annual USD) --------------------
drawGauges(heAnnual, revenue);
// ----- HUD + canonical equation ----------------------------------
drawHUD();
}
// =====================================================================
// Block diagram drawing
// =====================================================================
// Draw one process block with title + subtitle + index badge.
function drawBlock(b, idx) {
push();
// Subtle gradient: a darker base rectangle with a colored top stripe.
noStroke();
fill(28);
rect(b.x, b.y, b.w, b.h, 6);
// Top accent stripe (HOT/STRUCT/COLD/ACCENT depending on block).
const acc = b.accent || ACCENT;
fill(acc[0], acc[1], acc[2], 220);
rect(b.x, b.y, b.w, 6, 6, 6, 0, 0);
// Frame
noFill();
stroke(...STRUCT);
strokeWeight(1);
rect(b.x, b.y, b.w, b.h, 6);
// Index badge (small filled circle, top-left of block).
noStroke();
fill(...TRAJ);
circle(b.x + 12, b.y + 18, 16);
fill(BG);
textAlign(CENTER, CENTER);
textSize(10);
text(idx, b.x + 12, b.y + 18);
// Title (block.title) + subtitle (block.sub)
fill(FG);
noStroke();
textAlign(LEFT, TOP);
textSize(11);
text(b.title, b.x + 24, b.y + 12);
fill(...DIM);
textSize(9);
text(b.sub, b.x + 24, b.y + 28);
pop();
}
// Draw the four left-to-right arrows between adjacent stage blocks,
// with animated tokens whose density encodes local flow rate.
function drawConnectors(densities) {
push();
for (let i = 0; i < 4; i++) {
const a = blocks[i];
const b = blocks[i + 1];
const x0 = a.x + a.w + 2;
const x1 = b.x - 2;
const y = a.y + a.h / 2;
// Static pipe line
stroke(...STRUCT);
strokeWeight(2);
line(x0, y, x1, y);
// Arrowhead
drawArrowhead(x1, y, 8);
// Animated flow tokens (yellow dots) moving left-to-right.
const N = Math.max(2, Math.round(densities[i] * 10));
noStroke();
fill(...TRAJ);
for (let k = 0; k < N; k++) {
const f = ((k / N) + tickPhase) % 1;
const px = lerp(x0 + ARROW_GAP / 2, x1 - ARROW_GAP / 2, f);
const alpha = 220 - 120 * Math.abs(0.5 - f) * 2;
fill(TRAJ[0], TRAJ[1], TRAJ[2], alpha);
circle(px, y, 5);
}
}
pop();
}
// Draw the side-branch arrow + Cliffside storage block.
function drawCliffsideBranch() {
push();
const g4 = blocks[3];
const sx = g4.x + g4.w / 2;
const sy = g4.y + g4.h;
const ex = cliffside.x + cliffside.w / 2;
const ey = cliffside.y;
// Pipe (vertical down from stage 4 to top of Cliffside)
noFill();
stroke(...ACCENT);
strokeWeight(2);
line(sx, sy, ex, ey - 4);
drawArrowhead(ex, ey - 4, 8, 90); // arrowhead pointing down
// Animated tokens going down
const N = 5;
noStroke();
for (let k = 0; k < N; k++) {
const f = ((k / N) + tickPhase) % 1;
const py = lerp(sy + 4, ey - 8, f);
const alpha = 220 - 120 * Math.abs(0.5 - f) * 2;
fill(ACCENT[0], ACCENT[1], ACCENT[2], alpha);
circle(sx, py, 4);
}
// Annotation
noStroke();
fill(...ACCENT);
textSize(9);
textAlign(LEFT, CENTER);
text('to strategic reserve', sx + 8, (sy + ey) / 2);
pop();
}
// Small triangle arrowhead at (x, y). theta in degrees from +x axis
// (default 0 = points right; 90 = points down).
function drawArrowhead(x, y, size, thetaDeg) {
const t = (thetaDeg === undefined) ? 0 : thetaDeg;
push();
translate(x, y);
rotate(radians(t));
noStroke();
fill(...STRUCT);
triangle(0, 0, -size, -size / 2, -size, size / 2);
pop();
}
// =====================================================================
// Inter-stage flow labels + slider labels + output gauges
// =====================================================================
function drawFlowLabels(heIn, afterGP, afterCryo, afterPur, heOut) {
push();
textSize(9);
textAlign(CENTER, TOP);
fill(...DIM);
const ys = ROW_Y + BLOCK_H + 6; // just below the block row
// Midpoint between block centers gives the label x for each segment.
const mids = [];
for (let i = 0; i < 4; i++) {
const a = blocks[i];
const b = blocks[i + 1];
mids.push((a.x + a.w + b.x) / 2);
}
text('He_in: ' + formatMcf(heIn) + ' Mcf/d', mids[0], ys);
text(formatMcf(afterGP) + ' Mcf/d', mids[1], ys);
text('crude: ' + formatMcf(afterCryo) + ' Mcf/d', mids[2], ys);
text('Grade-A: ' + formatMcf(afterPur) + ' Mcf/d', mids[3], ys);
// Output label past the last block.
fill(...GAUGE);
textSize(11);
textAlign(LEFT, CENTER);
const last = blocks[blocks.length - 1];
text('out -> ' + formatMcf(heOut) + ' Mcf/d',
last.x + last.w + 4, last.y + last.h + 22);
pop();
}
function drawSliderLabels(yHe, qNG, etaSep, etaPur) {
push();
noStroke();
textAlign(LEFT, BOTTOM);
textSize(11);
fill(FG);
// Slider header
fill(...DIM);
textSize(10);
text('PARAMETERS', 24, 340);
// Row 1
fill(FG);
textSize(11);
text('He fraction in NG: ' + (yHe * 100).toFixed(2) + ' %', 24, 358);
text('NG feedstock: ' + qNG.toFixed(0) + ' Mscf/d', width / 2 + 12, 358);
// Row 2
text('eta_sep (cryo): ' + etaSep.toFixed(3), 24, 408);
text('eta_pur (Grade-A): ' + etaPur.toFixed(3), width / 2 + 12, 408);
pop();
}
function drawGauges(heAnnual, revenue) {
push();
// Place gauges along the bottom row, just above the equation line.
const gy = 458;
noStroke();
fill(...DIM);
textAlign(LEFT, TOP);
textSize(10);
text('OUTPUT', 24, gy);
fill(...GAUGE);
textSize(13);
text('annual He: ' + formatMmcf(heAnnual) + ' MMcf/yr', 24, gy + 14);
text('revenue ($385/Mcf):