From f2a47a9fa7ba8b412202b91bdd068f751b23a3f0 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Wed, 20 Feb 2019 12:57:29 -0500 Subject: [PATCH 1/7] Adding support for unitary case of QubitOperator --- .../_qracksim/_cpp/qracksimulator.hpp | 23 ++++++++++++ projectq/backends/_qracksim/_qracksim.cpp | 1 + projectq/backends/_qracksim/_simulator.py | 35 ++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp index 8608271e5..b20d83402 100755 --- a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp +++ b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp @@ -40,6 +40,10 @@ class QrackSimulator{ using StateVector = std::vector, aligned_allocator,64>>; using Map = std::map; using RndEngine = qrack_rand_gen; + using Term = std::vector>; + using TermsDict = std::vector>; + using ComplexTermsDict = std::vector>; + using Matrix = std::vector, aligned_allocator, 64>>>; enum Qrack::QInterfaceEngine QrackEngine = Qrack::QINTERFACE_QUNIT; enum Qrack::QInterfaceEngine QrackSubengine1 = Qrack::QINTERFACE_QFUSION; #if ENABLE_OPENCL @@ -422,6 +426,12 @@ class QrackSimulator{ delete[] substateVec; } + void apply_qubit_operator(ComplexTermsDict const& td, std::vector const& ids){ + for (auto const& term : td){ + apply_term(term.first, ids, {}); + } + } + std::tuple cheat(){ if (qReg == NULL) { StateVector vec(1, 0.0); @@ -556,6 +566,19 @@ class QrackSimulator{ } } + void apply_term(Term const& term, std::vector const& ids, + std::vector const& ctrl){ + std::complex I(0., 1.); + Matrix X = {{0., 1.}, {1., 0.}}; + Matrix Y = {{0., -I}, {I, 0.}}; + Matrix Z = {{1., 0.}, {0., -1.}}; + std::vector gates = {X, Y, Z}; + for (auto const& local_op : term){ + unsigned id = ids[local_op.first]; + apply_controlled_gate(gates[local_op.second - 'X'], {id}, ctrl); + } + } + Map map_; std::shared_ptr rnd_eng_; Qrack::QInterfacePtr qReg; diff --git a/projectq/backends/_qracksim/_qracksim.cpp b/projectq/backends/_qracksim/_qracksim.cpp index 5f16d09e6..6f993f693 100755 --- a/projectq/backends/_qracksim/_qracksim.cpp +++ b/projectq/backends/_qracksim/_qracksim.cpp @@ -51,6 +51,7 @@ PYBIND11_PLUGIN(_qracksim) { .def("apply_controlled_dec", &QrackSimulator::apply_controlled_dec) .def("apply_controlled_mul", &QrackSimulator::apply_controlled_mul) .def("apply_controlled_div", &QrackSimulator::apply_controlled_div) + .def("apply_qubit_operator", &QrackSimulator::apply_qubit_operator) .def("get_probability", &QrackSimulator::get_probability) .def("get_amplitude", &QrackSimulator::get_amplitude) .def("set_wavefunction", &QrackSimulator::set_wavefunction) diff --git a/projectq/backends/_qracksim/_simulator.py b/projectq/backends/_qracksim/_simulator.py index 0324f8bbc..bce61bb30 100755 --- a/projectq/backends/_qracksim/_simulator.py +++ b/projectq/backends/_qracksim/_simulator.py @@ -31,7 +31,8 @@ Deallocate, UniformlyControlledRy, UniformlyControlledRz, - StatePreparation) + StatePreparation, + QubitOperator) from projectq.libs.math import (AddConstant, AddConstantModN, MultiplyByConstantModN) @@ -119,6 +120,8 @@ def is_available(self, cmd): if (isinstance(cmd.gate, StatePreparation) and not cmd.control_qubits): # Qrack has inexpensive ways of preparing a partial state, without controls. return True + elif (isinstance(cmd.gate, QubitOperator) and not numpy.isclose(1, cmd.gate.coefficient)): + return True except: pass @@ -157,6 +160,36 @@ def get_expectation_value(self, qubit_operator, qureg): # To maintain compatibility with default Simulator, for the moment. pass + def apply_qubit_operator(self, qubit_operator, qureg): + """ + Apply a (strictly unitary, for Qrack) qubit_operator to the current + wave function represented by the supplied quantum register. + + Args: + qubit_operator (projectq.ops.QubitOperator): Operator to apply. + qureg (list[Qubit],Qureg): Quantum bits to which to apply the + operator. + + Raises: + Exception: If `qubit_operator` acts on more qubits than present in + the `qureg` argument. + + Note: + If there is a mapper present in the compiler, this function + automatically converts from logical qubits to mapped qubits for + the qureg argument. + """ + qureg = self._convert_logical_to_mapped_qureg(qureg) + num_qubits = len(qureg) + for term, _ in qubit_operator.terms.items(): + if not term == () and term[-1][0] >= num_qubits: + raise Exception("qubit_operator acts on more qubits than " + "contained in the qureg.") + operator = [(list(term), coeff) for (term, coeff) + in qubit_operator.terms.items()] + return self._simulator.apply_qubit_operator(operator, + [qb.id for qb in qureg]) + def apply_qubit_operator(self, qubit_operator, qureg): # To maintain compatibility with default Simulator, for the moment. pass From 65e15ed14fbabe9b5bf8e6baa5b5ff27d769a191 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Wed, 20 Feb 2019 13:37:47 -0500 Subject: [PATCH 2/7] Adding gate handling for unitary QubitOperator cases --- projectq/backends/_qracksim/_simulator.py | 40 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/projectq/backends/_qracksim/_simulator.py b/projectq/backends/_qracksim/_simulator.py index bce61bb30..3e754db09 100755 --- a/projectq/backends/_qracksim/_simulator.py +++ b/projectq/backends/_qracksim/_simulator.py @@ -157,8 +157,41 @@ def _convert_logical_to_mapped_qureg(self, qureg): return qureg def get_expectation_value(self, qubit_operator, qureg): - # To maintain compatibility with default Simulator, for the moment. - pass + """ + Get the expectation value of qubit_operator w.r.t. the current wave + function represented by the supplied quantum register. + + Args: + qubit_operator (projectq.ops.QubitOperator): Operator to measure. + qureg (list[Qubit],Qureg): Quantum bits to measure. + + Returns: + Expectation value + + Note: + Make sure all previous commands (especially allocations) have + passed through the compilation chain (call main_engine.flush() to + make sure). + + Note: + If there is a mapper present in the compiler, this function + automatically converts from logical qubits to mapped qubits for + the qureg argument. + + Raises: + Exception: If `qubit_operator` acts on more qubits than present in + the `qureg` argument. + """ + qureg = self._convert_logical_to_mapped_qureg(qureg) + num_qubits = len(qureg) + for term, _ in qubit_operator.terms.items(): + if not term == () and term[-1][0] >= num_qubits: + raise Exception("qubit_operator acts on more qubits than " + "contained in the qureg.") + operator = [(list(term), coeff) for (term, coeff) + in qubit_operator.terms.items()] + return self._simulator.get_expectation_value(operator, + [qb.id for qb in qureg]) def apply_qubit_operator(self, qubit_operator, qureg): """ @@ -390,6 +423,9 @@ def _handle(self, cmd): self._simulator.apply_controlled_sqrtswap(ids1, ids2, [qb.id for qb in cmd.control_qubits]) + elif isinstance(cmd.gate, QubitOperator): + ids = [qb.id for qb in cmd.qubits[0]] + self.apply_qubit_operator(cmd.gate, ids) elif isinstance(cmd.gate, AddConstant) or isinstance(cmd.gate, AddConstantModN): #Unless there's a carry, the only unitary addition is mod (2^len(ids)) ids = [qb.id for qr in cmd.qubits for qb in qr] From e02689d9673959b7ca42040ac2146ec1d4fd6ea5 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Wed, 20 Feb 2019 16:00:27 -0500 Subject: [PATCH 3/7] Qrack support for projector expectation values --- .../_qracksim/_cpp/qracksimulator.hpp | 19 +++++++++++++++++++ projectq/backends/_qracksim/_qracksim.cpp | 1 + 2 files changed, 20 insertions(+) diff --git a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp index b20d83402..7b2062e5a 100755 --- a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp +++ b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp @@ -432,6 +432,25 @@ class QrackSimulator{ } } + calc_type get_expectation_value(TermsDict const& td, std::vector const& ids){ + run(); + + Qrack::QInterfacePtr qRegOrig = qReg->Clone(); + for (auto const& term : td){ + apply_term(term.first, ids, {}); + } + + std::size_t mask = 0; + for (unsigned i = 0; i < ids.size(); i++){ + mask |= 1UL << map_[ids[i]]; + } + calc_type expectation = qReg->ProbMask(mask, mask); + + qReg = qRegOrig; + + return expectation; + } + std::tuple cheat(){ if (qReg == NULL) { StateVector vec(1, 0.0); diff --git a/projectq/backends/_qracksim/_qracksim.cpp b/projectq/backends/_qracksim/_qracksim.cpp index 6f993f693..89426da65 100755 --- a/projectq/backends/_qracksim/_qracksim.cpp +++ b/projectq/backends/_qracksim/_qracksim.cpp @@ -51,6 +51,7 @@ PYBIND11_PLUGIN(_qracksim) { .def("apply_controlled_dec", &QrackSimulator::apply_controlled_dec) .def("apply_controlled_mul", &QrackSimulator::apply_controlled_mul) .def("apply_controlled_div", &QrackSimulator::apply_controlled_div) + .def("get_expectation_value", &QrackSimulator::get_expectation_value) .def("apply_qubit_operator", &QrackSimulator::apply_qubit_operator) .def("get_probability", &QrackSimulator::get_probability) .def("get_amplitude", &QrackSimulator::get_amplitude) From bd0fc8831a51a0737d64e1e71dd6f3bda44503be Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Thu, 21 Feb 2019 10:27:01 -0500 Subject: [PATCH 4/7] Support for QubitOperator (with normalization off) --- .../_qracksim/_cpp/qracksimulator.hpp | 20 +++++++++++-------- projectq/backends/_qracksim/_simulator.py | 19 +++++++++++------- projectq/libs/math/_constantmath_test.py | 14 +++++++------ .../decompositions/qubitop2onequbit_test.py | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp index 7b2062e5a..996657fc9 100755 --- a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp +++ b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp @@ -42,7 +42,7 @@ class QrackSimulator{ using RndEngine = qrack_rand_gen; using Term = std::vector>; using TermsDict = std::vector>; - using ComplexTermsDict = std::vector>; + using ComplexTermsDict = std::vector>>; using Matrix = std::vector, aligned_allocator, 64>>>; enum Qrack::QInterfaceEngine QrackEngine = Qrack::QINTERFACE_QUNIT; enum Qrack::QInterfaceEngine QrackSubengine1 = Qrack::QINTERFACE_QFUSION; @@ -88,10 +88,10 @@ class QrackSimulator{ if (map_.count(id) == 0) { if (qReg == NULL) { map_[id] = 0; - qReg = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true); + qReg = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true); } else { map_[id] = qReg->GetQubitCount(); - qReg->Compose(Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true)); + qReg->Compose(Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true)); } } else @@ -413,7 +413,7 @@ class QrackSimulator{ } // Then, prepare the new substate. - Qrack::QInterfacePtr substate = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, ids.size(), 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true); + Qrack::QInterfacePtr substate = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, ids.size(), 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true); substate->SetQuantumState(substateVec); // Finally, combine the representation of the new substate with the remainder of the old engine. @@ -428,7 +428,7 @@ class QrackSimulator{ void apply_qubit_operator(ComplexTermsDict const& td, std::vector const& ids){ for (auto const& term : td){ - apply_term(term.first, ids, {}); + apply_term(term.first, term.second, ids, {}); } } @@ -437,7 +437,7 @@ class QrackSimulator{ Qrack::QInterfacePtr qRegOrig = qReg->Clone(); for (auto const& term : td){ - apply_term(term.first, ids, {}); + apply_term(term.first, term.second, ids, {}); } std::size_t mask = 0; @@ -585,7 +585,7 @@ class QrackSimulator{ } } - void apply_term(Term const& term, std::vector const& ids, + void apply_term(Term const& term, std::complex coeff, std::vector const& ids, std::vector const& ctrl){ std::complex I(0., 1.); Matrix X = {{0., 1.}, {1., 0.}}; @@ -594,7 +594,11 @@ class QrackSimulator{ std::vector gates = {X, Y, Z}; for (auto const& local_op : term){ unsigned id = ids[local_op.first]; - apply_controlled_gate(gates[local_op.second - 'X'], {id}, ctrl); + auto temp = gates[local_op.second - 'X']; + for (unsigned i = 0; i < 4; i++) { + temp[i / 2][i % 2] *= -coeff; + } + apply_controlled_gate(temp, {id}, ctrl); } } diff --git a/projectq/backends/_qracksim/_simulator.py b/projectq/backends/_qracksim/_simulator.py index 3e754db09..a19e5d0af 100755 --- a/projectq/backends/_qracksim/_simulator.py +++ b/projectq/backends/_qracksim/_simulator.py @@ -1,4 +1,3 @@ -# Copyright 2017 ProjectQ-Framework (www.projectq.ch) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -195,8 +194,8 @@ def get_expectation_value(self, qubit_operator, qureg): def apply_qubit_operator(self, qubit_operator, qureg): """ - Apply a (strictly unitary, for Qrack) qubit_operator to the current - wave function represented by the supplied quantum register. + Apply a (possibly non-unitary) qubit_operator to the current wave + function represented by the supplied quantum register. Args: qubit_operator (projectq.ops.QubitOperator): Operator to apply. @@ -207,6 +206,16 @@ def apply_qubit_operator(self, qubit_operator, qureg): Exception: If `qubit_operator` acts on more qubits than present in the `qureg` argument. + Warning: + This function allows applying non-unitary gates and it will not + re-normalize the wave function! It is for numerical experiments + only and should not be used for other purposes. + + Note: + Make sure all previous commands (especially allocations) have + passed through the compilation chain (call main_engine.flush() to + make sure). + Note: If there is a mapper present in the compiler, this function automatically converts from logical qubits to mapped qubits for @@ -223,10 +232,6 @@ def apply_qubit_operator(self, qubit_operator, qureg): return self._simulator.apply_qubit_operator(operator, [qb.id for qb in qureg]) - def apply_qubit_operator(self, qubit_operator, qureg): - # To maintain compatibility with default Simulator, for the moment. - pass - def get_probability(self, bit_string, qureg): """ Return the probability of the outcome `bit_string` when measuring diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 4a6babbf7..8b7d328ba 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -30,6 +30,8 @@ AddConstantModN, MultiplyByConstantModN) +tol = 1e-5 + def test_is_qrack_simulator_present(): try: import projectq.backends._qracksim._qracksim as _ @@ -67,14 +69,14 @@ def test_adder(): AddConstant(3) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][7])) + assert 1. == pytest.approx(abs(sim.cheat()[1][7]), abs=tol, rel=tol) init(eng, qureg, 7) # reset init(eng, qureg, 2) # check for overflow -> should be 15+2 = 1 (mod 16) AddConstant(15) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1])) + assert 1. == pytest.approx(abs(sim.cheat()[1][1]), abs=tol, rel=tol) All(Measure) | qureg @@ -89,13 +91,13 @@ def test_modadder(): AddConstantModN(3, 6) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1])) + assert 1. == pytest.approx(abs(sim.cheat()[1][1]), abs=tol, rel=tol) init(eng, qureg, 1) # reset init(eng, qureg, 7) AddConstantModN(10, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][4])) + assert 1. == pytest.approx(abs(sim.cheat()[1][4]), abs=tol, rel=tol) All(Measure) | qureg @@ -114,12 +116,12 @@ def test_modmultiplier(): MultiplyByConstantModN(3, 7) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][5])) + assert 1. == pytest.approx(abs(sim.cheat()[1][5]), abs=tol, rel=tol) init(eng, qureg, 5) # reset init(eng, qureg, 7) MultiplyByConstantModN(4, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][2])) + assert 1. == pytest.approx(abs(sim.cheat()[1][2]), abs=tol, rel=tol) All(Measure) | qureg diff --git a/projectq/setups/decompositions/qubitop2onequbit_test.py b/projectq/setups/decompositions/qubitop2onequbit_test.py index eddf4087b..1e8bb85f4 100644 --- a/projectq/setups/decompositions/qubitop2onequbit_test.py +++ b/projectq/setups/decompositions/qubitop2onequbit_test.py @@ -18,7 +18,7 @@ from projectq import MainEngine # Qrack simulator does not yet support QubitOperator, so always use the default simulator: -from projectq.backends._sim import Simulator +from projectq.backends import Simulator from projectq.cengines import (AutoReplacer, DecompositionRuleSet, DummyEngine, InstructionFilter) from projectq.meta import Control From eaaaaf98cde4431d294a7807f106dce760844585 Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sat, 2 Mar 2019 14:20:10 -0500 Subject: [PATCH 5/7] Qrack Support for get_expectation_value --- .../_qracksim/_cpp/qracksimulator.hpp | 50 +++++++++++++---- .../backends/_qracksim/_simulator_test.py | 54 +++++++++++++++++++ 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp index 996657fc9..0e9acf96d 100755 --- a/projectq/backends/_qracksim/_cpp/qracksimulator.hpp +++ b/projectq/backends/_qracksim/_cpp/qracksimulator.hpp @@ -88,10 +88,10 @@ class QrackSimulator{ if (map_.count(id) == 0) { if (qReg == NULL) { map_[id] = 0; - qReg = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true); + qReg = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true); } else { map_[id] = qReg->GetQubitCount(); - qReg->Compose(Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true)); + qReg->Compose(Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, 1, 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true)); } } else @@ -413,7 +413,7 @@ class QrackSimulator{ } // Then, prepare the new substate. - Qrack::QInterfacePtr substate = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, ids.size(), 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), false, false, true); + Qrack::QInterfacePtr substate = Qrack::CreateQuantumInterface(QrackEngine, QrackSubengine1, QrackSubengine2, ids.size(), 0, rnd_eng_, complex_type(ONE_R1, ZERO_R1), true, false, true); substate->SetQuantumState(substateVec); // Finally, combine the representation of the new substate with the remainder of the old engine. @@ -433,20 +433,20 @@ class QrackSimulator{ } calc_type get_expectation_value(TermsDict const& td, std::vector const& ids){ - run(); - - Qrack::QInterfacePtr qRegOrig = qReg->Clone(); - for (auto const& term : td){ - apply_term(term.first, term.second, ids, {}); - } + calc_type expectation = 0; std::size_t mask = 0; for (unsigned i = 0; i < ids.size(); i++){ mask |= 1UL << map_[ids[i]]; } - calc_type expectation = qReg->ProbMask(mask, mask); - qReg = qRegOrig; + run(); + + Qrack::QInterfacePtr qRegOrig = qReg->Clone(); + for (auto const& term : td){ + expectation += diagonalize(term.first, term.second, ids); + qReg = qRegOrig->Clone(); + } return expectation; } @@ -587,11 +587,13 @@ class QrackSimulator{ void apply_term(Term const& term, std::complex coeff, std::vector const& ids, std::vector const& ctrl){ + std::complex I(0., 1.); Matrix X = {{0., 1.}, {1., 0.}}; Matrix Y = {{0., -I}, {I, 0.}}; Matrix Z = {{1., 0.}, {0., -1.}}; std::vector gates = {X, Y, Z}; + for (auto const& local_op : term){ unsigned id = ids[local_op.first]; auto temp = gates[local_op.second - 'X']; @@ -602,6 +604,32 @@ class QrackSimulator{ } } + calc_type diagonalize(Term const& term, std::complex coeff, std::vector const& ids){ + calc_type expectation = 1; + calc_type angle = arg(coeff); + calc_type len = abs(coeff); + std::complex phaseFac(cos(angle), sin(angle)); + bitCapInt idPower; + + std::complex I(0., 1.); + Matrix X = {{M_SQRT1_2, M_SQRT1_2}, {M_SQRT1_2, -M_SQRT1_2}}; + Matrix Y = {{M_SQRT1_2, -M_SQRT1_2 * I}, {M_SQRT1_2, M_SQRT1_2 * I}}; + Matrix Z = {{1., 0.}, {0., 1.}}; + std::vector gates = {X, Y, Z}; + + for (auto const& local_op : term){ + unsigned id = ids[local_op.first]; + auto temp = gates[local_op.second - 'X']; + for (unsigned i = 0; i < 4; i++) { + temp[i / 2][i % 2] *= phaseFac; + } + apply_controlled_gate(temp, {id}, {}); + idPower = 1U << map_[id]; + expectation *= (qReg->ProbMask(idPower, 0) - qReg->ProbMask(idPower, idPower)); + } + return len * expectation; + } + Map map_; std::shared_ptr rnd_eng_; Qrack::QInterfacePtr qReg; diff --git a/projectq/backends/_qracksim/_simulator_test.py b/projectq/backends/_qracksim/_simulator_test.py index 0cba59575..78a6aab61 100755 --- a/projectq/backends/_qracksim/_simulator_test.py +++ b/projectq/backends/_qracksim/_simulator_test.py @@ -45,6 +45,9 @@ from projectq.backends import Simulator +tolerance = 1e-6 + + def test_is_qrack_simulator_present(): _qracksim = pytest.importorskip("projectq.backends._qracksim._qracksim") import projectq.backends._qracksim._qracksim as _ @@ -640,3 +643,54 @@ def test_uniformly_controlled_r(sim, gate_classes): All(Measure) | correct_qb + correct_ctrl_qureg test_eng.flush(deallocate_qubits=True) correct_eng.flush(deallocate_qubits=True) + +def test_get_expectation_value(sim): + num_qubits = 2 + test_eng = MainEngine(sim) + test_qureg = test_eng.allocate_qureg(num_qubits) + test_eng.flush() + + qubit_op = QubitOperator("X0", 1) + test_eng.backend.set_wavefunction([1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1, rel=tolerance, abs=tolerance)) + test_eng.backend.set_wavefunction([1 / math.sqrt(2), -1 / math.sqrt(2), 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) + + qubit_op = QubitOperator("Y0", 1) + test_eng.backend.set_wavefunction([1 / math.sqrt(2), 1j / math.sqrt(2), 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1, rel=tolerance, abs=tolerance)) + test_eng.backend.set_wavefunction([1 / math.sqrt(2), -1j / math.sqrt(2), 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) + + qubit_op = QubitOperator("Z0", 1) + test_eng.backend.set_wavefunction([1, 0, 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1, rel=tolerance, abs=tolerance)) + test_eng.backend.set_wavefunction([0, 1, 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) + + qubit_op = QubitOperator("Z0 Z1", 1) + test_eng.backend.set_wavefunction([1, 0, 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1, rel=tolerance, abs=tolerance)) + X | test_qureg[0] + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) + X | test_qureg[1] + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1, rel=tolerance, abs=tolerance)) + X | test_qureg[0] + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) From 26d20b5771ec54eff69ba59e30dfee8b7530b54f Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sat, 2 Mar 2019 14:30:01 -0500 Subject: [PATCH 6/7] Accept all unitary QubitOperator --- projectq/backends/_qracksim/_simulator.py | 2 +- projectq/backends/_qracksim/_simulator_test.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/projectq/backends/_qracksim/_simulator.py b/projectq/backends/_qracksim/_simulator.py index a19e5d0af..dd821e0d1 100755 --- a/projectq/backends/_qracksim/_simulator.py +++ b/projectq/backends/_qracksim/_simulator.py @@ -119,7 +119,7 @@ def is_available(self, cmd): if (isinstance(cmd.gate, StatePreparation) and not cmd.control_qubits): # Qrack has inexpensive ways of preparing a partial state, without controls. return True - elif (isinstance(cmd.gate, QubitOperator) and not numpy.isclose(1, cmd.gate.coefficient)): + elif (isinstance(cmd.gate, QubitOperator) and not np.isclose(1, np.absolute(cmd.gate.coefficient))): return True except: pass diff --git a/projectq/backends/_qracksim/_simulator_test.py b/projectq/backends/_qracksim/_simulator_test.py index 78a6aab61..b907b6be8 100755 --- a/projectq/backends/_qracksim/_simulator_test.py +++ b/projectq/backends/_qracksim/_simulator_test.py @@ -680,6 +680,16 @@ def test_get_expectation_value(sim): test_eng.flush() assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1, rel=tolerance, abs=tolerance)) + qubit_op = QubitOperator("Z0", 1 / 4) + test_eng.backend.set_wavefunction([1, 0, 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(1 / 4, rel=tolerance, abs=tolerance)) + test_eng.backend.set_wavefunction([0, 1, 0, 0], + test_qureg) + test_eng.flush() + assert(sim.get_expectation_value(qubit_op, test_qureg) == pytest.approx(-1 / 4, rel=tolerance, abs=tolerance)) + qubit_op = QubitOperator("Z0 Z1", 1) test_eng.backend.set_wavefunction([1, 0, 0, 0], test_qureg) From a85f6fcb2823efc5271985c2111ad4b9696c48ee Mon Sep 17 00:00:00 2001 From: WrathfulSpatula Date: Sat, 2 Mar 2019 14:40:41 -0500 Subject: [PATCH 7/7] Reverting _constant_math_test.pu --- projectq/libs/math/_constantmath_test.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/projectq/libs/math/_constantmath_test.py b/projectq/libs/math/_constantmath_test.py index 8b7d328ba..4a6babbf7 100755 --- a/projectq/libs/math/_constantmath_test.py +++ b/projectq/libs/math/_constantmath_test.py @@ -30,8 +30,6 @@ AddConstantModN, MultiplyByConstantModN) -tol = 1e-5 - def test_is_qrack_simulator_present(): try: import projectq.backends._qracksim._qracksim as _ @@ -69,14 +67,14 @@ def test_adder(): AddConstant(3) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][7]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][7])) init(eng, qureg, 7) # reset init(eng, qureg, 2) # check for overflow -> should be 15+2 = 1 (mod 16) AddConstant(15) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][1])) All(Measure) | qureg @@ -91,13 +89,13 @@ def test_modadder(): AddConstantModN(3, 6) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][1]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][1])) init(eng, qureg, 1) # reset init(eng, qureg, 7) AddConstantModN(10, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][4]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][4])) All(Measure) | qureg @@ -116,12 +114,12 @@ def test_modmultiplier(): MultiplyByConstantModN(3, 7) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][5]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][5])) init(eng, qureg, 5) # reset init(eng, qureg, 7) MultiplyByConstantModN(4, 13) | qureg - assert 1. == pytest.approx(abs(sim.cheat()[1][2]), abs=tol, rel=tol) + assert 1. == pytest.approx(abs(sim.cheat()[1][2])) All(Measure) | qureg