diff --git a/CORRECTIONS.md b/CORRECTIONS.md index 0c545e0..ce676fb 100644 --- a/CORRECTIONS.md +++ b/CORRECTIONS.md @@ -175,3 +175,15 @@ Claims made during the exploration that did not survive it, kept so the record s not 1.36. Whether the clouds' 1.36 matches the laboratory's 1.3 to 1.4 for a shared reason is not shown. The laboratory measures sections of turbulent surfaces (7/3 in 3D for the interface), the clouds a projection of supersonic gas, and the two only meet near 4/3. + +## 18 October 2026: cosmic homogeneity (issue #17) + +21. **"Beyond about 80 to 300 million parsecs (h⁻¹ Mpc, 80 to 300)."** Two errors, and a missing caveat. The 80 + to 300 h⁻¹ Mpc in Scrimgeour et al. (2012) is the range over which they rule out a fractal with D₂ below + 2.97, and 300 is the largest scale their survey reached. Their homogeneity scale is 70 to 81 h⁻¹ Mpc: 71 ± 8, + 70 ± 5, 81 ± 5 and 75 ± 4 at z ≈ 0.2, 0.4, 0.6 and 0.8. And h⁻¹ Mpc is not Mpc: with h = 0.674, 70 to + 81 h⁻¹ Mpc is 104 to 120 Mpc. The caveat: the scale is a convention, not a fixed length. It depends on the + tracer, the epoch and the threshold; a 0.1% threshold instead of 1% puts it at 185 h⁻¹ Mpc for matter today. + See [cosmic-homogeneity](cosmic-homogeneity/). +22. **"The nesting stops, the way a coastline's roughness stops at the size of molecules."** Points the wrong + way. A coastline is rough down to a smallest scale; galaxy clustering is clumpy up to a largest one. diff --git a/README.md b/README.md index 6ac95ec..e63d631 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,11 @@ with Claude (Anthropic), for [Foundation0](https://github.com/Foundation-0/f0). that roughness only if they kick it harder than its own momentum: a cold atom in a room-temperature vacuum, yes; nothing as warm as, and heavier than, the gas around it ([quantum-paths](quantum-paths/)). - **Cosmic structure is fractal only up to a point.** Galaxies cluster in a fractal-like way on small - scales, but beyond about 80 to 300 million parsecs (h⁻¹ Mpc, 80 to 300) they are spread almost evenly - (Scrimgeour et al., 2012). The nesting stops, the way a coastline's roughness stops at the size of molecules. + scales: well inside 5 h⁻¹ Mpc (the surveys' unit, about 1.5 million parsecs), the number of neighbours + within a distance r grows as r^1.2 rather than r³. Beyond a radius of about 70 to 80 h⁻¹ Mpc, roughly 100 to + 120 million parsecs, the galaxies of the WiggleZ survey are spread evenly to within 1% (Scrimgeour et al., + 2012). The nesting stops at a largest scale, as a coastline's roughness stops at a smallest one, and where + depends on the galaxies and on how even counts as even ([cosmic-homogeneity](cosmic-homogeneity/)). - **Turbulence repeats, but not exactly.** Between the size where a flow is stirred and the size where viscosity takes over, the same energy flux passes through every scale. In a cascade model it holds to 0.2% over the first nine of 21 shells (2.4 decades), and a third-order law that follows from it to 0.1%. The bursts do not scale diff --git a/cosmic-homogeneity/README.md b/cosmic-homogeneity/README.md new file mode 100644 index 0000000..7734942 --- /dev/null +++ b/cosmic-homogeneity/README.md @@ -0,0 +1,200 @@ +# Cosmic homogeneity: where galaxy clustering stops looking fractal + +Status: **exploration, not peer reviewed, no new result claimed.** Issue +[#17](https://github.com/Foundation-0/fractals/issues/17). + +Galaxies cluster: around any galaxy there are more neighbours nearby than an even spread would give. On large +enough scales the excess fades. This study computes where, from the standard cosmological model, checks it +against the surveys, and shows how much the answer depends on the definition. + +## The measure + +Count the galaxies within a distance r of a galaxy, divided by the count an even spread would give: +N( 31.5 * ob * Math.pow(th, -4) * (1e3 / z); // eq. 5 + const Req = R(zeq), Rd = R(zd); + const soundHorizon = z => { const R1 = R(z); return 2 / (3 * keq) * Math.sqrt(6 / Req) * Math.log((Math.sqrt(1 + R1) + Math.sqrt(R1 + Req)) / (1 + Math.sqrt(Req))); }; + const s = soundHorizon(zd); // eq. 6, Mpc, at Eisenstein and Hu's fitted drag redshift + const ksilk = 1.6 * Math.pow(ob, 0.52) * Math.pow(om, 0.73) * (1 + Math.pow(10.4 * om, -0.95)); // eq. 7, Mpc^-1 + const a1 = Math.pow(46.9 * om, 0.670) * (1 + Math.pow(32.1 * om, -0.532)); + const a2 = Math.pow(12.0 * om, 0.424) * (1 + Math.pow(45.0 * om, -0.582)); + const ac = Math.pow(a1, -fb) * Math.pow(a2, -fb * fb * fb); // eq. 11 + const c1 = 0.944 / (1 + Math.pow(458 * om, -0.708)), c2 = Math.pow(0.395 * om, -0.0266); + const bc = 1 / (1 + c1 * (Math.pow(fc, c2) - 1)); // eq. 12 + const y = (1 + zeq) / (1 + zd), sy = Math.sqrt(1 + y); + const G = y * (-6 * sy + (2 + 3 * y) * Math.log((sy + 1) / (sy - 1))); // eq. 15 + const ab = 2.07 * keq * s * Math.pow(1 + Rd, -0.75) * G; // eq. 14 + const bnode = 8.41 * Math.pow(om, 0.435); // eq. 23 + const bb = 0.5 + fb + (3 - 2 * fb) * Math.sqrt(Math.pow(17.2 * om, 2) + 1); // eq. 24 + const T0 = (k, a, b) => { // eqs. 10, 19, 20 + const q = k / (13.41 * keq), L = Math.log(Math.E + 1.8 * b * q), C = 14.2 / a + 386 / (1 + 69.9 * Math.pow(q, 1.08)); + return L / (L + C * q * q); + }; + const j0 = x => (x < 1e-4 ? 1 - x * x / 6 : Math.sin(x) / x); + function T(kh) { + const k = kh * par.h, ks = k * s; + const f = 1 / (1 + Math.pow(ks / 5.4, 4)); // eq. 18 + const Tc = f * T0(k, 1, bc) + (1 - f) * T0(k, ac, bc); // eq. 17 + const st = s / Math.cbrt(1 + Math.pow(bnode / ks, 3)); // eq. 22 + const Tb = (T0(k, 1, 1) / (1 + Math.pow(ks / 5.2, 2)) + ab / (1 + Math.pow(bb / ks, 3)) * Math.exp(-Math.pow(k / ksilk, 1.4))) * j0(k * st); // eq. 21 + return fb * Tb + fc * Tc; // eq. 16 + } + // No-wiggle form (eqs. 26, 28-31): the same broad shape without the acoustic oscillations. + const sNw = 44.5 * Math.log(9.83 / om) / Math.sqrt(1 + 10 * Math.pow(ob, 0.75)); // eq. 26, Mpc + const aG = 1 - 0.328 * Math.log(431 * om) * fb + 0.38 * Math.log(22.3 * om) * fb * fb; // eq. 31 + function Tnw(kh) { + const k = kh * par.h, G = par.Om * par.h * (aG + (1 - aG) / (1 + Math.pow(0.43 * k * sNw, 4))); // eq. 30 + const q = kh * th * th / G, L = Math.log(2 * Math.E + 1.8 * q), C = 14.2 + 731 / (1 + 62.5 * q); // eqs. 28, 29 + return L / (L + C * q * q); + } + return { T, Tnw, s, soundHorizon, zd, zeq }; + } + + // Linear growth in flat LCDM: D(a) proportional to H(a) int_0^a da' / (a' H(a'))^3, with D = 1 today; f = dln D / dln a. + function growth(par) { + const E = a => Math.sqrt(par.Om / (a * a * a) + 1 - par.Om); + const Dun = a => { const N = 2000; let s = 0; for (let i = 0; i < N; i++) { const x = a * (i + 0.5) / N; s += a / N / Math.pow(x * E(x), 3); } return E(a) * s; }; + const D0 = Dun(1); + const D = z => Dun(1 / (1 + z)) / D0; + const f = z => { const a = 1 / (1 + z), e = 1e-4; return Math.log(Dun(a * (1 + e)) / Dun(a * (1 - e))) / Math.log((1 + e) / (1 - e)); }; + return { D, f }; + } + + const tophat = x => (x < 1e-3 ? 1 - x * x / 10 : 3 * (Math.sin(x) - x * Math.cos(x)) / (x * x * x)); + const sinc = x => (x < 1e-4 ? 1 - x * x / 6 : Math.sin(x) / x); + + // A cosmology: today's linear matter power spectrum and the correlation integrals on a log-k grid. + // opts.sigmaNL (h^-1 Mpc) smooths the acoustic peak. A Gaussian cutoff exp(-(k kd)^2), kd = 0.25 h^-1 Mpc, lets the + // oscillating integrals converge at small r; it changes D2 by under 1e-5 beyond 40 h^-1 Mpc. The log-k grid + // resolves xi to 0.3% out to r = 300 h^-1 Mpc and not beyond, so R_H is searched no further than that. + function cosmology(par = PLANCK18, opts = {}) { + const tf = transferEH(par), gr = growth(par), SNL = opts.sigmaNL || 0; + const NK = opts.nk || 8000, k0 = 1e-5, k1 = 100, dl = Math.log(k1 / k0) / NK, kd = opts.kdamp || 0.25, K = []; + for (let i = 0; i < NK; i++) K.push(k0 * Math.exp((i + 0.5) * dl)); + const P0 = k => Math.pow(k, par.ns) * Math.pow(tf.T(k), 2); + const Psm = k => { const pnw = Math.pow(k, par.ns) * Math.pow(tf.Tnw(k), 2); return pnw + (P0(k) - pnw) * Math.exp(-k * k * SNL * SNL / 2); }; + const sum = (w, f) => { let s = 0; for (let i = 0; i < NK; i++) s += w[i] * f(K[i]); return s; }; + const w0 = K.map(k => k * k * k * P0(k) * dl / (2 * Math.PI * Math.PI)); + const A = par.sigma8 * par.sigma8 / sum(w0, k => Math.pow(tophat(8 * k), 2)); + const w = w0.map(x => A * x); + const wd = K.map((k, i) => (SNL ? A * k * k * k * Psm(k) * dl / (2 * Math.PI * Math.PI) : w[i]) * Math.exp(-k * k * kd * kd)); + return { + par, tf, growth: gr, + P: k => A * P0(k), + sigma: R => Math.sqrt(sum(w, k => Math.pow(tophat(k * R), 2))), + xi: r => sum(wd, k => sinc(k * r)), + xibar: r => sum(wd, k => tophat(k * r)), + }; + } + + // Clustering amplitude relative to today's matter: b^2 D(z)^2, times the Kaiser factor in redshift space. + // beta defaults to f(z)/b. + function amplitude(cos, { z = 0, b2 = 1, beta, redshiftSpace = false } = {}) { + const D = cos.growth.D(z), bt = beta === undefined ? cos.growth.f(z) / Math.sqrt(b2) : beta; + return b2 * D * D * (redshiftSpace ? 1 + 2 * bt / 3 + bt * bt / 5 : 1); + } + function D2(cos, r, amp = 1) { + const x = amp * cos.xi(r), xb = amp * cos.xibar(r); + return 3 + 3 * (x - xb) / (1 + xb); + } + const counts = (cos, r, amp = 1) => 1 + amp * cos.xibar(r); + // Smallest r beyond which the clustering stays within eps of homogeneity: D2 > 3(1 - eps) (by = 'D2'), or + // N < 1 + eps (by = 'N'). Scans outward from 1 to rmax h^-1 Mpc and bisects the last crossing; NaN if the + // clustering is already that even at 1 or not yet at rmax. + function homogeneityScale(cos, amp = 1, { eps = 0.01, by = 'D2', rmax = 300 } = {}) { + const inside = by === 'N' ? r => counts(cos, r, amp) > 1 + eps : r => D2(cos, r, amp) < 3 * (1 - eps); + if (!inside(1)) return NaN; + let last = null, prev = true; + for (let r = 1; r <= rmax; r *= 1.02) { const now = inside(r); if (!now && prev) last = r; prev = now; } + if (last === null || prev) return NaN; + let lo = last / 1.02, hi = last; + for (let i = 0; i < 40; i++) { const mid = Math.sqrt(lo * hi); if (inside(mid)) lo = mid; else hi = mid; } + return hi; + } + // The baryon acoustic peak: the local maximum of xi between 80 and 130 h^-1 Mpc (NaN if xi has none there). + function baoPeak(cos) { + let best = 80, bv = -Infinity; + for (let r = 80; r <= 130; r += 0.1) { const v = cos.xi(r); if (v > bv) { bv = v; best = r; } } + return best < 80.05 || best > 129.95 ? NaN : best; + } + // Galaxies on small scales: xi = (r/r0)^-gamma. Deep inside r0 the counts grow as r^(3 - gamma). + function D2powerLaw(r, r0, gamma) { + const x = Math.pow(r / r0, -gamma), xb = 3 * x / (3 - gamma); + return 3 + 3 * (x - xb) / (1 + xb); + } + // Endpoint (secant) slope of ln N( 3 * Math.log(r) + Math.log(1 + 3 * Math.pow(r / r0, -gamma) / (3 - gamma)); + return (lnN(r2) - lnN(r1)) / Math.log(r2 / r1); + } + + const api = { PLANCK18, PLANCK15, SCRIMGEOUR12, WIGGLEZ, transferEH, growth, cosmology, amplitude, D2, counts, homogeneityScale, baoPeak, D2powerLaw, slopePowerLaw }; + if (typeof module !== 'undefined' && module.exports) module.exports = api; else root.HOMOGENEITY = api; +})(typeof window !== 'undefined' ? window : globalThis); diff --git a/cosmic-homogeneity/table.mjs b/cosmic-homogeneity/table.mjs new file mode 100644 index 0000000..97216dd --- /dev/null +++ b/cosmic-homogeneity/table.mjs @@ -0,0 +1,49 @@ +// Prints the correlation dimension D2(r) and the homogeneity scale for matter and galaxies. +// Usage: node cosmic-homogeneity/table.mjs +import { createRequire } from 'node:module'; +const H = createRequire(import.meta.url)('./homogeneity.js'); + +const pc = H.cosmology(H.PLANCK18); +console.log('Correlation dimension D2(r), Planck 2018, h^-1 Mpc\n'); +console.log('r'.padStart(6) + 'galaxies, power law'.padStart(22) + 'matter z = 0, linear'.padStart(23)); +for (const r of [0.1, 0.3, 1, 3, 5, 10, 20, 40, 60, 80, 100, 120, 150, 200, 300]) { + const lin = r >= 10 ? H.D2(pc, r).toFixed(4) : '-', pl = r <= 20 ? H.D2powerLaw(r, 5, 1.8).toFixed(3) : '-'; + console.log(String(r).padStart(6) + pl.padStart(22) + lin.padStart(23)); +} +console.log('\nGalaxy power law xi = (r/5)^-1.8 (Zehavi et al. 2005) up to 20 h^-1 Mpc; linear theory from 10 h^-1 Mpc out.'); + +console.log('\nHomogeneity scale R_H, h^-1 Mpc: where D2 reaches 3 (1 - eps)\n'); +console.log('eps'.padStart(8) + 'matter z = 0'.padStart(14)); +for (const eps of [0.01, 0.005, 0.002, 0.001]) console.log(String(eps).padStart(8) + H.homogeneityScale(pc, 1, { eps }).toFixed(0).padStart(14)); + +console.log('\nR_H (eps = 1%) for matter and for galaxies of bias b, h^-1 Mpc\n'); +console.log('z'.padStart(5) + 'matter'.padStart(9) + 'b = 1, z-space'.padStart(16) + 'b = 2, z-space'.padStart(16)); +for (const z of [0, 0.5, 1, 2.4]) { + const a = H.amplitude(pc, { z }), a1 = H.amplitude(pc, { z, redshiftSpace: true }), a2 = H.amplitude(pc, { z, b2: 4, redshiftSpace: true }); + console.log(String(z).padStart(5) + H.homogeneityScale(pc, a).toFixed(0).padStart(9) + H.homogeneityScale(pc, a1).toFixed(0).padStart(16) + H.homogeneityScale(pc, a2).toFixed(0).padStart(16)); +} + +console.log('\nWiggleZ (Scrimgeour et al. 2012): their cosmology and measured bias, redshift space, h^-1 Mpc'); +console.log('D2: where D2 = 2.97 (the paper\'s definition); N: where the scaled counts N = 1.01'); +console.log('Each statistic uses its own fitted bias (Table 4): b^2 D2 for D2, b^2 N for N\n'); +console.log('z'.padStart(5) + 'b^2 D2'.padStart(8) + 'model D2'.padStart(10) + 'paper LCDM'.padStart(12) + 'measured'.padStart(11) + + 'b^2 N'.padStart(7) + 'model N'.padStart(9) + 'paper LCDM'.padStart(12)); +const sc = H.cosmology(H.SCRIMGEOUR12); +for (const s of H.WIGGLEZ) { + const amp = b2 => H.amplitude(sc, { z: s.z, b2, beta: s.beta, redshiftSpace: true }); + const m = H.homogeneityScale(sc, amp(s.b2D)), n = H.homogeneityScale(sc, amp(s.b2N), { by: 'N' }); + console.log(String(s.z).padStart(5) + s.b2D.toFixed(2).padStart(8) + m.toFixed(1).padStart(10) + String(s.RHlcdm).padStart(12) + `${s.RH}±${s.err}`.padStart(11) + + s.b2N.toFixed(2).padStart(7) + n.toFixed(1).padStart(9) + String(s.RHlcdmN).padStart(12)); +} + +console.log('\nBOSS CMASS (Ntelis et al. 2017): matter, real space, z = 0.57, h^-1 Mpc; their LCDM value is 62.0 in Planck 2015\n'); +for (const [name, par] of [['Planck 2015', H.PLANCK15], ['Planck 2018', H.PLANCK18]]) { + const c = H.cosmology(par); + console.log(name.padStart(12) + H.homogeneityScale(c, H.amplitude(c, { z: 0.57 })).toFixed(1).padStart(8)); +} + +console.log('\nThe acoustic peak: D2 for matter today, linear and with the peak smoothed by nonlinear motions (Sigma = 7 h^-1 Mpc)\n'); +const ps = H.cosmology(H.PLANCK18, { sigmaNL: 7 }); +console.log('r'.padStart(6) + 'linear'.padStart(10) + 'smoothed'.padStart(10)); +for (const r of [90, 100, 105, 110, 113, 116, 120, 125]) console.log(String(r).padStart(6) + H.D2(pc, r).toFixed(5).padStart(10) + H.D2(ps, r).toFixed(5).padStart(10)); +console.log(`peak of xi: ${H.baoPeak(pc).toFixed(1)} (linear), ${H.baoPeak(ps).toFixed(1)} (smoothed) h^-1 Mpc`); diff --git a/tests/cosmic-homogeneity.mjs b/tests/cosmic-homogeneity.mjs new file mode 100644 index 0000000..7f4f39f --- /dev/null +++ b/tests/cosmic-homogeneity.mjs @@ -0,0 +1,77 @@ +// Checks the cosmic homogeneity model against published numbers and its own closed forms. Run: npm test. +// Each check states the source, the published or expected value, the model's value and the tolerance. +import { createRequire } from 'node:module'; +const require = createRequire(import.meta.url); +const H = require('../cosmic-homogeneity/homogeneity.js'); + +let fails = 0; +const fmt = x => (x === 0 ? '0' : x.toExponential(2)); +function within(name, got, want, factor, src) { + const ok = got > 0 && got / want <= factor && want / got <= factor; + console.log(`${ok ? 'PASS' : 'FAIL'} ${name}\n model ${fmt(got)} expected ${fmt(want)} (within x${factor}) ${src}`); + if (!ok) fails++; +} +function check(name, ok, detail, src) { + console.log(`${ok ? 'PASS' : 'FAIL'} ${name}\n ${detail} ${src}`); + if (!ok) fails++; +} +const S12 = 'Scrimgeour et al., MNRAS 425, 116 (2012), arXiv:1205.6812'; + +// 1. Scrimgeour et al.'s own LCDM prediction for WiggleZ: their fiducial cosmology, and each slice's measured bias b^2 +// (Table 4, fitted separately for the D2 and the N statistic) and redshift-space beta (Table 3). Linear theory with +// the Kaiser factor only. +const sc = H.cosmology(H.SCRIMGEOUR12); +const rows = H.WIGGLEZ.map(s => { + const amp = b2 => H.amplitude(sc, { z: s.z, b2, beta: s.beta, redshiftSpace: true }); + return { ...s, model: H.homogeneityScale(sc, amp(s.b2D)), modelN: H.homogeneityScale(sc, amp(s.b2N), { by: 'N' }) }; +}); +check('WiggleZ LCDM homogeneity scale (D2 = 2.97), four slices, within 3 h^-1 Mpc', + rows.every(s => Math.abs(s.model - s.RHlcdm) <= 3), + rows.map(s => `z ${s.z}: ${s.model.toFixed(1)} vs ${s.RHlcdm}`).join('; '), S12 + ', Table 4 and Conclusions'); +check('Measured WiggleZ homogeneity scales lie within 2 sigma of the model', + rows.every(s => Math.abs(s.model - s.RH) <= 2 * s.err), + rows.map(s => `z ${s.z}: ${s.RH}±${s.err} vs ${s.model.toFixed(1)}`).join('; '), S12 + ', abstract'); +check('The other definition (N = 1.01) gives a few h^-1 Mpc more, as in their LCDM counts, within 3 h^-1 Mpc', + rows.every(s => Math.abs(s.modelN - s.RHlcdmN) <= 3 && s.modelN > s.model), + rows.map(s => `z ${s.z}: ${s.modelN.toFixed(1)} vs ${s.RHlcdmN}`).join('; '), S12 + ', Table 4'); + +// 2. Ntelis et al. (2017), BOSS CMASS galaxies, corrected for bias and redshift-space distortion: their abstract gives +// LCDM R_H = 62.0 h^-1 Mpc, a real-space matter value (Sec. 5.5, Table 5), in their fiducial Planck 2015 cosmology. +// The paper attaches no redshift to it; z = 0.57 is the CMASS effective redshift. +const p15 = H.cosmology(H.PLANCK15); +within('Matter homogeneity scale at z = 0.57, Planck 2015 (h^-1 Mpc)', H.homogeneityScale(p15, H.amplitude(p15, { z: 0.57 })), 62.0, 1.05, + 'Ntelis et al., JCAP 06 (2017) 019, arXiv:1702.02159, abstract (LCDM value for matter)'); + +// 3. The linear spectrum itself. +const pc = H.cosmology(H.PLANCK18); +within('Sound horizon at Planck\'s drag redshift (z = 1059.94), Eisenstein-Hu eq. 6 (Mpc)', pc.tf.soundHorizon(H.PLANCK18.zdrag), H.PLANCK18.rdrag, 1.01, + 'Planck 2018 r_drag (TT,TE,EE+lowE+lensing); at their own fitted z_d = 1021 the same formula gives 150.9'); +within('Baryon acoustic peak in the linear correlation function (sanity check)', H.baoPeak(pc), 100, 1.05, 'Eisenstein et al., ApJ 633, 560 (2005), ~100 h^-1 Mpc'); +within('sigma8 normalisation', pc.sigma(8), H.PLANCK18.sigma8, 1.0001, 'model'); + +// 4. Small scales: galaxies with xi = (r/r0)^-gamma, r0 = 5 h^-1 Mpc and gamma = 1.8 (Zehavi et al., ApJ 630, 1 (2005)). +// Deep inside r0 the counts grow as r^(3 - gamma) = r^1.2; between 1 and 30 h^-1 Mpc their endpoint slope is ~1.9. +const PL = 'model, with the power law of Zehavi et al., ApJ 630, 1 (2005)'; +within('Correlation dimension deep inside r0 tends to 3 - gamma', H.D2powerLaw(0.01, 5, 1.8), 1.2, 1.01, PL); +check('The counts\' slope over 1-30 h^-1 Mpc is an average through the climb from 1.2 to 3', Math.abs(H.slopePowerLaw(1, 30, 5, 1.8) - 1.9) < 0.05, + `slope ${H.slopePowerLaw(1, 30, 5, 1.8).toFixed(2)}; D2 is ${H.D2powerLaw(1, 5, 1.8).toFixed(2)} at 1 and ${H.D2powerLaw(30, 5, 1.8).toFixed(2)} at 30 h^-1 Mpc`, PL); + +// 5. The homogeneity scale is a convention, not a length nature sets: it moves with the threshold, the tracer and the epoch. +const r1 = H.homogeneityScale(pc), r01 = H.homogeneityScale(pc, 1, { eps: 0.001 }); +const rz = [0, 0.5, 1, 2.4].map(z => H.homogeneityScale(pc, H.amplitude(pc, { z }))); +check('A ten times stricter threshold more than doubles R_H; R_H falls with redshift for matter', + r01 > 2 * r1 && rz.every((v, i) => i === 0 || v < rz[i - 1]), + `matter, z = 0: ${r1.toFixed(0)} (1%) and ${r01.toFixed(0)} (0.1%) h^-1 Mpc; z = 0, 0.5, 1, 2.4: ${rz.map(v => v.toFixed(0)).join(', ')}`, 'model'); +// The acoustic peak makes the linear D2 dip slightly near 113 h^-1 Mpc, so there R_H can jump. Nonlinear motions +// smooth the peak (Sigma ~ 8 h^-1 Mpc before reconstruction in BOSS, Beutler et al. 2017) and the jump goes away. +function largestJump(cos) { + const a = H.amplitude(cos, { z: 0.5, b2: 4 }); let prev = null, best = 0; + for (let f = 1; f <= 1.15001; f += 0.005) { const r = H.homogeneityScale(cos, a * f); if (prev !== null) best = Math.max(best, r - prev); prev = r; } + return best; +} +const jLin = largestJump(pc), jSm = largestJump(H.cosmology(H.PLANCK18, { sigmaNL: 7 })); +check('Linear theory: R_H jumps on the acoustic plateau; with the peak smoothed it does not', + jLin > 10 && jSm < 2, `b = 2, z = 0.5, real space: largest change for a 0.5% amplitude step ${jLin.toFixed(1)} h^-1 Mpc (linear), ${jSm.toFixed(1)} (Sigma = 7 h^-1 Mpc)`, 'model'); + +console.log(fails ? `\n${fails} check(s) failed` : '\nall checks pass'); +process.exit(fails ? 1 : 0);