1// ============================================================ 2// chiGrassmannianCI.js 3// 4// Computes Ï(Ω^j_Z(t)) for a complete intersection Z of given 5// degrees in the Grassmannian Gr(k, n), using the f_λ(t) 6// hook-length formula and the Koszul/conormal recurrence. 7// 8// Exports: chiCI(k, n, degrees, t = 0) 9// Returns an array of length (dim Z + 1) with 10// [Ï(Ω^0_Z(t)), Ï(Ω^1_Z(t)), ..., Ï(Ω^{dim}_Z(t))]. 11// ============================================================ 12 13// --- Enumerate all partitions λ in the k à m box with |λ| = target. 14// Yields arrays (non-increasing, first entry ⤠m, length ⤠k). 15export function* partitionsInBox(k, m, target, maxFirst = m, len = 0, partial = []) { 16 if (target === 0) { yield partial.slice(); return; } 17 if (len === k || target <= 0) return; 18 const hi = Math.min(maxFirst, target); 19 for (let v = hi; v >= 1; v--) { 20 partial.push(v); 21 yield* partitionsInBox(k, m, target - v, v, len + 1, partial); 22 partial.pop(); 23 } 24} 25 26// --- Conjugate (transpose) of λ, padded to length m. 27function conjugate(lambda, m) { 28 const t = new Array(m).fill(0); 29 for (let a = 0; a < lambda.length; a++) { 30 for (let b = 0; b < lambda[a]; b++) t[b]++; 31 } 32 return t; 33} 34 35// --- Hook length h_λ(a, b) for 1-indexed a â [1,k], b â [1,m]. 36// λ is 0-indexed (λ[0] = λ_1, etc.); λ_a = 0 for a > len(λ). 37// λ^T is already computed and 0-indexed. 38function hookLength(lambda, lambdaT, a, b) { 39 const la = a <= lambda.length ? lambda[a - 1] : 0; 40 const lb = b <= lambdaT.length ? lambdaT[b - 1] : 0; 41 return la + lb - a - b + 1; 42} 43 44// --- Exact BigInt rational arithmetic helpers --- 45 46export function gcdBig(a, b) { 47 if (a < 0n) a = -a; 48 if (b < 0n) b = -b; 49 while (b) { const t = b; b = a % b; a = t; } 50 return a; 51} 52 53// Reduce [num, den] to lowest terms with den > 0. 54export function reduce(num, den) { 55 if (num === 0n) return [0n, 1n]; 56 const g = gcdBig(num < 0n ? -num : num, den < 0n ? -den : den); 57 num /= g; den /= g; 58 if (den < 0n) { num = -num; den = -den; } 59 return [num, den]; 60} 61 62export function addFrac([a, b], [c, d]) { 63 return reduce(a * d + c * b, b * d); 64} 65 66export function subFrac([a, b], [c, d]) { 67 return reduce(a * d - c * b, b * d); 68} 69 70// --- f_λ(t): product over all (a,b) in [1,k]Ã[1,m]. 71// Uses exact BigInt arithmetic to avoid float64 overflow on large Grassmannians. 72// If any h_λ(a,b) = 0: returns 1 when t = 0, else returns 0. 73export function fLambdaExact(lambda, k, m, t) { 74 const lT = conjugate(lambda, m); 75 const tB = BigInt(t); 76 let num = 1n; 77 let den = 1n; 78 for (let a = 1; a <= k; a++) { 79 for (let b = 1; b <= m; b++) { 80 const h = hookLength(lambda, lT, a, b); 81 if (h === 0) { 82 if (t !== 0) return [0n, 1n]; 83 // h = 0, t = 0: factor (h-t)/h = 0/0 â L'Hôpital â 1; skip it 84 } else { 85 const hB = BigInt(h); 86 num *= (hB - tB); 87 den *= hB; 88 } 89 } 90 } 91 return reduce(num, den); 92} 93 94// --- Ï(Ω^j_X(t)) for the Grassmannian X = Gr(k,n). 95// = (-1)^j * Σ_{λ : |λ|=j, λ in kÃm box} f_λ(t) 96// Returns a BigInt fraction [num, den]. 97export function chiGrassmannianExact(k, n, j, t) { 98 const m = n - k; 99 if (j < 0 || j > k * m) return [0n, 1n]; 100 let sum = [0n, 1n]; 101 for (const lambda of partitionsInBox(k, m, j)) { 102 sum = addFrac(sum, fLambdaExact(lambda, k, m, t)); 103 } 104 const sign = j % 2 === 0 ? 1n : -1n; 105 return [sign * sum[0], sum[1]]; 106} 107 108// --- Memoized recurrence for the CI. 109// Ï(Ω^j_{Z_s}(t)) = Ï(Ω^j_{Z_{s-1}}(t)) 110// - Ï(Ω^j_{Z_{s-1}}(t - d_s)) 111// - Ï(Ω^{j-1}_{Z_s}(t - d_s)) 112// All values are BigInt fractions [num, den]. 113function buildChiCI(k, n, degrees) { 114 const cache = new Map(); 115 116 function chi(s, j, t) { 117 if (j < 0) return [0n, 1n]; 118 const key = `${s},${j},${t}`; 119 if (cache.has(key)) return cache.get(key); 120 let val; 121 if (s === 0) { 122 val = chiGrassmannianExact(k, n, j, t); 123 } else { 124 const d = degrees[s - 1]; 125 val = subFrac( 126 subFrac(chi(s - 1, j, t), chi(s - 1, j, t - d)), 127 chi(s, j - 1, t - d) 128 ); 129 } 130 cache.set(key, val); 131 return val; 132 } 133 134 return chi; 135} 136 137// Convert a BigInt fraction to Number. For integer fractions (den divides num), 138// returns the exact integer; otherwise falls back to floating-point division. 139export function fracToNumber([num, den]) { 140 if (den === 1n) return Number(num); 141 // Exact integer check 142 if (num % den === 0n) return Number(num / den); 143 return Number(num) / Number(den); 144} 145 146// --- Main export. 147// Returns [Ï(Ω^0_Z(t)), ..., Ï(Ω^{dim Z}_Z(t))]. 148export function chiCI(k, n, degrees, t = 0) { 149 const dim = k * (n - k) - degrees.length; 150 if (dim < 0) throw new Error("Too many equations: dim Z < 0"); 151 const chi = buildChiCI(k, n, degrees); 152 const r = degrees.length; 153 return Array.from({ length: dim + 1 }, (_, j) => fracToNumber(chi(r, j, t))); 154} 155 156// --- Primitive middle-row Hodge numbers for rendering. 157// Returns an array of length floor(dim/2)+1 where entry j is 158// h^{dim-j, j}_prim = (-1)^{dim-j} * (Ï(Ω^j_Z) - (-1)^j * a_j) 159// and a_j = #{partitions in kÃm box of size j}. 160// 161// This is the format consumed by constructMiddleRow/renderDiamond in 162// grassmannian.js: renderDiamond adds h^{j,j}(Gr(k,n)) on top for the 163// diagonal entry when dim is even. 164// --- Full Hodge diamond for a CI Z in Gr(k,n). 165// Returns a 2D array in the same format as hodgeGrassmannian: 166// 2*dim+1 rows; row i has min(i+1, 2*dim+1-i) entries. 167// Non-middle rows: h^{p,p} from Grassmannian (Lefschetz + Hodge duality), all else 0. 168// Middle row: primitive Hodge numbers + Grassmannian diagonal contribution. 169 170// Module-level cache: keyed on "k,n,[d1,d2,...]" so repeated calls with the 171// same parameters (e.g. adjusting only the blow-up count s) skip recomputation. 172const _hodgeCICache = new Map() 173 174export function hodgeDiamondCI(k, n, degrees) { 175 const _cacheKey = `${k},${n},${JSON.stringify(degrees)}` 176 if (_hodgeCICache.has(_cacheKey)) return _hodgeCICache.get(_cacheKey) 177 178 const m = n - k; 179 const dim = k * m - degrees.length; 180 if (dim < 0) throw new Error("dim Z < 0"); 181 182 // a[j] = #{partitions in kÃm box of size j} = h^{j,j}(Gr(k,n)) 183 const a = []; 184 for (let j = 0; j <= dim; j++) {
185 let count = 0; 186 for (const _ of partitionsInBox(k, m, j)) count++; 187 a.push(count); 188 } 189 190 // Build full middle row from primitive values + Grassmannian diagonal. 191 // Grassmannian contributes a[half] only at j=half when dim is even. 192 const prim = hodgePrimitiveMiddleRow(k, n, degrees); 193 const half = Math.floor(dim / 2); 194 const isOdd = dim % 2 === 1; 195 const midHalf = prim.map((p, j) => 196 p + (!isOdd && j === half ? a[half] : 0) 197 ); 198 const midFull = [...midHalf]; 199 if (isOdd) { 200 for (let j = half; j >= 0; j--) midFull.push(midHalf[j]); 201 } else { 202 for (let j = half - 1; j >= 0; j--) midFull.push(midHalf[j]); 203 } 204 205 const totalRows = 2 * dim + 1; 206 const diamond = []; 207 for (let i = 0; i < totalRows; i++) { 208 const rowSize = i <= dim ? i + 1 : 2 * dim - i + 1; 209 const row = new Array(rowSize).fill(0); 210 if (i === dim) { 211 for (let j = 0; j < rowSize; j++) row[j] = midFull[j]; 212 } else { 213 // Non-middle: only diagonal entry h^{p,p} is nonzero. 214 // mirrorI = row index in the "above-middle" half (same for below by Serre duality). 215 // colJ = mirrorI/2 is the column of the diagonal entry (if mirrorI is even). 216 const mirrorI = i < dim ? i : 2 * dim - i; 217 if (mirrorI % 2 === 0) { 218 const colJ = mirrorI / 2; 219 if (colJ < rowSize) row[colJ] = a[colJ]; 220 } 221 } 222 diamond.push(row); 223 } 224 _hodgeCICache.set(_cacheKey, diamond) 225 return diamond; 226} 227 228export function hodgePrimitiveMiddleRow(k, n, degrees) { 229 const m = n - k; 230 const dim = k * m - degrees.length; 231 if (dim < 0) throw new Error("dim Z < 0"); 232 const chi = buildChiCI(k, n, degrees); 233 const r = degrees.length; 234 const half = Math.floor(dim / 2); 235 const result = []; 236 for (let j = 0; j <= half; j++) { 237 const chiVal = fracToNumber(chi(r, j, 0)); 238 let a_j = 0; 239 for (const _ of partitionsInBox(k, m, j)) a_j++; 240 const sign = ((dim - j) % 2 === 0) ? 1 : -1; 241 const chiSign = (j % 2 === 0) ? 1 : -1; 242 result.push(Math.round(sign * (chiVal - chiSign * a_j)) + 0); 243 } 244 return result; 245} 246 247// Returns the Hodge diamond of Bl_s(Z), the blow-up of Z at s points in 248// general position. Formula: h^{p,p} += s for 1 <= p <= dim-1; all other 249// Hodge numbers unchanged. diamond is the 2D array from hodgeDiamondCI. 250export function applyBlowUp(diamond, s, dim) { 251 const result = diamond.map(row => row.slice()); 252 if (s <= 0 || dim <= 1) return result; 253 for (let p = 1; p <= dim - 1; p++) { 254 const rowIdx = 2 * p; 255 // mirrorI = min(2p, 2*(dim-p)) = 2*min(p, dim-p), so colJ = min(p, dim-p) 256 const colJ = Math.min(p, dim - p); 257 result[rowIdx][colJ] += s; 258 } 259 return result; 260}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.