1/** 2 * Represents and manipulates a quantum state on the Bloch sphere 3 * Upgraded to seamlessly support multi-qubit (N-qubit) simulations, 4 * exact density matrix math, and partial trace computations for entanglement. 5 * Fully optimized for performance and absolute mathematical integrity. 6 * 7 * Set QuantumState.DEBUG = true to enable verbose physical integrity logging. 8 */ 9class QuantumState { 10 constructor(numQubits = 1) { 11 this.qubits = numQubits; 12 this.resetToZero(); 13 } 14 15 /** @type {boolean} Enable verbose physical integrity console logging */ 16 static DEBUG = false; 17 18 /** 19 * Resets the quantum state to |00...0â© 20 */ 21 resetToZero() { 22 const size = Math.pow(2, this.qubits); 23 this.stateVector = Array(size).fill(0).map((_, i) => 24 i === 0 ? math.complex(1, 0) : math.complex(0, 0) 25 ); 26 this.normalize(); 27 this.validateStatePhysicalIntegrity(); 28 this.updateAlphaBeta(); 29 this.updateBlochCoordinates(); 30 } 31 32 /** 33 * Enforces strict physical normalization <Ï|Ï> = 1 to prevent numerical precision drift 34 */ 35 normalize() { 36 let normSq = 0; 37 const size = this.stateVector.length; 38 for (let i = 0; i < size; i++) { 39 const c = this.stateVector[i]; 40 normSq += math.re(c) * math.re(c) + math.im(c) * math.im(c); 41 } 42 43 const norm = Math.sqrt(normSq); 44 if (norm > 0) { 45 for (let i = 0; i < size; i++) { 46 this.stateVector[i] = math.divide(this.stateVector[i], norm); 47 } 48 } 49 } 50 51 /** 52 * Diagnostic self-validation suite. 53 * Asserts that state vector normalization <Ï|Ï> = 1 and the trace of each 54 * reduced density matrix Tr(Ï_k) = 1 are completely accurate to within 1e-6. 55 * @returns {Object} Physical integrity report 56 */ 57 validateStatePhysicalIntegrity() { 58 let normSq = 0; 59 const size = this.stateVector.length; 60 for (let i = 0; i < size; i++) { 61 const c = this.stateVector[i]; 62 normSq += math.re(c) * math.re(c) + math.im(c) * math.im(c); 63 } 64 65 const normIsValid = Math.abs(normSq - 1.0) < 1e-6; 66 const qubitIntegrity = []; 67 let rdmTraceIsValid = true; 68 69 for (let q = 0; q < this.qubits; q++) { 70 const rho = this.getReducedDensityMatrix(q); 71 const trace = math.re(math.add(rho[0][0], rho[1][1])); 72 const traceIsValid = Math.abs(trace - 1.0) < 1e-6; 73 if (!traceIsValid) rdmTraceIsValid = false; 74 75 qubitIntegrity.push({ 76 qubit: q, 77 trace: trace, 78 isValid: traceIsValid 79 }); 80 } 81 82 const integrity = { 83 norm: normSq, 84 normIsValid, 85 rdmTraceIsValid, 86 qubitIntegrity, 87 overallValid: normIsValid && rdmTraceIsValid 88 }; 89 90 // Only log in DEBUG mode to prevent console flooding in production 91 if (QuantumState.DEBUG) { 92 console.log("𧬠Quantum State Physical Integrity Check:", 93 `\n- Norm <Ï|Ï>: ${normSq.toFixed(8)} [${normIsValid ? "â VALID" : "â INVALID"}]`, 94 `\n- RDM Traces Valid: ${rdmTraceIsValid ? "â VALID" : "â INVALID"}` 95 ); 96 } 97 98 // Auto-correct if drift detected 99 if (!normIsValid) { 100 if (QuantumState.DEBUG) { 101 console.warn("â ï¸ Rounding drift detected. Applying normalization corrections."); 102 } 103 this.normalize(); 104 } 105 106 return integrity; 107 } 108 109 /** 110 * Updates alpha and beta for 1-qubit backward compatibility 111 */ 112 updateAlphaBeta() { 113 this.alpha = this.stateVector[0] || math.complex(1, 0); 114 this.beta = this.stateVector[1] || math.complex(0, 0); 115 } 116 117 /** 118 * Sets the quantum state from Ket notation string (primarily for 1-qubit) 119 * @param {string} ketString - Input in format like "0.7|0â© + 0.7i|1â©" 120 * @returns {boolean} True if successfully parsed, false otherwise 121 */ 122 setFromKetString(ketString) { 123 try { 124 // Security: Limit input string length to 100 characters to prevent ReDoS / CPU exhaustion 125 if (!ketString || typeof ketString !== 'string' || ketString.length > 100) { 126 throw new Error("Invalid input: expression is empty or exceeds length limit of 100 characters"); 127 } 128 129 const cleaned = ketString.replace(/\s+/g, ''); 130 // Pattern to match coefficients and ket symbols, like: 131 // (0.5+0.5i)|0> or -0.707i|1> or |0â© 132 const regex = /([^|01>â©]*)\s*(?:\|(0|1)â©|\|(0|1)>)/g; 133 let match; 134 let alpha = math.complex(0, 0); 135 let beta = math.complex(0, 0); 136 let matchFound = false;
137 138 while ((match = regex.exec(cleaned)) !== null) { 139 matchFound = true; 140 let coefStr = match[1] || ''; 141 const ketVal = match[2] || match[3]; // '0' or '1' 142 143 // Strip outer parentheses if present 144 coefStr = coefStr.replace(/^\s*\(\s*/, '').replace(/\s*\)\s*$/, ''); 145 if (coefStr.endsWith('*')) coefStr = coefStr.slice(0, -1); 146 147 let coef = math.complex(1, 0); 148 if (coefStr === '' || coefStr === '+') { 149 coef = math.complex(1, 0); 150 } else if (coefStr === '-') { 151 coef = math.complex(-1, 0); 152 } else { 153 // Security: Restrict allowed characters and mathematical terms 154 if (!/^[0-9.+\-*/()ijepncsotqrx\s]*$/i.test(coefStr)) { 155 throw new Error("Forbidden characters in coefficient expression"); 156 } 157 const words = coefStr.match(/[a-z]+/gi) || []; 158 const allowedWords = new Set(['i', 'j', 'e', 'pi', 'sin', 'cos', 'tan', 'sqrt', 'exp']); 159 for (const word of words) { 160 if (!allowedWords.has(word.toLowerCase())) { 161 throw new Error("Forbidden function or term in coefficient expression: " + word); 162 } 163 } 164 // Evaluate cleanly using Math.js 165 coef = math.evaluate(coefStr); 166 } 167 168 if (ketVal === '0') { 169 alpha = math.add(alpha, coef); 170 } else if (ketVal === '1') { 171 beta = math.add(beta, coef); 172 } 173 } 174 175 if (!matchFound) { 176 throw new Error("No valid state terms found"); 177 } 178 179 const norm = Math.sqrt( 180 math.add( 181 math.pow(math.abs(alpha), 2), 182 math.pow(math.abs(beta), 2) 183 ) 184 ); 185 186 if (norm === 0) { 187 throw new Error("State cannot be zero norm"); 188 } 189 190 this.qubits = 1; 191 this.stateVector = [ 192 math.divide(alpha, norm), 193 math.divide(beta, norm) 194 ]; 195 196 this.normalize(); 197 this.validateStatePhysicalIntegrity(); 198 this.updateAlphaBeta(); 199 this.updateBlochCoordinates(); 200 return true; 201 } catch (error) { 202 console.error("Error parsing ket notation:", error); 203 return false; 204 } 205 } 206 207 /** 208 * Calculate reduced density matrix for qubit k by tracing out all other qubits. 209 * @param {number} k - Index of the qubit to inspect (0 to N-1) 210 * @returns {Array<Array<math.Complex>>} A 2x2 complex density matrix 211 */ 212 getReducedDensityMatrix(k) { 213 const N = this.qubits; 214 const size = Math.pow(2, N); 215 const shift = N - 1 - k; 216 const mask = 1 << shift; 217 218 let r00 = math.complex(0, 0); 219 let r11 = math.complex(0, 0); 220 let r01 = math.complex(0, 0); 221 let r10 = math.complex(0, 0); 222 223 for (let i = 0; i < size; i++) { 224 const bitK = (i & mask) >> shift; 225 226 if (bitK === 0) { 227 const i0 = i; 228 const i1 = i | mask; 229 230 const psi0 = this.stateVector[i0]; 231 const psi1 = this.stateVector[i1]; 232 233 r00 = math.add(r00, math.multiply(psi0, math.conj(psi0))); 234 r11 = math.add(r11, math.multiply(psi1, math.conj(psi1))); 235 r01 = math.add(r01, math.multiply(psi0, math.conj(psi1))); 236 r10 = math.add(r10, math.multiply(psi1, math.conj(psi0))); 237 } 238 } 239 240 return [[r00, r01], [r10, r11]]; 241 } 242 243 /** 244 * Calculate Bloch sphere coordinates from quantum state for all qubits 245 */ 246 updateBlochCoordinates() { 247 this.qubitCoordinates = []; 248 249 for (let q = 0; q < this.qubits; q++) { 250 const rho = this.getReducedDensityMatrix(q); 251 252 const x = 2 * math.re(rho[0][1]); 253 const y = 2 * math.im(rho[0][1]); 254 const z = math.re(math.subtract(rho[0][0], rho[1][1])); 255 256 this.qubitCoordinates.push({ x, y, z }); 257 } 258 259 this.blochCoordinates = this.qubitCoordinates[0] || { x: 0, y: 0, z: 1 }; 260 } 261 262 /** 263 * Get a nice formatted string representation of the current state 264 */ 265 getKetString() { 266 const formatComplex = (c) => { 267 const re = math.round(math.re(c), 3); 268 const im = math.round(math.im(c), 3); 269 270 if (im === 0) { 271 if (re === 0) return '0'; 272 return re.toString(); 273 } 274 if (re === 0) { 275 if (im === 1) return 'i'; 276 if (im === -1) return '-i'; 277 return `${im}i`; 278 } 279 return `${re}${im >= 0 ? '+' : ''}${im === 1 ? 'i' : im === -1 ? '-i' : im + 'i'}`; 280 }; 281 282 let result = ''; 283 const size = Math.pow(2, this.qubits); 284 for (let i = 0; i < size; i++) { 285 const coef = this.stateVector[i]; 286 if (math.abs(coef) > 0.001) { 287 if (result.length > 0) result += ' + '; 288 const coefStr = formatComplex(coef); 289 const binaryStr = i.toString(2).padStart(this.qubits, '0'); 290 291 if (coefStr === '1') { 292 result += `|${binaryStr}â©`; 293 } else if (coefStr === '-1') { 294 result += `-|${binaryStr}â©`; 295 } else { 296 const needsParens = coefStr.includes('+') || 297 (coefStr.includes('-') && coefStr.indexOf('-') !== 0) || 298 coefStr.includes('i'); 299 if (needsParens) { 300 result += `(${coefStr})|${binaryStr}â©`; 301 } else { 302 result += `${coefStr}|${binaryStr}â©`; 303 } 304 } 305 } 306 } 307 308 return result || '0'; 309 } 310 311 /** 312 * Apply a single qubit gate on qubit index k using its 2x2 matrix 313 * Pair-wise shift logic reduces complexity to O(2^N) from Kronecker matrix multiplication's O(2^2N). 314 */ 315 applySingleQubitGate(k, U) { 316 const N = this.qubits; 317 const size = Math.pow(2, N); 318 const shift = N - 1 - k; 319 const mask = 1 << shift; 320 const nextState = new Array(size); 321 322 for (let i = 0; i < size; i++) { 323 const bitK = (i & mask) >> shift; 324 if (bitK === 0) { 325 const i0 = i; 326 const i1 = i | mask; 327 328 const psi0 = this.stateVector[i0]; 329 const psi1 = this.stateVector[i1]; 330 331 nextState[i0] = math.add( 332 math.multiply(U[0][0], psi0), 333 math.multiply(U[0][1], psi1) 334 ); 335 nextState[i1] = math.add( 336 math.multiply(U[1][0], psi0), 337 math.multiply(U[1][1], psi1) 338 ); 339 } 340 } 341 342 this.stateVector = nextState; 343 this.normalize(); 344 this.validateStatePhysicalIntegrity(); 345 this.updateAlphaBeta(); 346 this.updateBlochCoordinates(); 347 } 348 349 /** 350 * Apply a CNOT gate (controlled-NOT) 351 */ 352 applyCNOT(c, t) { 353 const N = this.qubits; 354 const size = Math.pow(2, N); 355 const maskC = 1 << (N - 1 - c); 356 const maskT = 1 << (N - 1 - t); 357 const nextState = [...this.stateVector]; 358 359 for (let i = 0; i < size; i++) { 360 if ((i & maskC) !== 0) { 361 if ((i & maskT) === 0) { 362 const i0 = i; 363 const i1 = i | maskT; 364 365 const temp = nextState[i0]; 366 nextState[i0] = nextState[i1]; 367 nextState[i1] = temp; 368 } 369 } 370 } 371 372 this.stateVector = nextState; 373 this.normalize(); 374 this.validateStatePhysicalIntegrity(); 375 this.updateAlphaBeta(); 376 this.updateBlochCoordinates(); 377 } 378 379 /** 380 * Apply a CZ gate (controlled-Z phase flip) 381 */ 382 applyCZ(c, t) { 383 const N = this.qubits; 384 const size = Math.pow(2, N); 385 const maskC = 1 << (N - 1 - c); 386 const maskT = 1 << (N - 1 - t); 387 388 for (let i = 0; i < size; i++) { 389 if ((i & maskC) !== 0 && (i & maskT) !== 0) { 390 this.stateVector[i] = math.multiply(-1, this.stateVector[i]); 391 } 392 } 393 394 this.normalize(); 395 this.validateStatePhysicalIntegrity(); 396 this.updateAlphaBeta(); 397 this.updateBlochCoordinates(); 398 } 399 400 /** 401 * Apply a SWAP gate between qubit 1 and qubit 2 402 */ 403 applySWAP(q1, q2) { 404 const N = this.qubits; 405 const size = Math.pow(2, N); 406 const mask1 = 1 << (N - 1 - q1); 407 const mask2 = 1 << (N - 1 - q2); 408 const nextState = [...this.stateVector]; 409 410 for (let i = 0; i < size; i++) { 411 const bit1 = (i & mask1) !== 0 ? 1 : 0; 412 const bit2 = (i & mask2) !== 0 ? 1 : 0; 413 414 if (bit1 !== bit2) { 415 if (bit1 === 0) { 416 const i0 = i; 417 const i1 = (i | mask1) & ~mask2; 418 419 const temp = nextState[i0]; 420 nextState[i0] = nextState[i1]; 421 nextState[i1] = temp; 422 } 423 } 424 } 425 426 this.stateVector = nextState; 427 this.normalize(); 428 this.validateStatePhysicalIntegrity(); 429 this.updateAlphaBeta(); 430 this.updateBlochCoordinates(); 431 } 432 433 /** 434 * Apply a Toffoli gate (controlled-controlled-NOT) 435 */ 436 applyToffoli(c1, c2, t) { 437 const N = this.qubits; 438 const size = Math.pow(2, N); 439 const maskC1 = 1 << (N - 1 - c1); 440 const maskC2 = 1 << (N - 1 - c2); 441 const maskT = 1 << (N - 1 - t); 442 const nextState = [...this.stateVector]; 443 444 for (let i = 0; i < size; i++) { 445 if ((i & maskC1) !== 0 && (i & maskC2) !== 0) { 446 if ((i & maskT) === 0) { 447 const i0 = i; 448 const i1 = i | maskT; 449 450 const temp = nextState[i0]; 451 nextState[i0] = nextState[i1]; 452 nextState[i1] = temp; 453 } 454 } 455 } 456 457 this.stateVector = nextState; 458 this.normalize(); 459 this.validateStatePhysicalIntegrity(); 460 this.updateAlphaBeta(); 461 this.updateBlochCoordinates(); 462 } 463 464 /** 465 * Apply a quantum gate to the current state targeting a specific qubit 466 * @param {string} gate - The gate to apply ('h', 'x', 'y', 'z', 's', 't', 'sdg', 'tdg', etc.) 467 * @param {number} targetQubit - Index of the target qubit (0 or 1) 468 */ 469 applyGate(gate, targetQubit = 0) { 470 const H = [ 471 [math.divide(1, math.sqrt(2)), math.divide(1, math.sqrt(2))], 472 [math.divide(1, math.sqrt(2)), math.divide(-1, math.sqrt(2))] 473 ]; 474 const X = [[0, 1], [1, 0]]; 475 const Y = [[0, math.complex(0, -1)], [math.complex(0, 1), 0]]; 476 const Z = [[1, 0], [0, -1]]; 477 const S = [[1, 0], [0, math.complex(0, 1)]]; 478 const T = [[1, 0], [0, math.exp(math.complex(0, Math.PI / 4))]]; 479 const I = [[1, 0], [0, 1]]; 480 const Sdg = [[1, 0], [0, math.complex(0, -1)]]; 481 const Tdg = [[1, 0], [0, math.exp(math.complex(0, -Math.PI / 4))]]; 482 483 switch (gate.toLowerCase()) { 484 case 'i': 485 this.applySingleQubitGate(targetQubit, I); 486 break; 487 case 'h': 488 this.applySingleQubitGate(targetQubit, H); 489 break; 490 case 'x': 491 this.applySingleQubitGate(targetQubit, X); 492 break; 493 case 'y': 494 this.applySingleQubitGate(targetQubit, Y); 495 break; 496 case 'z': 497 this.applySingleQubitGate(targetQubit, Z); 498 break; 499 case 's': 500 this.applySingleQubitGate(targetQubit, S); 501 break; 502 case 'sdg': 503 case 'sâ ': 504 this.applySingleQubitGate(targetQubit, Sdg); 505 break; 506 case 't': 507 this.applySingleQubitGate(targetQubit, T); 508 break; 509 case 'tdg': 510 case 'tâ ': 511 this.applySingleQubitGate(targetQubit, Tdg); 512 break; 513 default: 514 console.error(`Unknown gate: ${gate}`); 515 } 516 } 517 518 /** 519 * Apply a custom rotation to a specific qubit in the quantum state 520 * @param {number} theta - Theta angle in radians 521 * @param {number} phi - Phi angle in radians 522 * @param {number} targetQubit - Index of the target qubit 523 */ 524 applyRotation(theta, phi, targetQubit = 0) { 525 const e_neg_i_phi = math.exp(math.complex(0, -phi)); 526 const e_i_phi = math.exp(math.complex(0, phi)); 527 const U = [ 528 [math.complex(Math.cos(theta / 2), 0), math.multiply(-Math.sin(theta / 2), e_neg_i_phi)], 529 [math.multiply(Math.sin(theta / 2), e_i_phi), math.complex(Math.cos(theta / 2), 0)] 530 ]; 531 this.applySingleQubitGate(targetQubit, U); 532 } 533 534 /** 535 * Apply a parameterized phase shift gate (Rphi) to a specific qubit 536 * @param {number} phi - Phase shift angle in radians 537 * @param {number} targetQubit - Index of the target qubit 538 */ 539 applyPhaseShift(phi, targetQubit = 0) { 540 const U = [ 541 [math.complex(1, 0), math.complex(0, 0)], 542 [math.complex(0, 0), math.exp(math.complex(0, phi))] 543 ]; 544 this.applySingleQubitGate(targetQubit, U); 545 } 546 547 548 /** 549 * Collapse a specific qubit based on Born's rule probabilities. 550 * @param {number} k - Index of the qubit to collapse 551 * @param {number|null} forcedOutcome - If specified, forces the collapse outcome (0 or 1) 552 * @returns {number} The outcome of the collapse (0 or 1) 553 */ 554 collapseQubit(k, forcedOutcome = null) { 555 const rho = this.getReducedDensityMatrix(k); 556 const p0 = Math.max(0, Math.min(1.0, math.re(rho[0][0]))); 557 558 let outcome = forcedOutcome; 559 if (outcome === null) { 560 outcome = Math.random() < p0 ? 0 : 1; 561 } 562 563 const N = this.qubits; 564 const size = Math.pow(2, N); 565 const shift = N - 1 - k; 566 const mask = 1 << shift; 567 568 for (let i = 0; i < size; i++) { 569 const bitK = (i & mask) >> shift; 570 if (bitK !== outcome) { 571 this.stateVector[i] = math.complex(0, 0); 572 } 573 } 574 575 this.normalize(); 576 this.validateStatePhysicalIntegrity(); 577 this.updateAlphaBeta(); 578 this.updateBlochCoordinates(); 579 580 return outcome; 581 } 582}
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.