From f9a5941e9b05eab3d7829ba29cc1a3161b9fcde9 Mon Sep 17 00:00:00 2001 From: Daniel Strano Date: Fri, 12 Jul 2024 07:32:07 -0400 Subject: [PATCH] QInterfaceNoisy (#1014) * Incomplete work towards QInterfaceNoisy * Nearly compiles * Compiles (does not apply noise) * Apply1QbNoise() method * Fix copy constructor * Add/fix noise * Auto fidelity calculation (note last commit had ChatGPT help) * Noisy interface in shared library * Default 1% noise per gate * Debug * SetNoiseParameter() * Fix edge case in applying noise --- CMakeLists.txt | 4 +- README.md | 4 +- doxygen.config | 2 +- include/pinvoke_api.hpp | 3 +- include/qfactory.hpp | 22 +- include/qinterface.hpp | 19 +- include/qinterface_noisy.hpp | 380 +++++++++++++++++++++++++++++++++++ include/qtensornetwork.hpp | 7 +- include/wasm_api.hpp | 7 +- src/pinvoke_api.cpp | 17 +- src/qinterface_noisy.cpp | 27 +++ src/wasm_api.cpp | 13 +- 12 files changed, 493 insertions(+), 12 deletions(-) create mode 100644 include/qinterface_noisy.hpp create mode 100644 src/qinterface_noisy.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e2186f57..7473d5d6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required (VERSION 3.9) -project (Qrack VERSION 9.8.3 DESCRIPTION "High Performance Quantum Bit Simulation" LANGUAGES CXX) +project (Qrack VERSION 9.9.0 DESCRIPTION "High Performance Quantum Bit Simulation" LANGUAGES CXX) # Installation commands include (GNUInstallDirs) @@ -44,6 +44,7 @@ add_library (qrack STATIC src/qstabilizerhybrid.cpp src/qcircuit.cpp src/qtensornetwork.cpp + src/qinterface_noisy.cpp ) set_property(TARGET qrack PROPERTY POSITION_INDEPENDENT_CODE ON) if (ENABLE_PTHREAD AND ENABLE_QUNIT_CPU_PARALLEL) @@ -389,6 +390,7 @@ install (FILES include/qengine_cuda.hpp include/qtensornetwork.hpp include/qinterface.hpp + include/qinterface_noisy.hpp include/qalu.hpp include/qparity.hpp include/qneuron.hpp diff --git a/README.md b/README.md index 6facd1aaf..3e9616b47 100644 --- a/README.md +++ b/README.md @@ -250,13 +250,15 @@ Set the maximum allowed allocation (in MB) for the global OpenCL pool with `QRAC Note that this controls total direct Qrack OpenCL buffer allocation, not total Qrack library allocation. Total OpenCL buffer allocation is **not** fully indicative of total library allocation. -## Approximation options +## Approximation and noise options `QUnit` can optionally round qubit subsystems proactively or on-demand to the nearest single or double qubit eigenstate with the `QRACK_QUNIT_SEPARABILITY_THRESHOLD=[0.0 - 1.0]` environment variable, with a value between `0.0` and `1.0`. When trying to find separable subsystems, Qrack will start by making 3-axis (independent or conditional) probability measurements. Based on the probability measurements, under the assumption that the state _is_ separable, an inverse state preparation to |0> procedure is fixed. If inverse state preparation would bring any single qubit Bloch sphere projection within parameter range of the edge of the Bloch sphere, (with unit length, `1.0`,) then the subsystem will be rounded to that state, normalized, and then "uncomputed" with the corresponding (forward) state preparation, effectively "hyperpolarizing" one and two qubit separable substates by replacing entanglement with local qubit Bloch sphere extent. (If 3-axis probability is _not_ within rounding range, nothing is done directly to the substate.) Similarly functionality to above is available for `QBdt` with `QRACK_QBDT_SEPARABILITY_THRESHOLD=[0.0 - 0.5]`. In the case of this parameter, any branch with less than the parameter value for probability is rounded to 0, and its partner branch is renormalized to unit length. This same value is also used for branch equality comparison. Environment variable `QRACK_NONCLIFFORD_ROUNDING_THRESHOLD` sets the non-Clifford phase gate magnitude, as a fraction of `T` gate phase angle, (from the closest Clifford state Bloch sphere orientation,) that will be rounded to 0 in terminal measurement and sampling operations. (For `0`/default value, all non-Clifford phase gates are exactly preserved.) +For single-qubit depolarizing noise channels, `QInterfaceNoisy` exposes environment variable `QRACK_GATE_DEPOLARIZATION`, which is a per-gate, single-qubit (applied to all qubits in gate) noise parameter for depolarizing noise, typically taking a floating-point value between `0` and `1`. + ## Vectorization optimization ```sh diff --git a/doxygen.config b/doxygen.config index 68aba28dc..f912cf0ea 100644 --- a/doxygen.config +++ b/doxygen.config @@ -38,7 +38,7 @@ PROJECT_NAME = "Qrack" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 9.8 +PROJECT_NUMBER = 9.9 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/include/pinvoke_api.hpp b/include/pinvoke_api.hpp index 2e3cc8743..2672aacc2 100644 --- a/include/pinvoke_api.hpp +++ b/include/pinvoke_api.hpp @@ -38,7 +38,7 @@ extern "C" { // non-quantum MICROSOFT_QUANTUM_DECL int get_error(_In_ uintq sid); MICROSOFT_QUANTUM_DECL uintq init_count_type(_In_ uintq q, _In_ bool tn, _In_ bool md, _In_ bool sd, _In_ bool sh, - _In_ bool bdt, _In_ bool pg, _In_ bool zxf, _In_ bool hy, _In_ bool oc, _In_ bool dm); + _In_ bool bdt, _In_ bool pg, _In_ bool nw, _In_ bool hy, _In_ bool oc, _In_ bool dm); MICROSOFT_QUANTUM_DECL uintq init_count(_In_ uintq q, _In_ bool dm); MICROSOFT_QUANTUM_DECL uintq init_count_pager(_In_ uintq q, _In_ bool dm); MICROSOFT_QUANTUM_DECL uintq init() { return init_count(0, false); } @@ -343,6 +343,7 @@ MICROSOFT_QUANTUM_DECL void SetSdrp(_In_ uintq sid, _In_ double sdrp); MICROSOFT_QUANTUM_DECL void SetNcrp(_In_ uintq sid, _In_ double ncrp); MICROSOFT_QUANTUM_DECL void SetReactiveSeparate(_In_ uintq sid, _In_ bool irs); MICROSOFT_QUANTUM_DECL void SetTInjection(_In_ uintq sid, _In_ bool iti); +MICROSOFT_QUANTUM_DECL void SetNoiseParameter(_In_ uintq sid, _In_ double np); #if !(FPPOW < 6 && !defined(ENABLE_COMPLEX_X2)) MICROSOFT_QUANTUM_DECL void TimeEvolve(_In_ uintq sid, _In_ double t, _In_ uintq n, diff --git a/include/qfactory.hpp b/include/qfactory.hpp index a832b9bc9..8e2f6c70b 100644 --- a/include/qfactory.hpp +++ b/include/qfactory.hpp @@ -38,6 +38,8 @@ #include "qbdthybrid.hpp" #endif +#include "qinterface_noisy.hpp" + namespace Qrack { /** Factory method to create specific engine implementations. */ @@ -83,6 +85,8 @@ QInterfacePtr CreateQuantumInterface( #endif case QINTERFACE_CPU: return std::make_shared(args...); + case QINTERFACE_NOISY: + return std::make_shared(engines, args...); default: throw std::invalid_argument("CreateQuantumInterface received a request to create a nonexistent type instance!"); } @@ -129,6 +133,8 @@ QInterfacePtr CreateQuantumInterface(QInterfaceEngine engine1, QInterfaceEngine #endif case QINTERFACE_CPU: return std::make_shared(args...); + case QINTERFACE_NOISY: + return std::make_shared(engines, args...); default: throw std::invalid_argument("CreateQuantumInterface received a request to create a nonexistent type instance!"); } @@ -171,6 +177,8 @@ template QInterfacePtr CreateQuantumInterface(QInterfaceEngine #endif case QINTERFACE_CPU: return std::make_shared(args...); + case QINTERFACE_NOISY: + return std::make_shared(args...); default: throw std::invalid_argument("CreateQuantumInterface received a request to create a nonexistent type instance!"); } @@ -237,6 +245,8 @@ template QInterfacePtr CreateQuantumInterface(std::vector(args...); + case QINTERFACE_NOISY: + return std::make_shared(engines, args...); default: throw std::invalid_argument("CreateQuantumInterface received a request to create a nonexistent type instance!"); } @@ -248,7 +258,8 @@ template QInterfacePtr CreateQuantumInterface(std::vector -QInterfacePtr CreateArrangedLayers(bool md, bool sd, bool sh, bool bdt, bool pg, bool tn, bool hy, bool oc, Ts... args) +QInterfacePtr CreateArrangedLayersFull( + bool nw, bool md, bool sd, bool sh, bool bdt, bool pg, bool tn, bool hy, bool oc, Ts... args) { #if ENABLE_OPENCL || ENABLE_CUDA bool isOcl = oc && (DEVICE_COUNT > 0); @@ -294,6 +305,10 @@ QInterfacePtr CreateArrangedLayers(bool md, bool sd, bool sh, bool bdt, bool pg, simulatorType.push_back(QINTERFACE_TENSOR_NETWORK); } + if (nw) { + simulatorType.push_back(QINTERFACE_NOISY); + } + // (...then reverse:) std::reverse(simulatorType.begin(), simulatorType.end()); @@ -315,5 +330,10 @@ QInterfacePtr CreateArrangedLayers(bool md, bool sd, bool sh, bool bdt, bool pg, return CreateQuantumInterface(simulatorType, args...); } +template +QInterfacePtr CreateArrangedLayers(bool md, bool sd, bool sh, bool bdt, bool pg, bool tn, bool hy, bool oc, Ts... args) +{ + return CreateArrangedLayersFull(false, md, sd, sh, bdt, pg, tn, hy, oc, args...); +} } // namespace Qrack diff --git a/include/qinterface.hpp b/include/qinterface.hpp index 1a90bc0b8..f0dd662f0 100644 --- a/include/qinterface.hpp +++ b/include/qinterface.hpp @@ -102,10 +102,15 @@ enum QInterfaceEngine { QINTERFACE_QUNIT_CLIFFORD, /** - * Circuit-simplification layer, with (optional) recourse to cuTensorNetwork + * Circuit-simplification layer */ QINTERFACE_TENSOR_NETWORK, + /** + * Noisy wrapper layer + */ + QINTERFACE_NOISY, + #if ENABLE_OPENCL || ENABLE_CUDA QINTERFACE_OPTIMAL_SCHROEDINGER = QINTERFACE_QPAGER, @@ -2910,6 +2915,18 @@ class QInterface : public ParallelFor { * helps. */ virtual bool GetTInjection() { return false; } + /** + * Set the noise level option (only for a noisy interface) + * + * If this is a QInterfaceNoisy, adjust the noise level per gate. + */ + virtual void SetNoiseParameter(real1_f lambda) {} + /** + * Get the noise level option (only for a noisy interface) + * + * If this is a QInterfaceNoisy, return the noise level per gate. + */ + virtual real1_f GetNoiseParameter() { return ZERO_R1_F; } /** * Clone this QInterface diff --git a/include/qinterface_noisy.hpp b/include/qinterface_noisy.hpp new file mode 100644 index 000000000..9343d5479 --- /dev/null +++ b/include/qinterface_noisy.hpp @@ -0,0 +1,380 @@ +////////////////////////////////////////////////////////////////////////////////////// +// +// (C) Daniel Strano and the Qrack contributors 2017-2023. All rights reserved. +// +// This is a multithreaded, universal quantum register simulation, allowing +// (nonphysical) register cloning and direct measurement of probability and +// phase, to leverage what advantages classical emulation of qubits can have. +// +// Licensed under the GNU Lesser General Public License V3. +// See LICENSE.md in the project root or https://www.gnu.org/licenses/lgpl-3.0.en.html +// for details. +#pragma once + +#if ENABLE_ALU +#include "qalu.hpp" +#endif + +namespace Qrack { + +class QInterfaceNoisy; +typedef std::shared_ptr QInterfaceNoisyPtr; + +/** + * A "Qrack::QInterfaceNoisy" that wraps any other QInterface with a simple noise model + */ +class QInterfaceNoisy : public QInterface { +protected: + double logFidelity; + real1_f noiseParam; + QInterfacePtr engine; + std::vector engines; + + void Apply1QbNoise(bitLenInt qb) + { + real1_f n = noiseParam; +#if ENABLE_ENV_VARS + if (getenv("QRACK_GATE_DEPOLARIZATION")) { + n = (real1_f)std::stof(std::string(getenv("QRACK_GATE_DEPOLARIZATION"))); + } +#endif + if (n <= ZERO_R1_F) { + return; + } + engine->DepolarizingChannelWeak1Qb(qb, n); + if ((n + FP_NORM_EPSILON) >= ONE_R1_F) { + logFidelity = -1 * std::numeric_limits::infinity(); + } else { + logFidelity += (double)log(ONE_R1_F - n); + } + } + +public: + QInterfaceNoisy(bitLenInt qBitCount, bitCapInt initState = ZERO_BCI, qrack_rand_gen_ptr rgp = nullptr, + complex phaseFac = CMPLX_DEFAULT_ARG, bool doNorm = false, bool randomGlobalPhase = true, + bool useHostMem = false, int64_t deviceId = -1, bool useHardwareRNG = true, bool useSparseStateVec = false, + real1_f norm_thresh = REAL1_EPSILON, std::vector devList = {}, bitLenInt qubitThreshold = 0U, + real1_f separation_thresh = FP_NORM_EPSILON_F) + : QInterfaceNoisy({ QINTERFACE_TENSOR_NETWORK }, qBitCount, initState, rgp, phaseFac, doNorm, randomGlobalPhase, + useHostMem, deviceId, useHardwareRNG, useSparseStateVec, norm_thresh, devList, qubitThreshold, + separation_thresh) + { + // Intentionally left blank; + } + + QInterfaceNoisy(std::vector eng, bitLenInt qBitCount, bitCapInt initState = ZERO_BCI, + qrack_rand_gen_ptr rgp = nullptr, complex phaseFac = CMPLX_DEFAULT_ARG, bool doNorm = false, + bool randomGlobalPhase = true, bool useHostMem = false, int64_t deviceId = -1, bool useHardwareRNG = true, + bool useSparseStateVec = false, real1_f norm_thresh = REAL1_EPSILON, std::vector devList = {}, + bitLenInt qubitThreshold = 0U, real1_f separation_thresh = FP_NORM_EPSILON_F); + + QInterfaceNoisy(QInterfaceNoisy* o) + : noiseParam(o->noiseParam) + , engine(o->engine) + , engines(o->engines) + { + engine = o->engine->Clone(); + } + + void SetNoiseParameter(real1_f lambda) { noiseParam = lambda; } + real1_f GetNoiseParameter() { return noiseParam; } + + double GetUnitaryFidelity() { return (double)exp(logFidelity); } + void ResetUnitaryFidelity() { logFidelity = 0.0; } + + void SetQubitCount(bitLenInt qb) + { + QInterface::SetQubitCount(qb); + engine->SetQubitCount(qb); + } + + bool isOpenCL() { return engine->isOpenCL(); } + + void SetConcurrency(uint32_t threadCount) + { + QInterface::SetConcurrency(threadCount); + engine->SetConcurrency(GetConcurrencyLevel()); + } + + real1_f ProbReg(bitLenInt start, bitLenInt length, bitCapInt permutation) + { + return engine->ProbReg(start, length, permutation); + } + + bitLenInt Allocate(bitLenInt start, bitLenInt length) + { + SetQubitCount(qubitCount + length); + return engine->Allocate(start, length); + } + using QInterface::Compose; + bitLenInt Compose(QInterfaceNoisyPtr toCopy) + { + SetQubitCount(qubitCount + toCopy->qubitCount); + return engine->Compose(toCopy->engine); + } + bitLenInt Compose(QInterfacePtr toCopy) { return Compose(std::dynamic_pointer_cast(toCopy)); } + bitLenInt Compose(QInterfaceNoisyPtr toCopy, bitLenInt start) + { + SetQubitCount(qubitCount + toCopy->qubitCount); + return engine->Compose(toCopy->engine, start); + } + bitLenInt Compose(QInterfacePtr toCopy, bitLenInt start) + { + return Compose(std::dynamic_pointer_cast(toCopy), start); + } + bitLenInt ComposeNoClone(QInterfaceNoisyPtr toCopy) + { + SetQubitCount(qubitCount + toCopy->qubitCount); + return engine->ComposeNoClone(toCopy->engine); + } + bitLenInt ComposeNoClone(QInterfacePtr toCopy) + { + return ComposeNoClone(std::dynamic_pointer_cast(toCopy)); + } + using QInterface::Decompose; + void Decompose(bitLenInt start, QInterfacePtr dest) + { + Decompose(start, std::dynamic_pointer_cast(dest)); + } + QInterfacePtr Decompose(bitLenInt start, bitLenInt length) + { + QInterfaceNoisyPtr dest = std::make_shared(this); + engine->Decompose(start, dest->engine); + + return dest; + } + bool TryDecompose(bitLenInt start, QInterfacePtr dest, real1_f error_tol = TRYDECOMPOSE_EPSILON) + { + return TryDecompose(start, std::dynamic_pointer_cast(dest), error_tol); + } + void Decompose(bitLenInt start, QInterfaceNoisyPtr dest) + { + engine->Decompose(start, dest->engine); + SetQubitCount(qubitCount - dest->GetQubitCount()); + } + void Dispose(bitLenInt start, bitLenInt length) + { + engine->Dispose(start, length); + SetQubitCount(qubitCount - length); + } + void Dispose(bitLenInt start, bitLenInt length, bitCapInt disposedPerm) + { + engine->Dispose(start, length, disposedPerm); + SetQubitCount(qubitCount - length); + } + + bool TryDecompose(bitLenInt start, QInterfaceNoisyPtr dest, real1_f error_tol = TRYDECOMPOSE_EPSILON) + { + const bitLenInt nQubitCount = qubitCount - dest->GetQubitCount(); + const bool result = engine->TryDecompose(start, dest->engine, error_tol); + if (result) { + SetQubitCount(nQubitCount); + } + return result; + } + + void SetQuantumState(const complex* inputState) { engine->SetQuantumState(inputState); } + void GetQuantumState(complex* outputState) { engine->GetQuantumState(outputState); } + void GetProbs(real1* outputProbs) { engine->GetProbs(outputProbs); } + complex GetAmplitude(bitCapInt perm) { return engine->GetAmplitude(perm); } + void SetAmplitude(bitCapInt perm, complex amp) { engine->SetAmplitude(perm, amp); } + void SetPermutation(bitCapInt perm, complex phaseFac = CMPLX_DEFAULT_ARG) + { + engine->SetPermutation(perm, phaseFac); + } + + void Mtrx(const complex* mtrx, bitLenInt qubitIndex) + { + engine->Mtrx(mtrx, qubitIndex); + Apply1QbNoise(qubitIndex); + } + void Phase(complex topLeft, complex bottomRight, bitLenInt qubitIndex) + { + engine->Phase(topLeft, bottomRight, qubitIndex); + Apply1QbNoise(qubitIndex); + } + void Invert(complex topRight, complex bottomLeft, bitLenInt qubitIndex) + { + engine->Invert(topRight, bottomLeft, qubitIndex); + Apply1QbNoise(qubitIndex); + } + void MCMtrx(const std::vector& controls, const complex* mtrx, bitLenInt target) + { + engine->MCMtrx(controls, mtrx, target); + Apply1QbNoise(target); + } + void MACMtrx(const std::vector& controls, const complex* mtrx, bitLenInt target) + { + engine->MACMtrx(controls, mtrx, target); + Apply1QbNoise(target); + } + + using QInterface::UniformlyControlledSingleBit; + void UniformlyControlledSingleBit(const std::vector& controls, bitLenInt qubitIndex, + const complex* mtrxs, const std::vector mtrxSkipPowers, bitCapInt mtrxSkipValueMask) + { + engine->UniformlyControlledSingleBit(controls, qubitIndex, mtrxs, mtrxSkipPowers, mtrxSkipValueMask); + Apply1QbNoise(qubitIndex); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + + void XMask(bitCapInt mask) + { + engine->XMask(mask); + bitCapInt v = mask; + while (bi_compare_0(mask) != 0) { + v = v & (v - ONE_BCI); + Apply1QbNoise(log2(mask ^ v)); + mask = v; + } + } + void PhaseParity(real1_f radians, bitCapInt mask) + { + engine->PhaseParity(radians, mask); + bitCapInt v = mask; + while (bi_compare_0(mask) != 0) { + v = v & (v - ONE_BCI); + Apply1QbNoise(log2(mask ^ v)); + mask = v; + } + } + + real1_f CProb(bitLenInt control, bitLenInt target) { return engine->CProb(control, target); } + real1_f ACProb(bitLenInt control, bitLenInt target) { return engine->ACProb(control, target); } + + void CSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->CSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + void AntiCSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->AntiCSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + void CSqrtSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->CSqrtSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + void AntiCSqrtSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->AntiCSqrtSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + void CISqrtSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->CISqrtSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + void AntiCISqrtSwap(const std::vector& controls, bitLenInt qubit1, bitLenInt qubit2) + { + engine->AntiCISqrtSwap(controls, qubit1, qubit2); + Apply1QbNoise(qubit1); + Apply1QbNoise(qubit2); + for (const bitLenInt& control : controls) { + Apply1QbNoise(control); + } + } + + bool ForceM(bitLenInt qubit, bool result, bool doForce = true, bool doApply = true) + { + return engine->ForceM(qubit, result, doForce, doApply); + } + + void Swap(bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->Swap(qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + void ISwap(bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->ISwap(qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + void IISwap(bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->IISwap(qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + void SqrtSwap(bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->SqrtSwap(qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + void ISqrtSwap(bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->ISqrtSwap(qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + void FSim(real1_f theta, real1_f phi, bitLenInt qubitIndex1, bitLenInt qubitIndex2) + { + engine->FSim(theta, phi, qubitIndex1, qubitIndex2); + Apply1QbNoise(qubitIndex1); + Apply1QbNoise(qubitIndex2); + } + + real1_f Prob(bitLenInt qubitIndex) { return engine->Prob(qubitIndex); } + real1_f ProbAll(bitCapInt fullRegister) { return engine->ProbAll(fullRegister); } + real1_f ProbMask(bitCapInt mask, bitCapInt permutation) { return engine->ProbMask(mask, permutation); } + + real1_f SumSqrDiff(QInterfacePtr toCompare) + { + return SumSqrDiff(std::dynamic_pointer_cast(toCompare)); + } + real1_f SumSqrDiff(QInterfaceNoisyPtr toCompare) { return engine->SumSqrDiff(toCompare->engine); } + + void UpdateRunningNorm(real1_f norm_thresh = REAL1_DEFAULT_ARG) { engine->UpdateRunningNorm(norm_thresh); } + void NormalizeState( + real1_f nrm = REAL1_DEFAULT_ARG, real1_f norm_thresh = REAL1_DEFAULT_ARG, real1_f phaseArg = ZERO_R1_F) + { + engine->NormalizeState(nrm, norm_thresh, phaseArg); + } + + real1_f ExpectationBitsAll(const std::vector& bits, const bitCapInt& offset = ZERO_BCI) + { + return engine->ExpectationBitsAll(bits, offset); + } + + void Finish() { engine->Finish(); } + + bool isFinished() { return engine->isFinished(); } + + void Dump() { engine->Dump(); } + + QInterfacePtr Clone() { return std::make_shared(this); } + + void SetDevice(int64_t dID) { engine->SetDevice(dID); } + + int64_t GetDevice() { return engine->GetDevice(); } + + bitCapIntOcl GetMaxSize() { return engine->GetMaxSize(); }; +}; +} // namespace Qrack diff --git a/include/qtensornetwork.hpp b/include/qtensornetwork.hpp index 0a87fb7d5..e4e22bb06 100644 --- a/include/qtensornetwork.hpp +++ b/include/qtensornetwork.hpp @@ -448,9 +448,10 @@ class QTensorNetwork : public QInterface { } // "lambda" is the overall depolarization strength. - // We think of this as the Euclidean norm of 3 equal, - // probabilistic Pauli channels for Z, X, and Y. - lambda = sqrt((lambda * lambda) / 3); + // ChatGPT (custom GPT "Elara") reasons that + // \epsilon(p) = (1 - p) * \rho + (p / 3) * (X \rho X + Y \rho Y + Z \rho Z), + // so we use lambda / 3 for 3 checks. + lambda = lambda / 3; if (Rand() < lambda) { Z(qubit); } diff --git a/include/wasm_api.hpp b/include/wasm_api.hpp index 82d56e3ac..5862cd8c9 100644 --- a/include/wasm_api.hpp +++ b/include/wasm_api.hpp @@ -83,7 +83,8 @@ struct QubitPauliBasis { * "OpenCL" (TURN OFF IN SERIAL BUILD) - use OpenCL acceleration (in general) hp - "Host pointer" (TURN OFF IN SERIAL * BUILD) - allocate OpenCL state vectors on "host" instead of "device" (useful for certain accelerators, like Intel HD) */ -quid init_count_type(bitLenInt q, bool tn, bool md, bool sd, bool sh, bool bdt, bool pg, bool hy, bool oc, bool hp); +quid init_count_type( + bitLenInt q, bool tn, bool md, bool sd, bool sh, bool bdt, bool pg, bool nw, bool hy, bool oc, bool hp); // Utility @@ -488,6 +489,10 @@ void SetReactiveSeparate(quid sid, bool irs); * Turn off/on "T-injection" feature (for "near-Clifford" simulation with RZ gates) */ void SetTInjection(quid sid, bool iti); +/** + * Set noise parameter (for QInterfaceNoisy) + */ +void SetNoiseParameter(quid sid, double np); /** * Initialize a "quantum neuron" that takes a list of qubit "controls" for input and acts on a single "target" output diff --git a/src/pinvoke_api.cpp b/src/pinvoke_api.cpp index af57f4ca2..0f12f7015 100644 --- a/src/pinvoke_api.cpp +++ b/src/pinvoke_api.cpp @@ -600,7 +600,7 @@ MICROSOFT_QUANTUM_DECL int get_error(_In_ uintq sid) * (External API) Initialize a simulator ID with "q" qubits and explicit layer options on/off */ MICROSOFT_QUANTUM_DECL uintq init_count_type(_In_ uintq q, _In_ bool tn, _In_ bool md, _In_ bool sd, _In_ bool sh, - _In_ bool bdt, _In_ bool pg, _In_ bool zxf, _In_ bool hy, _In_ bool oc, _In_ bool hp) + _In_ bool bdt, _In_ bool pg, _In_ bool nw, _In_ bool hy, _In_ bool oc, _In_ bool hp) { META_LOCK_GUARD() @@ -659,6 +659,10 @@ MICROSOFT_QUANTUM_DECL uintq init_count_type(_In_ uintq q, _In_ bool tn, _In_ bo simulatorType.push_back(QINTERFACE_TENSOR_NETWORK); } + if (nw) { + simulatorType.push_back(QINTERFACE_NOISY); + } + // (...then reverse:) std::reverse(simulatorType.begin(), simulatorType.end()); @@ -3319,6 +3323,17 @@ MICROSOFT_QUANTUM_DECL void SetTInjection(_In_ uintq sid, _In_ bool irs) } } +MICROSOFT_QUANTUM_DECL void SetNoiseParameter(_In_ uintq sid, _In_ double np) +{ + SIMULATOR_LOCK_GUARD_VOID(sid) + try { + simulator->SetNoiseParameter((real1_f)np); + } catch (const std::exception& ex) { + simulatorErrors[sid] = 1; + std::cout << ex.what() << std::endl; + } +} + #if !(FPPOW < 6 && !defined(ENABLE_COMPLEX_X2)) /** * (External API) Simulate a Hamiltonian diff --git a/src/qinterface_noisy.cpp b/src/qinterface_noisy.cpp new file mode 100644 index 000000000..8dd4c6095 --- /dev/null +++ b/src/qinterface_noisy.cpp @@ -0,0 +1,27 @@ +////////////////////////////////////////////////////////////////////////////////////// +// +// (C) Daniel Strano and the Qrack contributors 2017-2023. All rights reserved. +// +// QPager breaks a QEngine instance into pages of contiguous amplitudes. +// +// Licensed under the GNU Lesser General Public License V3. +// See LICENSE.md in the project root or https://www.gnu.org/licenses/lgpl-3.0.en.html +// for details. + +#include "qfactory.hpp" + +namespace Qrack { + +QInterfaceNoisy::QInterfaceNoisy(std::vector eng, bitLenInt qBitCount, bitCapInt initState, + qrack_rand_gen_ptr rgp, complex phaseFac, bool doNorm, bool randomGlobalPhase, bool useHostMem, int64_t deviceId, + bool useHardwareRNG, bool useSparseStateVec, real1_f norm_thresh, std::vector devList, + bitLenInt qubitThreshold, real1_f sep_thresh) + : QInterface(qBitCount, rgp, doNorm, useHardwareRNG, randomGlobalPhase, norm_thresh) + , logFidelity(0.0) + , noiseParam(ONE_R1_F / 100) + , engines(eng) +{ + engine = CreateQuantumInterface(engines, qBitCount, initState, rgp, phaseFac, doNorm, randGlobalPhase, useHostMem, + deviceId, useHardwareRNG, useSparseStateVec, norm_thresh, devList, qubitThreshold, sep_thresh); +} +} // namespace Qrack diff --git a/src/wasm_api.cpp b/src/wasm_api.cpp index 926d78337..a39a0eddb 100644 --- a/src/wasm_api.cpp +++ b/src/wasm_api.cpp @@ -535,7 +535,8 @@ MapArithmeticResult2 MapArithmetic3(QInterfacePtr simulator, std::vectorSetTInjection(irs); } +void SetNoiseParameter(quid sid, double np) +{ + SIMULATOR_LOCK_GUARD_VOID(sid) + simulator->SetNoiseParameter((real1_f)np); +} + quid init_qneuron(quid sid, std::vector c, bitLenInt q, QNeuronActivationFn f, real1_f a, real1_f tol) { META_LOCK_GUARD()