From 428b1534c3f287509c394784648bc7beb3dc5239 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Wed, 27 Sep 2023 00:22:06 +0000 Subject: [PATCH 1/8] Add exp_pauli quantum instruction to the runtime and ASTBridge Signed-off-by: Alex McCaskey --- .github/workflows/python_wheels.yml | 2 +- .gitignore | 1 + include/cudaq/Optimizer/Dialect/CC/CCOps.td | 13 + include/cudaq/Optimizer/Dialect/CC/CCTypes.td | 14 + .../cudaq/Optimizer/Dialect/Quake/QuakeOps.td | 19 +- lib/Frontend/nvqpp/ConvertDecl.cpp | 2 +- lib/Frontend/nvqpp/ConvertExpr.cpp | 43 +- lib/Frontend/nvqpp/ConvertType.cpp | 29 +- lib/Optimizer/CodeGen/LowerToQIR.cpp | 95 +++- lib/Optimizer/Dialect/CC/CCTypes.cpp | 3 +- .../cudaq/builder/py_kernel_builder.cpp | 12 +- python/runtime/cudaq/spin/py_spin_op.cpp | 2 + python/tests/unittests/test_kernel_builder.py | 22 + runtime/cudaq/builder/kernel_builder.cpp | 32 ++ runtime/cudaq/builder/kernel_builder.h | 46 +- runtime/cudaq/domains/chemistry/uccsd.h | 420 ++---------------- runtime/cudaq/qis/execution_manager.h | 4 +- .../qis/managers/BasicExecutionManager.h | 18 +- .../default/DefaultExecutionManager.cpp | 7 +- runtime/cudaq/qis/qubit_qis.h | 29 ++ runtime/nvqir/CircuitSimulator.h | 47 ++ runtime/nvqir/NVQIR.cpp | 8 + .../custatevec/CuStateVecCircuitSimulator.cu | 26 ++ test/AST-Quake/exp-pauli-1.cpp | 36 ++ test/AST-Quake/exp-pauli-2.cpp | 38 ++ test/AST-Quake/exp-pauli-3.cpp | 40 ++ test/Quake-QIR/exp-pauli-qir-1.qke | 39 ++ test/Quake-QIR/exp-pauli-qir-2.qke | 42 ++ test/Quake-QIR/exp-pauli-qir-3.qke | 70 +++ test/Quake/roundtrip-ops.qke | 8 + unittests/CMakeLists.txt | 2 +- unittests/domains/ChemistryTester.cpp | 23 + unittests/integration/builder_tester.cpp | 55 +++ .../SimpleQuditExecutionManager.cpp | 2 +- 34 files changed, 834 insertions(+), 415 deletions(-) create mode 100644 test/AST-Quake/exp-pauli-1.cpp create mode 100644 test/AST-Quake/exp-pauli-2.cpp create mode 100644 test/AST-Quake/exp-pauli-3.cpp create mode 100644 test/Quake-QIR/exp-pauli-qir-1.qke create mode 100644 test/Quake-QIR/exp-pauli-qir-2.qke create mode 100644 test/Quake-QIR/exp-pauli-qir-3.qke diff --git a/.github/workflows/python_wheels.yml b/.github/workflows/python_wheels.yml index eafb567d83..c420861303 100644 --- a/.github/workflows/python_wheels.yml +++ b/.github/workflows/python_wheels.yml @@ -197,7 +197,7 @@ jobs: image: wheel_validation:local shell: bash run: | - python${{ inputs.python_version }} -m pytest -v /tmp/tests/ \ + python${{ inputs.python_version }} -m pytest -v -s /tmp/tests/ \ --ignore python/tests/backends pytest_status=$? if [ ! $pytest_status -eq 0 ]; then diff --git a/.gitignore b/.gitignore index 33565c14f8..7c9decc636 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ _version.py # third party integrations simulators/ +apps/ # macOS .DS_Store diff --git a/include/cudaq/Optimizer/Dialect/CC/CCOps.td b/include/cudaq/Optimizer/Dialect/CC/CCOps.td index ef5514065f..17d77c6c81 100644 --- a/include/cudaq/Optimizer/Dialect/CC/CCOps.td +++ b/include/cudaq/Optimizer/Dialect/CC/CCOps.td @@ -1369,4 +1369,17 @@ def cc_CallableClosureOp : CCOp<"callable_closure", [Pure]> { }]; } +def cc_CreateStringLiteralOp : CCOp<"string_literal"> { + let summary = "Create a constant string literal."; + let description = [{ + This operation creates CC StringType values from constant MLIR + String Attributes. + }]; + + let arguments = (ins StrAttr:$stringLiteral); + let results = (outs cc_StringType:$result); + let assemblyFormat = [{ + `{` $stringLiteral `}` `:` functional-type(operands, results) attr-dict + }]; +} #endif // CUDAQ_OPTIMIZER_DIALECT_CC_OPS diff --git a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td index 5c2ef5af78..da15c039aa 100644 --- a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td +++ b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td @@ -23,6 +23,20 @@ class CCType traits = [], let mnemonic = typeMnemonic; } + +//===----------------------------------------------------------------------===// +// StringType +//===----------------------------------------------------------------------===// + +def cc_StringType : CCType<"String", "string"> { + let summary = "A C++ string type."; + let description = [{ + The bridge can use this type to represent std::string values. This type + is basically a placeholder type that we can usher down to the LLVM Dialect + level. + }]; +} + //===----------------------------------------------------------------------===// // PointerType //===----------------------------------------------------------------------===// diff --git a/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td b/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td index 8aa52e9024..ad82065f8c 100644 --- a/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td +++ b/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td @@ -786,6 +786,23 @@ class TwoTargetOp traits = []> : // Quantum operators (gates) //===----------------------------------------------------------------------===// +def ExpPauliOp : QuakeOp<"exp_pauli", []> { + let summary = "General Pauli tensor product rotation"; + let description = [{ + This operation affects a general Pauli tensor product rotation on + the input qubits. The number of Pauli characters in the input Pauli word + string must equal the number of qubits in the veq. Mathematically, this operation + applies exp(i theta P) where P is a general Pauli tensor product. + }]; + + let arguments = (ins AnyFloat:$parameter, VeqType:$qubits, cc_StringType:$pauli); + let results = (outs ); + + let assemblyFormat = [{ + `(` $parameter `)` $qubits `,` $pauli `:` functional-type(operands, results) attr-dict + }]; +} + def HOp : OneTargetOp<"h", [Hermitian]> { let summary = "Hadamard operation"; let description = [{ @@ -815,7 +832,7 @@ def PhasedRxOp : QuakeOperator<"phased_rx", Matrix representation: ``` PhasedRx(θ,φ) = | cos(θ/2) -iexp(-iφ) * sin(θ/2) | - | -iexp(iφ)) * sin(θ/2) cos(θ/2) | + | -iexp(iφ) * sin(θ/2) cos(θ/2) | ``` Circuit symbol: diff --git a/lib/Frontend/nvqpp/ConvertDecl.cpp b/lib/Frontend/nvqpp/ConvertDecl.cpp index b116383498..5d4baeb7a2 100644 --- a/lib/Frontend/nvqpp/ConvertDecl.cpp +++ b/lib/Frontend/nvqpp/ConvertDecl.cpp @@ -92,7 +92,7 @@ void QuakeBridgeVisitor::addArgumentSymbols( auto parmTy = entryBlock->getArgument(index).getType(); if (isa(parmTy)) { + cc::StringType, quake::VeqType>(parmTy)) { symbolTable.insert(name, entryBlock->getArgument(index)); } else { auto stackSlot = builder.create(loc, parmTy); diff --git a/lib/Frontend/nvqpp/ConvertExpr.cpp b/lib/Frontend/nvqpp/ConvertExpr.cpp index 93a19d6710..78934f51b6 100644 --- a/lib/Frontend/nvqpp/ConvertExpr.cpp +++ b/lib/Frontend/nvqpp/ConvertExpr.cpp @@ -948,7 +948,8 @@ bool QuakeBridgeVisitor::VisitMaterializeTemporaryExpr( // The following cases are λ expressions, quantum data, or a std::vector view. // In those cases, there is nothing to materialize, so we can just pass the // Value on the top of the stack. - if (isa(ty)) + if (isa(ty)) return true; // If not one of the above special cases, then materialize the value to a @@ -1217,6 +1218,34 @@ bool QuakeBridgeVisitor::VisitCallExpr(clang::CallExpr *x) { isAdjoint = structTypeAsRecord->getName() == "adj"; } + if (funcName.equals("exp_pauli")) { + assert(args.size() > 2); + SmallVector processedArgs; + if (args.size() == 3 && isa(args[1].getType())) + processedArgs = args; + else { + // should have f64, string, qubits... + // need f64, veq, string, so process here + + // add f64 value + processedArgs.push_back(args[0]); + + // concat the qubits to a veq + SmallVector quantumArgs; + for (std::size_t i = 2; i < args.size(); i++) + quantumArgs.push_back(args[i]); + processedArgs.push_back(builder.create( + loc, quake::VeqType::get(builder.getContext(), quantumArgs.size()), + quantumArgs)); + + // add the string + processedArgs.push_back(args[1]); + } + + builder.create(loc, TypeRange{}, processedArgs); + return true; + } + if (funcName.equals("mx") || funcName.equals("my") || funcName.equals("mz")) { // Measurements always return a bool or a std::vector. @@ -2091,6 +2120,12 @@ bool QuakeBridgeVisitor::VisitCXXConstructExpr(clang::CXXConstructExpr *x) { } } + // Is this a std::string constructed from a StringLiteral? If yes + // return true, we have what we need. + if (!ctor->isDefaultConstructor() && ctorName == "basic_string") + if (isa(peekValue().getType())) + return true; + if (ctor->isCopyConstructor()) if (auto *parent = ctor->getParent()) if (parent->isLambda()) { @@ -2102,7 +2137,7 @@ bool QuakeBridgeVisitor::VisitCXXConstructExpr(clang::CXXConstructExpr *x) { // TODO: remove this when we can handle ctors more generally. if (!ctor->isDefaultConstructor()) { - LLVM_DEBUG(llvm::dbgs() << "unhandled ctor:\n"; x->dump()); + LLVM_DEBUG(llvm::dbgs() << ctorName << " - unhandled ctor:\n"; x->dump()); TODO_loc(loc, "C++ ctor (not-default)"); } @@ -2168,8 +2203,8 @@ bool QuakeBridgeVisitor::VisitDeclRefExpr(clang::DeclRefExpr *x) { } bool QuakeBridgeVisitor::VisitStringLiteral(clang::StringLiteral *x) { - TODO_x(toLocation(x->getSourceRange()), x, mangler, "string literal"); - return false; + return pushValue(builder.create( + toLocation(x), builder.getStringAttr(x->getString()))); } } // namespace cudaq::details diff --git a/lib/Frontend/nvqpp/ConvertType.cpp b/lib/Frontend/nvqpp/ConvertType.cpp index 9e5cdbb06d..f645ec67c5 100644 --- a/lib/Frontend/nvqpp/ConvertType.cpp +++ b/lib/Frontend/nvqpp/ConvertType.cpp @@ -17,6 +17,7 @@ using namespace mlir; static bool isArithmeticType(Type t) { return isa(t); } +static bool isStringType(Type t) { return isa(t); } static bool isQuantumType(Type t) { return isa(t); @@ -81,6 +82,32 @@ QuakeBridgeVisitor::findCallOperator(const clang::CXXRecordDecl *decl) { bool QuakeBridgeVisitor::TraverseRecordType(clang::RecordType *t) { auto *recDecl = t->getDecl(); + + // Is this RecordDecl a std::string type + auto isStdString = [](clang::RecordDecl *decl) { + // Not in std, can't be std::string + if (!isInNamespace(decl, "std")) + return false; + + // Has no identifier, can't be std::string + auto *ident = decl->getIdentifier(); + if (!ident) + return false; + + // The type name is basic_string, check that + if (ident->getName().equals("basic_string")) + return true; + + // Wasn't what we're looking for. + return false; + }(recDecl); + + // If this is a std::string, add the type to the stack and return + if (isStdString) { + pushType(cc::StringType::get(builder.getContext())); + return true; + } + if (ignoredClass(recDecl)) return true; auto reci = records.find(t); @@ -304,7 +331,7 @@ bool QuakeBridgeVisitor::doSyntaxChecks(const clang::FunctionDecl *x) { for (auto [t, p] : llvm::zip(funcTy.getInputs(), x->parameters())) { // Structs, lambdas, functions are valid callable objects. Also pure // device kernels may take veq and/or ref arguments. - if (isArithmeticType(t) || isArithmeticSequenceType(t) || + if (isArithmeticType(t) || isArithmeticSequenceType(t) || isStringType(t) || isQuantumType(t) || isKernelCallable(t) || isFunctionCallable(t) || isReferenceToCallableRecord(t, p)) continue; diff --git a/lib/Optimizer/CodeGen/LowerToQIR.cpp b/lib/Optimizer/CodeGen/LowerToQIR.cpp index 555bf58071..65a5e721d5 100644 --- a/lib/Optimizer/CodeGen/LowerToQIR.cpp +++ b/lib/Optimizer/CodeGen/LowerToQIR.cpp @@ -348,6 +348,52 @@ class ResetRewrite : public ConvertOpToLLVMPattern { } }; +/// Lower exp_pauli(f64, veq, cc.string) to __quantum__qis__exp_pauli +class ExpPauliRewrite : public ConvertOpToLLVMPattern { +public: + using Base = ConvertOpToLLVMPattern; + using Base::Base; + + LogicalResult + matchAndRewrite(quake::ExpPauliOp instOp, typename Base::OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = instOp->getLoc(); + auto parentModule = instOp->template getParentOfType(); + auto context = parentModule->getContext(); + std::string qirQisPrefix(cudaq::opt::QIRQISPrefix); + auto qirFunctionName = qirQisPrefix + "exp_pauli"; + FlatSymbolRefAttr symbolRef = cudaq::opt::factory::createLLVMFunctionSymbol( + qirFunctionName, /*return type=*/LLVM::LLVMVoidType::get(context), + {rewriter.getF64Type(), cudaq::opt::getArrayType(context), + cudaq::opt::factory::getPointerType(context)}, + parentModule); + + // The Pauli string can come from a BlockArgument, in which case it + // is a cc.string, or it can come to us here as the return type of + // the string_literal op (which ends up being a AddressOf to a global + // string, bitcasted to a i8*). If it is a block argument, then we need to + // treat that cc.string type as a pointer to a i8* (which the std::string + // ultimately comes to us as [a ptr arg, which contains a ptr to the data]) + SmallVector operands = adaptor.getOperands(); + if (!operands.back().getDefiningOp()) { + Value casted = rewriter.create( + loc, + cudaq::opt::factory::getPointerType( + cudaq::opt::factory::getPointerType(context)), + adaptor.getOperands().back()); + + casted = rewriter.create( + loc, cudaq::opt::factory::getPointerType(context), casted); + operands = adaptor.getOperands().drop_back(); + operands.push_back(casted); + } + + rewriter.replaceOpWithNewOp(instOp, TypeRange{}, symbolRef, + operands); + return success(); + } +}; + /// Lower single target Quantum ops with no parameter to QIR: /// h, x, y, z, s, t template @@ -1310,6 +1356,47 @@ class StdvecSizeOpPattern } }; +class CreateStringLiteralOpPattern + : public ConvertOpToLLVMPattern { +public: + using Base = ConvertOpToLLVMPattern; + using Base::Base; + + LogicalResult + matchAndRewrite(cudaq::cc::CreateStringLiteralOp stringLiteralOp, + OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = stringLiteralOp.getLoc(); + auto parentModule = stringLiteralOp->getParentOfType(); + auto context = parentModule->getContext(); + + StringRef stringLiteral = stringLiteralOp.getStringLiteral(); + + // Write to the module body + auto insertPoint = rewriter.saveInsertionPoint(); + rewriter.setInsertionPointToStart(parentModule.getBody()); + + // Create the register name global + auto builder = cudaq::IRBuilder::atBlockEnd(parentModule.getBody()); + auto slGlobal = + builder.genCStringLiteralAppendNul(loc, parentModule, stringLiteral); + + // Shift back to the function + rewriter.restoreInsertionPoint(insertPoint); + + // Get the string address and bit cast + auto slRef = rewriter.create( + loc, cudaq::opt::factory::getPointerType(slGlobal.getType()), + slGlobal.getSymName()); + Value castedSlRef = rewriter.create( + loc, cudaq::opt::factory::getPointerType(context), slRef); + + rewriter.replaceOp(stringLiteralOp, castedSlRef); + + return success(); + } +}; + class StoreOpPattern : public ConvertOpToLLVMPattern { public: using Base = ConvertOpToLLVMPattern; @@ -1424,8 +1511,9 @@ class QuakeToQIRRewrite : public cudaq::opt::QuakeToQIRBase { AllocaOpRewrite, AllocaOpPattern, CallableClosureOpPattern, CallableFuncOpPattern, CallCallableOpPattern, CastOpPattern, ComputePtrOpPattern, ConcatOpRewrite, DeallocOpRewrite, - ExtractQubitOpRewrite, ExtractValueOpPattern, FuncToPtrOpPattern, - InsertValueOpPattern, InstantiateCallableOpPattern, LoadOpPattern, + CreateStringLiteralOpPattern, ExtractQubitOpRewrite, + ExtractValueOpPattern, FuncToPtrOpPattern, InsertValueOpPattern, + InstantiateCallableOpPattern, LoadOpPattern, ExpPauliRewrite, OneTargetRewrite, OneTargetRewrite, OneTargetRewrite, OneTargetRewrite, OneTargetRewrite, OneTargetRewrite, @@ -1487,6 +1575,9 @@ class QuakeToQIRRewrite : public cudaq::opt::QuakeToQIRBase { members.push_back(typeConverter.convertType(t)); return LLVM::LLVMStructType::getLiteral(type.getContext(), members); }); + typeConverter.addConversion([&](cudaq::cc::StringType type) { + return cudaq::opt::factory::getPointerType(type.getContext()); + }); } }; diff --git a/lib/Optimizer/Dialect/CC/CCTypes.cpp b/lib/Optimizer/Dialect/CC/CCTypes.cpp index 3a2ea1e014..92f5fed55d 100644 --- a/lib/Optimizer/Dialect/CC/CCTypes.cpp +++ b/lib/Optimizer/Dialect/CC/CCTypes.cpp @@ -142,7 +142,8 @@ cc::CallableType cc::CallableType::getNoSignature(MLIRContext *ctx) { //===----------------------------------------------------------------------===// void cc::CCDialect::registerTypes() { - addTypes(); + addTypes(); } } // namespace cudaq diff --git a/python/runtime/cudaq/builder/py_kernel_builder.cpp b/python/runtime/cudaq/builder/py_kernel_builder.cpp index c5984beefe..c89120eace 100644 --- a/python/runtime/cudaq/builder/py_kernel_builder.cpp +++ b/python/runtime/cudaq/builder/py_kernel_builder.cpp @@ -893,7 +893,17 @@ provided `function` will be applied within `self` at each iteration. .def("to_quake", &kernel_builder<>::to_quake, "See :func:`__str__`.") .def("__str__", &kernel_builder<>::to_quake, "Return the :class:`Kernel` as a string in its MLIR representation " - "using the Quake dialect.\n"); + "using the Quake dialect.\n") + .def("exp_pauli", + [](kernel_builder<> &self, double theta, const QuakeValue &qubits, + const std::string &pauliWord) { + self.exp_pauli(theta, qubits, pauliWord); + }) + .def("exp_pauli", + [](kernel_builder<> &self, const QuakeValue &theta, + const QuakeValue &qubits, const std::string &pauliWord) { + self.exp_pauli(theta, qubits, pauliWord); + }); } void bindBuilder(py::module &mod) { diff --git a/python/runtime/cudaq/spin/py_spin_op.cpp b/python/runtime/cudaq/spin/py_spin_op.cpp index 8154d85b38..64a3905bef 100644 --- a/python/runtime/cudaq/spin/py_spin_op.cpp +++ b/python/runtime/cudaq/spin/py_spin_op.cpp @@ -79,6 +79,8 @@ void bindSpinOperator(py::module &mod) { .def(py::init( [](py::object o) { return fromOpenFermionQubitOperator(o); }), "Create from OpenFermion QubitOperator.") + .def(py::init &, std::size_t>(), py::arg("data"), + py::arg("num_qubits"), "") /// @brief Bind the member functions. .def("get_raw_data", &cudaq::spin_op::get_raw_data, diff --git a/python/tests/unittests/test_kernel_builder.py b/python/tests/unittests/test_kernel_builder.py index f2e67cabbd..1ba6e904f9 100644 --- a/python/tests/unittests/test_kernel_builder.py +++ b/python/tests/unittests/test_kernel_builder.py @@ -281,3 +281,25 @@ def test_from_state(): ss = cudaq.get_state(kernel) for i in range(4): assert np.isclose(ss[i], state[i], 1e-3) + +def test_exp_pauli(): + cudaq.reset_target() + kernel, theta = cudaq.make_kernel(float) + qubits = kernel.qalloc(4) + kernel.x(qubits[0]) + kernel.x(qubits[1]) + kernel.exp_pauli(theta, qubits, "XXXY") + print(kernel) + h2_data = [ + 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, + 0, 0, 2, 0, -0.220041, -0, 1, 3, 3, 1, 0.0454063, 0, + 0, 0, 0, 0, -0.106477, 0, 0, 2, 0, 0, 0.17028, 0, + 0, 0, 0, 2, -0.220041, -0, 3, 3, 1, 1, -0.0454063, -0, + 2, 2, 0, 0, 0.168336, 0, 2, 0, 2, 0, 0.1202, 0, + 0, 2, 0, 2, 0.1202, 0, 2, 0, 0, 2, 0.165607, 0, + 0, 2, 2, 0, 0.165607, 0, 0, 0, 2, 2, 0.174073, 0, + 1, 1, 3, 3, -0.0454063, -0, 15 + ] + h = cudaq.SpinOperator(h2_data, 4) + want_exp = cudaq.observe(kernel, h, -.22).expectation_z() + assert np.isclose(want_exp, -1.13, atol=1e-2) \ No newline at end of file diff --git a/runtime/cudaq/builder/kernel_builder.cpp b/runtime/cudaq/builder/kernel_builder.cpp index 3df248d359..d024586566 100644 --- a/runtime/cudaq/builder/kernel_builder.cpp +++ b/runtime/cudaq/builder/kernel_builder.cpp @@ -163,6 +163,38 @@ bool isArgStdVec(std::vector &args, std::size_t idx) { return args[idx].isStdVec(); } +void exp_pauli(ImplicitLocOpBuilder &builder, const QuakeValue &theta, + const std::vector &qubits, + const std::string &pauliWord) { + Value qubitsVal; + if (qubits.size() == 1) + qubitsVal = qubits.front().getValue(); + else { + // we have a vector of quake value qubits, need to concat them + SmallVector values; + for (auto &v : qubits) + values.push_back(v.getValue()); + + qubitsVal = builder.create( + quake::VeqType::get(builder.getContext(), qubits.size()), values); + } + + auto thetaVal = theta.getValue(); + if (!isa(qubitsVal.getType())) + throw std::runtime_error( + "exp_pauli must take a QuakeValue of veq type as second argument."); + if (!thetaVal.getType().isIntOrFloat()) + throw std::runtime_error("exp_pauli must take a QuakeValue of float/int " + "type as first argument."); + cudaq::info("kernel_builder apply exp_pauli {}", pauliWord); + + Value stringLiteral = builder.create( + builder.getStringAttr(pauliWord)); + SmallVector args{thetaVal, qubitsVal, stringLiteral}; + builder.create(TypeRange{}, args); + return; +} + /// @brief Search the given `FuncOp` for all `CallOps` recursively. /// If found, see if the called function is in the current `ModuleOp` /// for this `kernel_builder`, if so do nothing. If it is not found, diff --git a/runtime/cudaq/builder/kernel_builder.h b/runtime/cudaq/builder/kernel_builder.h index 4314f790d8..8f0814dfdd 100644 --- a/runtime/cudaq/builder/kernel_builder.h +++ b/runtime/cudaq/builder/kernel_builder.h @@ -191,6 +191,10 @@ CUDAQ_DETAILS_MEASURE_DECLARATION(mx) CUDAQ_DETAILS_MEASURE_DECLARATION(my) CUDAQ_DETAILS_MEASURE_DECLARATION(mz) +void exp_pauli(ImplicitLocOpBuilder &builder, const QuakeValue &theta, + const std::vector &qubits, + const std::string &pauliWord); + void swap(ImplicitLocOpBuilder &builder, const std::vector &ctrls, const std::vector &targets, bool adjoint = false); @@ -231,17 +235,17 @@ void control(ImplicitLocOpBuilder &builder, std::string &name, void adjoint(ImplicitLocOpBuilder &builder, std::string &name, std::string &quakeCode, std::vector &values); -/// @brief Add a for loop that starts from the given `start` integer index, ends -/// at the given `end` integer index, and applies the given `body` as a callable -/// function. This callable function must take as input an index variable that -/// can be used within the body. +/// @brief Add a for loop that starts from the given `start` integer index, +/// ends at the given `end` integer index, and applies the given `body` as a +/// callable function. This callable function must take as input an index +/// variable that can be used within the body. void forLoop(ImplicitLocOpBuilder &builder, std::size_t start, std::size_t end, std::function &body); -/// @brief Add a for loop that starts from the given `start` integer index, ends -/// at the given `end` index, and applies the given `body` as a -/// callable function. This callable function must take as input an index -/// variable that can be used within the body. +/// @brief Add a for loop that starts from the given `start` integer index, +/// ends at the given `end` index, and applies the given `body` as a callable +/// function. This callable function must take as input an index variable that +/// can be used within the body. void forLoop(ImplicitLocOpBuilder &builder, std::size_t start, QuakeValue &end, std::function &body); @@ -541,6 +545,32 @@ class kernel_builder : public details::kernel_builder_base { details::c_if(*opBuilder, result, thenFunctor); } + /// @brief Apply a general pauli rotation, exp(-i theta P), + /// takes a QuakeValue representing a register of qubits. + template + void exp_pauli(const ParamT &theta, const QuakeValue &qubits, + const std::string &pauliWord) { + std::vector qubitValues{qubits}; + if constexpr (std::is_floating_point_v) + details::exp_pauli(*opBuilder, QuakeValue(*opBuilder, theta), qubitValues, + pauliWord); + else + details::exp_pauli(*opBuilder, theta, qubitValues, pauliWord); + } + + /// @brief Apply a general pauli rotation, exp(-i theta P), + /// takes a variadic list of QuakeValues representing a individual qubits. + template + void exp_pauli(const ParamT &theta, const std::string &pauliWord, + QubitArgs &&...qubits) { + std::vector qubitValues{qubits...}; + if constexpr (std::is_floating_point_v) + details::exp_pauli(*opBuilder, QuakeValue(*opBuilder, theta), qubitValues, + pauliWord); + else + details::exp_pauli(*opBuilder, theta, qubitValues, pauliWord); + } + /// @brief Apply the given `otherKernel` with the provided `QuakeValue` /// arguments. template diff --git a/runtime/cudaq/domains/chemistry/uccsd.h b/runtime/cudaq/domains/chemistry/uccsd.h index 29ce66338c..d3dc14729e 100644 --- a/runtime/cudaq/domains/chemistry/uccsd.h +++ b/runtime/cudaq/domains/chemistry/uccsd.h @@ -46,84 +46,16 @@ void singletExcitation(KernelBuilder &&kernel, QuakeValue &qubits, QuakeValue &theta, const SingleIndices &indices) { auto r = indices.front(); auto p = indices.back(); - - kernel.rx(-M_PI_2, qubits[r]); - kernel.h(qubits[p]); - - std::vector> cnots; - - for (std::size_t i = r; i < p; i++) - cnots.emplace_back(i, i + 1); - - auto reversed = cnots; - std::reverse(reversed.begin(), reversed.end()); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz(0.5 * theta, qubits[p]); - - for (auto &[i, j] : reversed) - kernel.template x(qubits[i], qubits[j]); - - kernel.rx(M_PI_2, qubits[r]); - kernel.h(qubits[p]); - - kernel.h(qubits[r]); - kernel.rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz(-0.5 * theta, qubits[p]); - - for (auto &[i, j] : reversed) - kernel.template x(qubits[i], qubits[j]); - - kernel.h(qubits[r]); - kernel.rx(M_PI_2, qubits[p]); + kernel.exp_pauli(0.5 * theta, "YX", qubits[r], qubits[p]); + kernel.exp_pauli(-0.5 * theta, "XY", qubits[r], qubits[p]); } __qpu__ void singletExcitation(cudaq::qspan<> qubits, double theta, const SingleIndices &indices) { auto r = indices.front(); auto p = indices.back(); - - rx(-M_PI_2, qubits[r]); - h(qubits[p]); - - std::vector> cnots; - - for (std::size_t i = r; i < p; i++) - cnots.emplace_back(i, i + 1); - - auto reversed = cnots; - std::reverse(reversed.begin(), reversed.end()); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(theta / 2., qubits[p]); - - for (auto &[i, j] : reversed) - x(qubits[i], qubits[j]); - - rx(M_PI_2, qubits[r]); - h(qubits[p]); - - h(qubits[r]); - rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(-theta / 2., qubits[p]); - - for (auto &[i, j] : reversed) - x(qubits[i], qubits[j]); - - h(qubits[r]); - rx(M_PI_2, qubits[p]); + exp_pauli(theta / 2., "YX", qubits[r], qubits[p]); + exp_pauli(-theta / 2., "XY", qubits[r], qubits[p]); } template @@ -135,168 +67,30 @@ void doubletExcitation(KernelBuilder &kernel, QuakeValue &qubits, auto q = d2.front(); auto p = d2.back(); - std::vector> cnots; - for (auto &i : cudaq::range(d1.size() - 1)) - cnots.emplace_back(d1[i], d1[i + 1]); - - cnots.emplace_back(r, q); - - for (auto &i : cudaq::range(d2.size() - 1)) - cnots.emplace_back(d2[i], d2[i + 1]); - - auto reversed_cnots = cnots; - std::reverse(reversed_cnots.begin(), reversed_cnots.end()); - - kernel.h(qubits[s]); - kernel.h(qubits[r]); - kernel.rx(-M_PI_2, qubits[q]); - kernel.h(qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.h(qubits[s]); - kernel.h(qubits[r]); - kernel.rx(M_PI_2, qubits[q]); - kernel.h(qubits[p]); - + // layer 1 + kernel.exp_pauli((1. / 8.) * theta, "XXYX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 2 - kernel.rx(-M_PI_2, qubits[s]); - kernel.h(qubits[r]); - kernel.rx(-M_PI_2, qubits[q]); - kernel.rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rx(M_PI_2, qubits[s]); - kernel.h(qubits[r]); - kernel.rx(M_PI_2, qubits[q]); - kernel.rx(M_PI_2, qubits[p]); - + kernel.exp_pauli((1. / 8.) * theta, "YXYY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 3 - kernel.h(qubits[s]); - kernel.rx(-M_PI_2, qubits[r]); - kernel.rx(-M_PI_2, qubits[q]); - kernel.rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.h(qubits[s]); - kernel.rx(M_PI_2, qubits[r]); - kernel.rx(M_PI_2, qubits[q]); - kernel.rx(M_PI_2, qubits[p]); - + kernel.exp_pauli((1. / 8.) * theta, "XYYY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 4 - kernel.h(qubits[s]); - kernel.h(qubits[r]); - kernel.h(qubits[q]); - kernel.rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.h(qubits[s]); - kernel.h(qubits[r]); - kernel.h(qubits[q]); - kernel.rx(M_PI_2, qubits[p]); - + kernel.exp_pauli((1. / 8.) * theta, "XXXY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 5 - kernel.rx(-M_PI_2, qubits[s]); - kernel.h(qubits[r]); - kernel.h(qubits[q]); - kernel.h(qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((-1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rx(M_PI_2, qubits[s]); - kernel.h(qubits[r]); - kernel.h(qubits[q]); - kernel.h(qubits[p]); - + kernel.exp_pauli((-1. / 8.) * theta, "YXXX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 6 - kernel.h(qubits[s]); - kernel.rx(-M_PI_2, qubits[r]); - kernel.h(qubits[q]); - kernel.h(qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((-1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.h(qubits[s]); - kernel.rx(M_PI_2, qubits[r]); - kernel.h(qubits[q]); - kernel.h(qubits[p]); - + kernel.exp_pauli((-1. / 8.) * theta, "XYXX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 7 - kernel.rx(-M_PI_2, qubits[s]); - kernel.rx(-M_PI_2, qubits[r]); - kernel.rx(-M_PI_2, qubits[q]); - kernel.h(qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((-1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rx(M_PI_2, qubits[s]); - kernel.rx(M_PI_2, qubits[r]); - kernel.rx(M_PI_2, qubits[q]); - kernel.h(qubits[p]); - + kernel.exp_pauli((-1. / 8.) * theta, "YYYX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 8 - kernel.rx(-M_PI_2, qubits[s]); - kernel.rx(-M_PI_2, qubits[r]); - kernel.h(qubits[q]); - kernel.rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rz((-1. / 8.0) * theta, qubits[p]); - - for (auto &[i, j] : reversed_cnots) - kernel.template x(qubits[i], qubits[j]); - - kernel.rx(M_PI_2, qubits[s]); - kernel.rx(M_PI_2, qubits[r]); - kernel.h(qubits[q]); - kernel.rx(M_PI_2, qubits[p]); + kernel.exp_pauli((-1. / 8.) * theta, "YYXY", qubits[s], qubits[r], qubits[q], + qubits[p]); } __qpu__ void doubletExcitation(cudaq::qspan<> qubits, double theta, @@ -307,168 +101,30 @@ __qpu__ void doubletExcitation(cudaq::qspan<> qubits, double theta, auto q = d2.front(); auto p = d2.back(); - std::vector> cnots; - for (auto &i : cudaq::range(d1.size() - 1)) - cnots.emplace_back(d1[i], d1[i + 1]); - - cnots.emplace_back(r, q); - - for (auto &i : cudaq::range(d2.size() - 1)) - cnots.emplace_back(d2[i], d2[i + 1]); - - auto reversed_cnots = cnots; - std::reverse(reversed_cnots.begin(), reversed_cnots.end()); - - h(qubits[s]); - h(qubits[r]); - rx(-M_PI_2, qubits[q]); - h(qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - h(qubits[s]); - h(qubits[r]); - rx(M_PI_2, qubits[q]); - h(qubits[p]); - + // layer 1 + exp_pauli((1. / 8.) * theta, "XXYX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 2 - rx(-M_PI_2, qubits[s]); - h(qubits[r]); - rx(-M_PI_2, qubits[q]); - rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - rx(M_PI_2, qubits[s]); - h(qubits[r]); - rx(M_PI_2, qubits[q]); - rx(M_PI_2, qubits[p]); - + exp_pauli((1. / 8.) * theta, "YXYY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 3 - h(qubits[s]); - rx(-M_PI_2, qubits[r]); - rx(-M_PI_2, qubits[q]); - rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - h(qubits[s]); - rx(M_PI_2, qubits[r]); - rx(M_PI_2, qubits[q]); - rx(M_PI_2, qubits[p]); - + exp_pauli((1. / 8.) * theta, "XYYY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 4 - h(qubits[s]); - h(qubits[r]); - h(qubits[q]); - rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - h(qubits[s]); - h(qubits[r]); - h(qubits[q]); - rx(M_PI_2, qubits[p]); - + exp_pauli((1. / 8.) * theta, "XXXY", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 5 - rx(-M_PI_2, qubits[s]); - h(qubits[r]); - h(qubits[q]); - h(qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(-theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - rx(M_PI_2, qubits[s]); - h(qubits[r]); - h(qubits[q]); - h(qubits[p]); - + exp_pauli((-1. / 8.) * theta, "YXXX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 6 - h(qubits[s]); - rx(-M_PI_2, qubits[r]); - h(qubits[q]); - h(qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(-theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - h(qubits[s]); - rx(M_PI_2, qubits[r]); - h(qubits[q]); - h(qubits[p]); - + exp_pauli((-1. / 8.) * theta, "XYXX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 7 - rx(-M_PI_2, qubits[s]); - rx(-M_PI_2, qubits[r]); - rx(-M_PI_2, qubits[q]); - h(qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(-theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - rx(M_PI_2, qubits[s]); - rx(M_PI_2, qubits[r]); - rx(M_PI_2, qubits[q]); - h(qubits[p]); - + exp_pauli((-1. / 8.) * theta, "YYYX", qubits[s], qubits[r], qubits[q], + qubits[p]); // layer 8 - rx(-M_PI_2, qubits[s]); - rx(-M_PI_2, qubits[r]); - h(qubits[q]); - rx(-M_PI_2, qubits[p]); - - for (auto &[i, j] : cnots) - x(qubits[i], qubits[j]); - - rz(-theta / 8., qubits[p]); - - for (auto &[i, j] : reversed_cnots) - x(qubits[i], qubits[j]); - - rx(M_PI_2, qubits[s]); - rx(M_PI_2, qubits[r]); - h(qubits[q]); - rx(M_PI_2, qubits[p]); + exp_pauli((-1. / 8.) * theta, "YYXY", qubits[s], qubits[r], qubits[q], + qubits[p]); } std::size_t uccsd_num_parameters(std::size_t nElectrons, std::size_t nQubits) { diff --git a/runtime/cudaq/qis/execution_manager.h b/runtime/cudaq/qis/execution_manager.h index daf223f728..79f6f56119 100644 --- a/runtime/cudaq/qis/execution_manager.h +++ b/runtime/cudaq/qis/execution_manager.h @@ -108,11 +108,13 @@ class ExecutionManager { /// Apply the quantum instruction with the given name, on the provided /// target qudits. Supports input of control qudits and rotational parameters. + /// Can also optionally take a spin_op as input to affect a general + /// Pauli rotation. virtual void apply(const std::string_view gateName, const std::vector ¶ms, const std::vector &controls, const std::vector &targets, - bool isAdjoint = false) = 0; + bool isAdjoint = false, const spin_op op = spin_op()) = 0; /// Reset the qubit to the |0> state virtual void reset(const QuditInfo &target) = 0; diff --git a/runtime/cudaq/qis/managers/BasicExecutionManager.h b/runtime/cudaq/qis/managers/BasicExecutionManager.h index 8d79537ad9..89d36a4c35 100644 --- a/runtime/cudaq/qis/managers/BasicExecutionManager.h +++ b/runtime/cudaq/qis/managers/BasicExecutionManager.h @@ -34,11 +34,11 @@ class BasicExecutionManager : public cudaq::ExecutionManager { protected: /// @brief An instruction is composed of a operation name, - /// a optional set of rotation parameters, control qudits, and - /// target qudits. - using Instruction = - std::tuple, - std::vector, std::vector>; + /// a optional set of rotation parameters, control qudits, + /// target qudits, and an optional spin_op. + using Instruction = std::tuple, + std::vector, + std::vector, spin_op>; /// @brief `typedef` for a queue of instructions using InstructionQueue = std::queue; @@ -207,7 +207,7 @@ class BasicExecutionManager : public cudaq::ExecutionManager { void apply(const std::string_view gateName, const std::vector ¶ms, const std::vector &controls, const std::vector &targets, - bool isAdjoint = false) override { + bool isAdjoint = false, spin_op op = spin_op()) override { // Make a copy of the name that we can mutate if necessary std::string mutable_name(gateName); @@ -240,21 +240,21 @@ class BasicExecutionManager : public cudaq::ExecutionManager { if (!adjointQueueStack.empty()) { // Add to the adjoint instruction queue adjointQueueStack.top().emplace(std::make_tuple( - mutable_name, mutable_params, mutable_controls, mutable_targets)); + mutable_name, mutable_params, mutable_controls, mutable_targets, op)); return; } // Add to the instruction queue instructionQueue.emplace(std::make_tuple(std::move(mutable_name), mutable_params, mutable_controls, - mutable_targets)); + mutable_targets, op)); } void synchronize() override { while (!instructionQueue.empty()) { auto instruction = instructionQueue.front(); if (isInTracerMode()) { - auto [gateName, params, controls, targets] = instruction; + auto [gateName, params, controls, targets, op] = instruction; std::vector controlIds; std::transform(controls.begin(), controls.end(), std::back_inserter(controlIds), diff --git a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp index 27f2d317cd..af57513b7c 100644 --- a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp +++ b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp @@ -102,7 +102,7 @@ class DefaultExecutionManager : public cudaq::BasicExecutionManager { flushRequestedAllocations(); // Get the data, create the Qubit* targets - auto [gateName, parameters, controls, targets] = instruction; + auto [gateName, parameters, controls, targets, op] = instruction; // Map the Qudits to Qubits std::vector localT; @@ -112,6 +112,11 @@ class DefaultExecutionManager : public cudaq::BasicExecutionManager { std::transform(controls.begin(), controls.end(), std::back_inserter(localC), [](auto &&el) { return el.id; }); + if (gateName == "exp_pauli") { + simulator()->applyExpPauli(parameters[0], localT, op); + return; + } + // Apply the gate llvm::StringSwitch>(gateName) .Case("h", [&]() { simulator()->h(localC, localT[0]); }) diff --git a/runtime/cudaq/qis/qubit_qis.h b/runtime/cudaq/qis/qubit_qis.h index e4625af83b..996f5c7b39 100644 --- a/runtime/cudaq/qis/qubit_qis.h +++ b/runtime/cudaq/qis/qubit_qis.h @@ -276,6 +276,35 @@ inline void cs(qubit &q, qubit &r) { s(q, r); } inline void ct(qubit &q, qubit &r) { t(q, r); } inline void ccx(qubit &q, qubit &r, qubit &s) { x(q, r, s); } +/// @brief Apply a general Pauli rotation, takes a qubit register and the +/// size must be equal to the pauli word length. +template + requires(std::ranges::range) +inline void exp_pauli(double theta, QubitRange &&qubits, + const std::string &pauliWord) { + std::vector quditInfos; + std::transform(qubits.begin(), qubits.end(), std::back_inserter(quditInfos), + [](auto &q) { return cudaq::qubitToQuditInfo(q); }); + getExecutionManager()->apply("exp_pauli", {theta}, {}, quditInfos, false, + spin_op::from_word(pauliWord)); +} + +/// @brief Apply a general Pauli rotation, takes a variadic set of +/// qubits, and the number of qubits must be equal to the pauli word length. +template +inline void exp_pauli(double theta, const std::string &pauliWord, + QubitArgs &...qubits) { + + if (sizeof...(QubitArgs) != pauliWord.length()) + throw std::runtime_error( + "Invalid exp_pauli call, number of qubits != size of pauliWord."); + + // Map the qubits to their unique ids and pack them into a std::array + std::vector quditInfos{qubitToQuditInfo(qubits)...}; + getExecutionManager()->apply("exp_pauli", {theta}, {}, quditInfos, false, + spin_op::from_word(pauliWord)); +} + /// @brief Measure an individual qubit, return 0,1 as `bool` inline measure_result mz(qubit &q) { return getExecutionManager()->measure({q.n_levels(), q.id()}); diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index c37ce74aa0..df818b7c23 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -62,6 +62,12 @@ class CircuitSimulator { // do nothing } + /// @brief Apply exp(-i theta PauliTensorProd) to the underlying state. + /// This must be provided by subclasses. + virtual void applyExpPauli(double theta, + const std::vector &qubitIds, + const cudaq::spin_op &op) = 0; + /// @brief Compute the expected value of the given spin op /// with respect to the current state, . virtual cudaq::ExecutionResult observe(const cudaq::spin_op &term) = 0; @@ -621,6 +627,47 @@ class CircuitSimulatorBase : public CircuitSimulator { /// @brief The destructor virtual ~CircuitSimulatorBase() = default; + void applyExpPauli(double theta, const std::vector &qubitIds, + const cudaq::spin_op &op) override { + cudaq::info(" [decomposing] exp_pauli({}, {})", theta, op.to_string(false)); + std::vector qubitSupport; + std::vector> basisChange; + op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { + if (type != cudaq::pauli::I) + qubitSupport.push_back(qubitIds[qubitIdx]); + + if (type == cudaq::pauli::Y) + basisChange.emplace_back([&, qubitIdx](bool reverse) { + rx(!reverse ? M_PI_2 : -M_PI_2, qubitIds[qubitIdx]); + }); + else if (type == cudaq::pauli::X) + basisChange.emplace_back( + [&, qubitIdx](bool) { h(qubitIds[qubitIdx]); }); + }); + + if (!basisChange.empty()) + for (auto &basis : basisChange) + basis(false); + + std::vector> toReverse; + for (std::size_t i = 0; i < qubitSupport.size() - 1; i++) { + x({qubitSupport[i]}, qubitSupport[i + 1]); + toReverse.emplace_back(qubitSupport[i], qubitSupport[i + 1]); + } + + rz(theta, qubitSupport.back()); + + std::reverse(toReverse.begin(), toReverse.end()); + for (auto &[i, j] : toReverse) + x({i}, j); + + if (!basisChange.empty()) { + std::reverse(basisChange.begin(), basisChange.end()); + for (auto &basis : basisChange) + basis(true); + } + } + /// @brief Set the current noise model to consider when /// simulating the state. This should be overridden by /// simulation strategies that support noise modeling. diff --git a/runtime/nvqir/NVQIR.cpp b/runtime/nvqir/NVQIR.cpp index 311e648ee8..57f37790ce 100644 --- a/runtime/nvqir/NVQIR.cpp +++ b/runtime/nvqir/NVQIR.cpp @@ -363,6 +363,14 @@ Result *__quantum__qis__mz__to__register(Qubit *q, const char *name) { return b ? ResultOne : ResultZero; } +void __quantum__qis__exp_pauli(double theta, Array *qubits, char *pauliWord) { + std::string pauliWordStr(pauliWord); + auto qubitsVec = arrayToVectorSizeT(qubits); + nvqir::getCircuitSimulatorInternal()->applyExpPauli( + theta, qubitsVec, cudaq::spin_op::from_word(pauliWordStr)); + return; +} + void __quantum__rt__result_record_output(Result *, int8_t *) {} /// @brief Map an Array pointer containing Paulis to a vector of Paulis. diff --git a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu index 16e963da24..f502ebd45e 100644 --- a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu +++ b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu @@ -363,6 +363,32 @@ public: } } + /// @brief Override base class functionality for a general Pauli + /// rotation to delegate to the performant custatevecApplyPauliRotation. + void applyExpPauli(double theta, const std::vector &qubits, + const cudaq::spin_op &op) override { + flushGateQueue(); + std::vector controls, targets; + std::vector paulis; + op.for_each_pauli([&](cudaq::pauli p, std::size_t i) { + if (p == cudaq::pauli::I) + paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_I); + else if (p == cudaq::pauli::X) + paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_X); + else if (p == cudaq::pauli::Y) + paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_Y); + else + paulis.push_back(custatevecPauli_t::CUSTATEVEC_PAULI_Z); + + targets.push_back(qubits[i]); + }); + + custatevecApplyPauliRotation( + handle, deviceStateVector, cuStateVecCudaDataType, nQubitsAllocated, + theta, paulis.data(), targets.data(), targets.size(), + controls.data(), nullptr, controls.size()); + } + /// @brief Compute the operator expectation value, with respect to /// the current state vector, directly on GPU with the /// given the operator matrix and target qubit indices. diff --git a/test/AST-Quake/exp-pauli-1.cpp b/test/AST-Quake/exp-pauli-1.cpp new file mode 100644 index 0000000000..4e8496ab45 --- /dev/null +++ b/test/AST-Quake/exp-pauli-1.cpp @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: cudaq-quake %s | cudaq-opt | FileCheck %s + +#include + +int main() { + auto kernel = [](double theta) __qpu__ { + cudaq::qreg q(4); + x(q[0]); + x(q[1]); + exp_pauli(theta, q, "XXXY"); + }; +} + + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( +// CHECK-SAME: %[[VAL_0:.*]]: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { +// CHECK: %[[VAL_1:.*]] = cc.alloca f64 +// CHECK: cc.store %[[VAL_0]], %[[VAL_1]] : !cc.ptr +// CHECK: %[[VAL_2:.*]] = quake.alloca !quake.veq<4> +// CHECK: %[[VAL_3:.*]] = quake.extract_ref %[[VAL_2]][0] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_3]] : (!quake.ref) -> () +// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_4]] : (!quake.ref) -> () +// CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_1]] : !cc.ptr +// CHECK: %[[VAL_6:.*]] = cc.string_literal{"XXXY"} : () -> !cc.string +// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_2]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: return +// CHECK: } \ No newline at end of file diff --git a/test/AST-Quake/exp-pauli-2.cpp b/test/AST-Quake/exp-pauli-2.cpp new file mode 100644 index 0000000000..39706363e6 --- /dev/null +++ b/test/AST-Quake/exp-pauli-2.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: cudaq-quake %s | cudaq-opt | FileCheck %s + +#include + +int main() { + auto kernel2 = [](double theta, std::string pauli) __qpu__ { + cudaq::qreg q(4); + x(q[0]); + x(q[1]); + exp_pauli(theta, q, pauli); + }; +} + + + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( +// CHECK-SAME: %[[VAL_0:.*]]: f64, +// CHECK-SAME: %[[VAL_1:.*]]: !cc.string) attributes {"cudaq-entrypoint", "cudaq-kernel"} { +// CHECK: %[[VAL_2:.*]] = cc.alloca f64 +// CHECK: cc.store %[[VAL_0]], %[[VAL_2]] : !cc.ptr +// CHECK: %[[VAL_3:.*]] = quake.alloca !quake.veq<4> +// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_3]][0] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_4]] : (!quake.ref) -> () +// CHECK: %[[VAL_5:.*]] = quake.extract_ref %[[VAL_3]][1] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_5]] : (!quake.ref) -> () +// CHECK: %[[VAL_6:.*]] = cc.load %[[VAL_2]] : !cc.ptr +// CHECK: quake.exp_pauli(%[[VAL_6]]) %[[VAL_3]], %[[VAL_1]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: return +// CHECK: } + diff --git a/test/AST-Quake/exp-pauli-3.cpp b/test/AST-Quake/exp-pauli-3.cpp new file mode 100644 index 0000000000..b701101915 --- /dev/null +++ b/test/AST-Quake/exp-pauli-3.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +// RUN: cudaq-quake %s | cudaq-opt | FileCheck %s + +#include + +int main() { + auto kernel = [](double theta) __qpu__ { + cudaq::qreg q(4); + x(q[0]); + x(q[1]); + exp_pauli(theta, "XXXY", q[0], q[1], q[2], q[3]); + }; +} + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( +// CHECK-SAME: %[[VAL_0:.*]]: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { +// CHECK: %[[VAL_1:.*]] = cc.alloca f64 +// CHECK: cc.store %[[VAL_0]], %[[VAL_1]] : !cc.ptr +// CHECK: %[[VAL_2:.*]] = quake.alloca !quake.veq<4> +// CHECK: %[[VAL_3:.*]] = quake.extract_ref %[[VAL_2]][0] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_3]] : (!quake.ref) -> () +// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref +// CHECK: quake.x %[[VAL_4]] : (!quake.ref) -> () +// CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_1]] : !cc.ptr +// CHECK: %[[VAL_6:.*]] = cc.string_literal{"XXXY"} : () -> !cc.string +// CHECK: %[[VAL_7:.*]] = quake.extract_ref %[[VAL_2]][0] : (!quake.veq<4>) -> !quake.ref +// CHECK: %[[VAL_8:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref +// CHECK: %[[VAL_9:.*]] = quake.extract_ref %[[VAL_2]][2] : (!quake.veq<4>) -> !quake.ref +// CHECK: %[[VAL_10:.*]] = quake.extract_ref %[[VAL_2]][3] : (!quake.veq<4>) -> !quake.ref +// CHECK: %[[VAL_11:.*]] = quake.concat %[[VAL_7]], %[[VAL_8]], %[[VAL_9]], %[[VAL_10]] : (!quake.ref, !quake.ref, !quake.ref, !quake.ref) -> !quake.veq<4> +// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_11]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: return +// CHECK: } \ No newline at end of file diff --git a/test/Quake-QIR/exp-pauli-qir-1.qke b/test/Quake-QIR/exp-pauli-qir-1.qke new file mode 100644 index 0000000000..e1ac68569a --- /dev/null +++ b/test/Quake-QIR/exp-pauli-qir-1.qke @@ -0,0 +1,39 @@ +// ========================================================================== // +// Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// RUN: cudaq-opt %s --canonicalize --quake-add-deallocs | cudaq-translate --convert-to=qir | FileCheck %s + +module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_ZZ4mainENK3$_0clEd"}} { + func.func @__nvqpp__mlirgen__Z4mainE3$_0(%arg0: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { + %0 = cc.alloca f64 + cc.store %arg0, %0 : !cc.ptr + %1 = quake.alloca !quake.veq<4> + %2 = quake.extract_ref %1[0] : (!quake.veq<4>) -> !quake.ref + quake.x %2 : (!quake.ref) -> () + %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref + quake.x %3 : (!quake.ref) -> () + %4 = cc.load %0 : !cc.ptr + %5 = cc.string_literal{"XXXY"} : () -> !cc.string + quake.exp_pauli(%4) %1, %5 : (f64, !quake.veq<4>, !cc.string) -> () + return + } +} +// CHECK: %[[VAL_0:.*]] = tail call +// CHECK: %[[VAL_1:.*]]* @__quantum__rt__qubit_allocate_array(i64 4) +// CHECK: %[[VAL_2:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_slice(%[[VAL_1]]* %[[VAL_0]], i32 1, i64 0, i64 1, i64 3) +// CHECK: %[[VAL_3:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 0) +// CHECK: %[[VAL_4:.*]] = bitcast i8* %[[VAL_3]] to %[[VAL_5:.*]]** +// CHECK: %[[VAL_6:.*]] = load %[[VAL_5]]*, %[[VAL_5]]** %[[VAL_4]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_6]]) +// CHECK: %[[VAL_7:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 1) +// CHECK: %[[VAL_8:.*]] = bitcast i8* %[[VAL_7]] to %[[VAL_5]]** +// CHECK: %[[VAL_9:.*]] = load %[[VAL_5]]*, %[[VAL_5]]** %[[VAL_8]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_9]]) +// CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_10:.*]], %[[VAL_1]]* %[[VAL_2]], i8* nonnull getelementptr inbounds ([5 x i8], [5 x i8]* @cstr.5858585900, i64 0, i64 0)) +// CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) +// CHECK: ret void \ No newline at end of file diff --git a/test/Quake-QIR/exp-pauli-qir-2.qke b/test/Quake-QIR/exp-pauli-qir-2.qke new file mode 100644 index 0000000000..a676ae9bd7 --- /dev/null +++ b/test/Quake-QIR/exp-pauli-qir-2.qke @@ -0,0 +1,42 @@ +// ========================================================================== // +// Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// RUN: cudaq-opt %s --canonicalize --quake-add-deallocs | cudaq-translate --convert-to=qir | FileCheck %s + + +module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_ZZ4mainENK3$_0clEdNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"}} { + func.func @__nvqpp__mlirgen__Z4mainE3$_0(%arg0: f64, %arg1: !cc.string) attributes {"cudaq-entrypoint", "cudaq-kernel"} { + %0 = cc.alloca f64 + cc.store %arg0, %0 : !cc.ptr + %1 = quake.alloca !quake.veq<4> + %2 = quake.extract_ref %1[0] : (!quake.veq<4>) -> !quake.ref + quake.x %2 : (!quake.ref) -> () + %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref + quake.x %3 : (!quake.ref) -> () + %4 = cc.load %0 : !cc.ptr + quake.exp_pauli(%4) %1, %arg1 : (f64, !quake.veq<4>, !cc.string) -> () + return + } +} + +// CHECK: %[[VAL_0:.*]] = tail call +// CHECK: %[[VAL_1:.*]]* @__quantum__rt__qubit_allocate_array(i64 4) +// CHECK: %[[VAL_2:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_slice(%[[VAL_1]]* %[[VAL_0]], i32 1, i64 0, i64 1, i64 3) +// CHECK: %[[VAL_3:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 0) +// CHECK: %[[VAL_4:.*]] = bitcast i8* %[[VAL_3]] to %[[VAL_5:.*]]** +// CHECK: %[[VAL_6:.*]] = load %[[VAL_5]]*, %[[VAL_5]]** %[[VAL_4]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_6]]) +// CHECK: %[[VAL_7:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 1) +// CHECK: %[[VAL_8:.*]] = bitcast i8* %[[VAL_7]] to %[[VAL_5]]** +// CHECK: %[[VAL_9:.*]] = load %[[VAL_5]]*, %[[VAL_5]]** %[[VAL_8]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_9]]) +// CHECK: %[[VAL_10:.*]] = bitcast i8* %[[VAL_11:.*]] to i8** +// CHECK: %[[VAL_12:.*]] = load i8*, i8** %[[VAL_10]], align 8 +// CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_13:.*]], %[[VAL_1]]* %[[VAL_2]], i8* %[[VAL_12]]) +// CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) +// CHECK: ret void \ No newline at end of file diff --git a/test/Quake-QIR/exp-pauli-qir-3.qke b/test/Quake-QIR/exp-pauli-qir-3.qke new file mode 100644 index 0000000000..7f0d5b90b2 --- /dev/null +++ b/test/Quake-QIR/exp-pauli-qir-3.qke @@ -0,0 +1,70 @@ +// ========================================================================== // +// Copyright (c) 2022 - 2023 NVIDIA Corporation & Affiliates. // +// All rights reserved. // +// // +// This source code and the accompanying materials are made available under // +// the terms of the Apache License 2.0 which accompanies this distribution. // +// ========================================================================== // + +// RUN: cudaq-opt %s --canonicalize --quake-add-deallocs | cudaq-translate --convert-to=qir | FileCheck %s + + +module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_ZZ4mainENK3$_0clEd"}} { + func.func @__nvqpp__mlirgen__Z4mainE3$_0(%arg0: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { + %0 = cc.alloca f64 + cc.store %arg0, %0 : !cc.ptr + %1 = quake.alloca !quake.veq<4> + %2 = quake.extract_ref %1[0] : (!quake.veq<4>) -> !quake.ref + quake.x %2 : (!quake.ref) -> () + %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref + quake.x %3 : (!quake.ref) -> () + %4 = cc.load %0 : !cc.ptr + %5 = cc.string_literal{"XXXY"} : () -> !cc.string + %6 = quake.extract_ref %1[0] : (!quake.veq<4>) -> !quake.ref + %7 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref + %8 = quake.extract_ref %1[2] : (!quake.veq<4>) -> !quake.ref + %9 = quake.extract_ref %1[3] : (!quake.veq<4>) -> !quake.ref + %10 = quake.concat %6, %7, %8, %9 : (!quake.ref, !quake.ref, !quake.ref, !quake.ref) -> !quake.veq<4> + quake.exp_pauli(%4) %10, %5 : (f64, !quake.veq<4>, !cc.string) -> () + return + } +} + +// CHECK: %[[VAL_0:.*]] = tail call +// CHECK: %[[VAL_1:.*]]* @__quantum__rt__qubit_allocate_array(i64 4) +// CHECK: %[[VAL_2:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 0) +// CHECK: %[[VAL_3:.*]] = bitcast i8* %[[VAL_2]] to %[[VAL_4:.*]]** +// CHECK: %[[VAL_5:.*]] = load %[[VAL_4]]*, %[[VAL_4]]** %[[VAL_3]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_4]]* %[[VAL_5]]) +// CHECK: %[[VAL_6:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 1) +// CHECK: %[[VAL_7:.*]] = bitcast i8* %[[VAL_6]] to %[[VAL_4]]** +// CHECK: %[[VAL_8:.*]] = load %[[VAL_4]]*, %[[VAL_4]]** %[[VAL_7]], align 8 +// CHECK: tail call void @__quantum__qis__x(%[[VAL_4]]* %[[VAL_8]]) +// CHECK: %[[VAL_9:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 2) +// CHECK: %[[VAL_10:.*]] = bitcast i8* %[[VAL_9]] to i8** +// CHECK: %[[VAL_11:.*]] = load i8*, i8** %[[VAL_10]], align 8 +// CHECK: %[[VAL_12:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_0]], i64 3) +// CHECK: %[[VAL_13:.*]] = bitcast i8* %[[VAL_12]] to i8** +// CHECK: %[[VAL_14:.*]] = load i8*, i8** %[[VAL_13]], align 8 +// CHECK: %[[VAL_15:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_create_1d(i32 8, i64 1) +// CHECK: %[[VAL_16:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_15]], i64 0) +// CHECK: %[[VAL_17:.*]] = bitcast i8* %[[VAL_16]] to %[[VAL_4]]** +// CHECK: store %[[VAL_4]]* %[[VAL_5]], %[[VAL_4]]** %[[VAL_17]], align 8 +// CHECK: %[[VAL_18:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_create_1d(i32 8, i64 1) +// CHECK: %[[VAL_19:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_18]], i64 0) +// CHECK: %[[VAL_20:.*]] = bitcast i8* %[[VAL_19]] to %[[VAL_4]]** +// CHECK: store %[[VAL_4]]* %[[VAL_8]], %[[VAL_4]]** %[[VAL_20]], align 8 +// CHECK: %[[VAL_21:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_concatenate(%[[VAL_1]]* %[[VAL_15]], %[[VAL_1]]* %[[VAL_18]]) +// CHECK: %[[VAL_22:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_create_1d(i32 8, i64 1) +// CHECK: %[[VAL_23:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_22]], i64 0) +// CHECK: %[[VAL_24:.*]] = bitcast i8* %[[VAL_23]] to i8** +// CHECK: store i8* %[[VAL_11]], i8** %[[VAL_24]], align 8 +// CHECK: %[[VAL_25:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_concatenate(%[[VAL_1]]* %[[VAL_21]], %[[VAL_1]]* %[[VAL_22]]) +// CHECK: %[[VAL_26:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_create_1d(i32 8, i64 1) +// CHECK: %[[VAL_27:.*]] = tail call i8* @__quantum__rt__array_get_element_ptr_1d(%[[VAL_1]]* %[[VAL_26]], i64 0) +// CHECK: %[[VAL_28:.*]] = bitcast i8* %[[VAL_27]] to i8** +// CHECK: store i8* %[[VAL_14]], i8** %[[VAL_28]], align 8 +// CHECK: %[[VAL_29:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_concatenate(%[[VAL_1]]* %[[VAL_25]], %[[VAL_1]]* %[[VAL_26]]) +// CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_30:.*]], %[[VAL_1]]* %[[VAL_29]], i8* nonnull getelementptr inbounds ([5 x i8], [5 x i8]* @cstr.5858585900, i64 0, i64 0)) +// CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) +// CHECK: ret void \ No newline at end of file diff --git a/test/Quake/roundtrip-ops.qke b/test/Quake/roundtrip-ops.qke index 818534eb4b..7ec7e5e53c 100644 --- a/test/Quake/roundtrip-ops.qke +++ b/test/Quake/roundtrip-ops.qke @@ -143,6 +143,11 @@ func.func @quantum_ops() { quake.u2 (%f, %g) [%46] %7 : (f32, f32, !quake.veq<3>, !quake.ref) -> () quake.u3 (%f, %g, %47) [%46] %7 : (f32, f32, f64, !quake.veq<3>, !quake.ref) -> () + // Exp Pauli and StringLiteral + %f2 = arith.constant 12.0 : f64 + %48 = cc.string_literal{"XXY"} : () -> !cc.string + quake.exp_pauli (%f2) %46, %48 : (f64, !quake.veq<3>, !cc.string) -> () + return } @@ -250,6 +255,9 @@ func.func @quantum_ops() { // CHECK: quake.rz (%[[VAL_20]]) {{\[}}%[[VAL_54]]] %[[VAL_10]] : (f32, !quake.veq<3>, !quake.ref) -> () // CHECK: quake.u2 (%[[VAL_20]], %[[VAL_21]]) {{\[}}%[[VAL_54]]] %[[VAL_10]] : (f32, f32, !quake.veq<3>, !quake.ref) -> () // CHECK: quake.u3 (%[[VAL_20]], %[[VAL_21]], %[[VAL_53]]) {{\[}}%[[VAL_54]]] %[[VAL_10]] : (f32, f32, f64, !quake.veq<3>, !quake.ref) -> () +// CHECK: %[[VAL_59:.*]] = arith.constant 1.200000e+01 : f64 +// CHECK: %[[VAL_60:.*]] = cc.string_literal{"XXY"} : () -> !cc.string +// CHECK: quake.exp_pauli(%[[VAL_59]]) %[[VAL_54]], %[[VAL_60]] : (f64, !quake.veq<3>, !cc.string) -> () // CHECK: return // CHECK: } diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 6a12a22397..ef06ce7e55 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -64,7 +64,7 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) nvqir-${NVQIR_BACKEND} nvqir cudaq fmt::fmt-header-only cudaq-platform-default - cudaq-builder + cudaq-builder gtest_main) if (${NVQIR_BACKEND} STREQUAL "dm") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_BACKEND_DM) diff --git a/unittests/domains/ChemistryTester.cpp b/unittests/domains/ChemistryTester.cpp index 11f5f3b31d..d0e8383e79 100644 --- a/unittests/domains/ChemistryTester.cpp +++ b/unittests/domains/ChemistryTester.cpp @@ -53,6 +53,29 @@ CUDAQ_TEST(H2MoleculeTester, checkHamiltonian) { } } +CUDAQ_TEST(H2MoleculeTester, checkExpPauli) { + auto kernel = [](double theta) __qpu__ { + cudaq::qreg q(4); + x(q[0]); + x(q[1]); + exp_pauli(theta, q, "XXXY"); + }; + + cudaq::molecular_geometry geometry{{"H", {0., 0., 0.}}, + {"H", {0., 0., .7474}}}; + auto molecule = cudaq::create_molecule(geometry, "sto-3g", 1, 0); + cudaq::observe(kernel, molecule.hamiltonian, 1.1); + + cudaq::optimizers::cobyla optimizer; + auto [e, opt] = optimizer.optimize(1, [&](std::vector x) -> double { + double e = cudaq::observe(kernel, molecule.hamiltonian, x[0]); + printf("E = %lf\n", e); + return e; + }); + + EXPECT_NEAR(-1.137, e, 1e-3); +} + CUDAQ_TEST(H2MoleculeTester, checkUCCSD) { { cudaq::molecular_geometry geometry{{"H", {0., 0., 0.}}, diff --git a/unittests/integration/builder_tester.cpp b/unittests/integration/builder_tester.cpp index dd8ae63f04..4ed4d6800a 100644 --- a/unittests/integration/builder_tester.cpp +++ b/unittests/integration/builder_tester.cpp @@ -654,6 +654,61 @@ CUDAQ_TEST(BuilderTester, checkEntryPointAttribute) { EXPECT_TRUE(std::regex_search(quake, functionDecleration)); } +CUDAQ_TEST(BuilderTester, checkExpPauli) { + { + auto [kernel, theta] = cudaq::make_kernel(); + auto qubits = kernel.qalloc(4); + kernel.x(qubits[0]); + kernel.x(qubits[1]); + kernel.exp_pauli(theta, qubits, "XXXY"); + std::cout << kernel << "\n"; + std::vector h2_data{ + 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, + 0, 0, 2, 0, -0.220041, -0, 1, 3, 3, 1, 0.0454063, 0, + 0, 0, 0, 0, -0.106477, 0, 0, 2, 0, 0, 0.17028, 0, + 0, 0, 0, 2, -0.220041, -0, 3, 3, 1, 1, -0.0454063, -0, + 2, 2, 0, 0, 0.168336, 0, 2, 0, 2, 0, 0.1202, 0, + 0, 2, 0, 2, 0.1202, 0, 2, 0, 0, 2, 0.165607, 0, + 0, 2, 2, 0, 0.165607, 0, 0, 0, 2, 2, 0.174073, 0, + 1, 1, 3, 3, -0.0454063, -0, 15}; + cudaq::spin_op h(h2_data, 4); + cudaq::optimizers::cobyla optimizer; + optimizer.max_eval = 30; + auto [e, opt] = optimizer.optimize(1, [&](std::vector x) -> double { + double e = cudaq::observe(kernel, h, x[0]); + printf("E = %lf, %lf\n", e, x[0]); + return e; + }); + EXPECT_NEAR(e, -1.13, 1e-2); + } + { + auto [kernel, theta] = cudaq::make_kernel(); + auto qubits = kernel.qalloc(4); + kernel.x(qubits[0]); + kernel.x(qubits[1]); + kernel.exp_pauli(theta, "XXXY", qubits[0], qubits[1], qubits[2], qubits[3]); + std::cout << kernel << "\n"; + std::vector h2_data{ + 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, + 0, 0, 2, 0, -0.220041, -0, 1, 3, 3, 1, 0.0454063, 0, + 0, 0, 0, 0, -0.106477, 0, 0, 2, 0, 0, 0.17028, 0, + 0, 0, 0, 2, -0.220041, -0, 3, 3, 1, 1, -0.0454063, -0, + 2, 2, 0, 0, 0.168336, 0, 2, 0, 2, 0, 0.1202, 0, + 0, 2, 0, 2, 0.1202, 0, 2, 0, 0, 2, 0.165607, 0, + 0, 2, 2, 0, 0.165607, 0, 0, 0, 2, 2, 0.174073, 0, + 1, 1, 3, 3, -0.0454063, -0, 15}; + cudaq::spin_op h(h2_data, 4); + cudaq::optimizers::cobyla optimizer; + optimizer.max_eval = 30; + auto [e, opt] = optimizer.optimize(1, [&](std::vector x) -> double { + double e = cudaq::observe(kernel, h, x[0]); + printf("E = %lf, %lf\n", e, x[0]); + return e; + }); + EXPECT_NEAR(e, -1.13, 1e-2); + } +} + #ifndef CUDAQ_BACKEND_DM CUDAQ_TEST(BuilderTester, checkCanProgressivelyBuild) { diff --git a/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp b/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp index 5ea3d1b48a..34640dd793 100644 --- a/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp +++ b/unittests/qudit/simple_qudit/SimpleQuditExecutionManager.cpp @@ -105,7 +105,7 @@ class SimpleQuditExecutionManager : public cudaq::BasicExecutionManager { instructions.emplace("plusGate", [&](const Instruction &inst) { qpp::cmat u(3, 3); u << 0, 0, 1, 1, 0, 0, 0, 1, 0; - auto &[gateName, params, controls, qudits] = inst; + auto &[gateName, params, controls, qudits, op] = inst; auto target = qudits[0]; cudaq::info("Applying plusGate on {}<{}>", target.id, target.levels); state = qpp::applyCTRL(state, u, {}, {target.id}, target.levels); From 55ed00d665252a4abfb0b49f3f1fe3fa7c625ae8 Mon Sep 17 00:00:00 2001 From: Eric Schweitz Date: Wed, 27 Sep 2023 12:43:15 -0700 Subject: [PATCH 2/8] Drop the cc::StringType for the moment. Convert the headers to use ASCIIZ string literals for the final argument to exp_pauli(). (Simplifies the AST presented to the bridge and falls back to using code that handles string literals.) Update LowerToQIR for the new types, fix bugs. Fix up tests. --- include/cudaq/Frontend/nvqpp/ASTBridge.h | 12 +++ include/cudaq/Optimizer/Dialect/CC/CCOps.td | 13 ++- include/cudaq/Optimizer/Dialect/CC/CCTypes.td | 13 --- .../cudaq/Optimizer/Dialect/Quake/QuakeOps.td | 2 +- lib/Frontend/nvqpp/ConvertDecl.cpp | 7 +- lib/Frontend/nvqpp/ConvertExpr.cpp | 38 ++++--- lib/Frontend/nvqpp/ConvertType.cpp | 30 +----- lib/Optimizer/CodeGen/LowerToQIR.cpp | 101 +++++++----------- lib/Optimizer/Dialect/CC/CCTypes.cpp | 7 +- runtime/cudaq/builder/kernel_builder.cpp | 4 +- runtime/cudaq/builder/kernel_builder.h | 9 +- runtime/cudaq/qis/qubit_qis.h | 7 +- test/AST-Quake/exp-pauli-1.cpp | 6 +- test/AST-Quake/exp-pauli-2.cpp | 6 +- test/AST-Quake/exp-pauli-3.cpp | 8 +- test/Quake-QIR/exp-pauli-qir-1.qke | 6 +- test/Quake-QIR/exp-pauli-qir-2.qke | 10 +- test/Quake-QIR/exp-pauli-qir-3.qke | 6 +- test/Quake/roundtrip-ops.qke | 8 +- 19 files changed, 128 insertions(+), 165 deletions(-) diff --git a/include/cudaq/Frontend/nvqpp/ASTBridge.h b/include/cudaq/Frontend/nvqpp/ASTBridge.h index e9217fd970..f1ea7facba 100644 --- a/include/cudaq/Frontend/nvqpp/ASTBridge.h +++ b/include/cudaq/Frontend/nvqpp/ASTBridge.h @@ -703,4 +703,16 @@ inline bool isCallOperator(clang::OverloadedOperatorKind kindValue) { return kindValue == clang::OverloadedOperatorKind::OO_Call; } +// Is \p t of type `char *`? +inline bool isCharPointerType(mlir::Type t) { + if (auto ptrTy = dyn_cast(t)) { + mlir::Type eleTy = ptrTy.getElementType(); + if (auto arrTy = dyn_cast(eleTy)) + eleTy = arrTy.getElementType(); + if (auto intTy = dyn_cast(eleTy)) + return intTy.getWidth() == 8; + } + return false; +} + } // namespace cudaq diff --git a/include/cudaq/Optimizer/Dialect/CC/CCOps.td b/include/cudaq/Optimizer/Dialect/CC/CCOps.td index 17d77c6c81..98dc2215c4 100644 --- a/include/cudaq/Optimizer/Dialect/CC/CCOps.td +++ b/include/cudaq/Optimizer/Dialect/CC/CCOps.td @@ -1372,14 +1372,19 @@ def cc_CallableClosureOp : CCOp<"callable_closure", [Pure]> { def cc_CreateStringLiteralOp : CCOp<"string_literal"> { let summary = "Create a constant string literal."; let description = [{ - This operation creates CC StringType values from constant MLIR - String Attributes. + This operation creates a ASCIIZ string literal value. It's argument is a + constant MLIR String Attribute. The literal will have a null character + appended automatically. + + ```mlir + %0 = cc.string_literal "Quantum Computing" : !cc.ptr> + ``` }]; let arguments = (ins StrAttr:$stringLiteral); - let results = (outs cc_StringType:$result); + let results = (outs cc_PointerType:$result); let assemblyFormat = [{ - `{` $stringLiteral `}` `:` functional-type(operands, results) attr-dict + $stringLiteral `:` qualified(type(results)) attr-dict }]; } #endif // CUDAQ_OPTIMIZER_DIALECT_CC_OPS diff --git a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td index da15c039aa..16b9bbb13a 100644 --- a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td +++ b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td @@ -24,19 +24,6 @@ class CCType traits = [], } -//===----------------------------------------------------------------------===// -// StringType -//===----------------------------------------------------------------------===// - -def cc_StringType : CCType<"String", "string"> { - let summary = "A C++ string type."; - let description = [{ - The bridge can use this type to represent std::string values. This type - is basically a placeholder type that we can usher down to the LLVM Dialect - level. - }]; -} - //===----------------------------------------------------------------------===// // PointerType //===----------------------------------------------------------------------===// diff --git a/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td b/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td index ad82065f8c..882a993ec5 100644 --- a/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td +++ b/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.td @@ -795,7 +795,7 @@ def ExpPauliOp : QuakeOp<"exp_pauli", []> { applies exp(i theta P) where P is a general Pauli tensor product. }]; - let arguments = (ins AnyFloat:$parameter, VeqType:$qubits, cc_StringType:$pauli); + let arguments = (ins AnyFloat:$parameter, VeqType:$qubits, cc_PointerType:$pauli); let results = (outs ); let assemblyFormat = [{ diff --git a/lib/Frontend/nvqpp/ConvertDecl.cpp b/lib/Frontend/nvqpp/ConvertDecl.cpp index 5d4baeb7a2..a1cfae0eb8 100644 --- a/lib/Frontend/nvqpp/ConvertDecl.cpp +++ b/lib/Frontend/nvqpp/ConvertDecl.cpp @@ -90,9 +90,10 @@ void QuakeBridgeVisitor::addArgumentSymbols( // Transform pass-by-value arguments to stack slots. auto loc = toLocation(argVal); auto parmTy = entryBlock->getArgument(index).getType(); - if (isa(parmTy)) { + if (isa(parmTy)) { symbolTable.insert(name, entryBlock->getArgument(index)); } else { auto stackSlot = builder.create(loc, parmTy); diff --git a/lib/Frontend/nvqpp/ConvertExpr.cpp b/lib/Frontend/nvqpp/ConvertExpr.cpp index 78934f51b6..db4c79a46f 100644 --- a/lib/Frontend/nvqpp/ConvertExpr.cpp +++ b/lib/Frontend/nvqpp/ConvertExpr.cpp @@ -948,8 +948,7 @@ bool QuakeBridgeVisitor::VisitMaterializeTemporaryExpr( // The following cases are λ expressions, quantum data, or a std::vector view. // In those cases, there is nothing to materialize, so we can just pass the // Value on the top of the stack. - if (isa(ty)) + if (isa(ty)) return true; // If not one of the above special cases, then materialize the value to a @@ -1221,9 +1220,24 @@ bool QuakeBridgeVisitor::VisitCallExpr(clang::CallExpr *x) { if (funcName.equals("exp_pauli")) { assert(args.size() > 2); SmallVector processedArgs; - if (args.size() == 3 && isa(args[1].getType())) - processedArgs = args; - else { + auto addTheString = [&](Value v) { + // The C-string argument (char*) may be loaded by an lvalue to rvalue + // cast. Here, we must pass the pointer and not the first character's + // value. + if (isCharPointerType(v.getType())) { + processedArgs.push_back(v); + } else if (auto load = v.getDefiningOp()) { + processedArgs.push_back(load.getPtrvalue()); + } else { + reportClangError(x, mangler, "could not determine string argument"); + } + }; + if (args.size() == 3 && isa(args[1].getType())) { + // Have f64, veq, string + processedArgs.push_back(args[0]); + processedArgs.push_back(args[1]); + addTheString(args[2]); + } else { // should have f64, string, qubits... // need f64, veq, string, so process here @@ -1237,9 +1251,7 @@ bool QuakeBridgeVisitor::VisitCallExpr(clang::CallExpr *x) { processedArgs.push_back(builder.create( loc, quake::VeqType::get(builder.getContext(), quantumArgs.size()), quantumArgs)); - - // add the string - processedArgs.push_back(args[1]); + addTheString(args[1]); } builder.create(loc, TypeRange{}, processedArgs); @@ -2120,12 +2132,6 @@ bool QuakeBridgeVisitor::VisitCXXConstructExpr(clang::CXXConstructExpr *x) { } } - // Is this a std::string constructed from a StringLiteral? If yes - // return true, we have what we need. - if (!ctor->isDefaultConstructor() && ctorName == "basic_string") - if (isa(peekValue().getType())) - return true; - if (ctor->isCopyConstructor()) if (auto *parent = ctor->getParent()) if (parent->isLambda()) { @@ -2203,8 +2209,10 @@ bool QuakeBridgeVisitor::VisitDeclRefExpr(clang::DeclRefExpr *x) { } bool QuakeBridgeVisitor::VisitStringLiteral(clang::StringLiteral *x) { + auto strLitTy = cc::PointerType::get(cc::ArrayType::get( + builder.getContext(), builder.getI8Type(), x->getString().size() + 1)); return pushValue(builder.create( - toLocation(x), builder.getStringAttr(x->getString()))); + toLocation(x), strLitTy, builder.getStringAttr(x->getString()))); } } // namespace cudaq::details diff --git a/lib/Frontend/nvqpp/ConvertType.cpp b/lib/Frontend/nvqpp/ConvertType.cpp index f645ec67c5..376edf3a87 100644 --- a/lib/Frontend/nvqpp/ConvertType.cpp +++ b/lib/Frontend/nvqpp/ConvertType.cpp @@ -17,7 +17,6 @@ using namespace mlir; static bool isArithmeticType(Type t) { return isa(t); } -static bool isStringType(Type t) { return isa(t); } static bool isQuantumType(Type t) { return isa(t); @@ -83,31 +82,6 @@ QuakeBridgeVisitor::findCallOperator(const clang::CXXRecordDecl *decl) { bool QuakeBridgeVisitor::TraverseRecordType(clang::RecordType *t) { auto *recDecl = t->getDecl(); - // Is this RecordDecl a std::string type - auto isStdString = [](clang::RecordDecl *decl) { - // Not in std, can't be std::string - if (!isInNamespace(decl, "std")) - return false; - - // Has no identifier, can't be std::string - auto *ident = decl->getIdentifier(); - if (!ident) - return false; - - // The type name is basic_string, check that - if (ident->getName().equals("basic_string")) - return true; - - // Wasn't what we're looking for. - return false; - }(recDecl); - - // If this is a std::string, add the type to the stack and return - if (isStdString) { - pushType(cc::StringType::get(builder.getContext())); - return true; - } - if (ignoredClass(recDecl)) return true; auto reci = records.find(t); @@ -331,9 +305,9 @@ bool QuakeBridgeVisitor::doSyntaxChecks(const clang::FunctionDecl *x) { for (auto [t, p] : llvm::zip(funcTy.getInputs(), x->parameters())) { // Structs, lambdas, functions are valid callable objects. Also pure // device kernels may take veq and/or ref arguments. - if (isArithmeticType(t) || isArithmeticSequenceType(t) || isStringType(t) || + if (isArithmeticType(t) || isArithmeticSequenceType(t) || isQuantumType(t) || isKernelCallable(t) || isFunctionCallable(t) || - isReferenceToCallableRecord(t, p)) + isCharPointerType(t) || isReferenceToCallableRecord(t, p)) continue; reportClangError(p, mangler, "kernel argument type not supported"); return false; diff --git a/lib/Optimizer/CodeGen/LowerToQIR.cpp b/lib/Optimizer/CodeGen/LowerToQIR.cpp index 65a5e721d5..568c2bc545 100644 --- a/lib/Optimizer/CodeGen/LowerToQIR.cpp +++ b/lib/Optimizer/CodeGen/LowerToQIR.cpp @@ -316,16 +316,14 @@ class SubveqOpRewrite : public ConvertOpToLLVMPattern { }; /// Lower the quake.reset op to QIR -template -class ResetRewrite : public ConvertOpToLLVMPattern { +class ResetRewrite : public ConvertOpToLLVMPattern { public: - using Base = ConvertOpToLLVMPattern; - using Base::Base; + using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; LogicalResult - matchAndRewrite(ResetOpType instOp, typename Base::OpAdaptor adaptor, + matchAndRewrite(quake::ResetOp instOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - auto parentModule = instOp->template getParentOfType(); + auto parentModule = instOp->getParentOfType(); auto context = parentModule->getContext(); std::string qirQisPrefix(cudaq::opt::QIRQISPrefix); std::string instName = instOp->getName().stripDialect().str(); @@ -351,15 +349,14 @@ class ResetRewrite : public ConvertOpToLLVMPattern { /// Lower exp_pauli(f64, veq, cc.string) to __quantum__qis__exp_pauli class ExpPauliRewrite : public ConvertOpToLLVMPattern { public: - using Base = ConvertOpToLLVMPattern; - using Base::Base; + using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; LogicalResult - matchAndRewrite(quake::ExpPauliOp instOp, typename Base::OpAdaptor adaptor, + matchAndRewrite(quake::ExpPauliOp instOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { auto loc = instOp->getLoc(); - auto parentModule = instOp->template getParentOfType(); - auto context = parentModule->getContext(); + auto parentModule = instOp->getParentOfType(); + auto *context = rewriter.getContext(); std::string qirQisPrefix(cudaq::opt::QIRQISPrefix); auto qirFunctionName = qirQisPrefix + "exp_pauli"; FlatSymbolRefAttr symbolRef = cudaq::opt::factory::createLLVMFunctionSymbol( @@ -367,27 +364,13 @@ class ExpPauliRewrite : public ConvertOpToLLVMPattern { {rewriter.getF64Type(), cudaq::opt::getArrayType(context), cudaq::opt::factory::getPointerType(context)}, parentModule); - - // The Pauli string can come from a BlockArgument, in which case it - // is a cc.string, or it can come to us here as the return type of - // the string_literal op (which ends up being a AddressOf to a global - // string, bitcasted to a i8*). If it is a block argument, then we need to - // treat that cc.string type as a pointer to a i8* (which the std::string - // ultimately comes to us as [a ptr arg, which contains a ptr to the data]) SmallVector operands = adaptor.getOperands(); - if (!operands.back().getDefiningOp()) { - Value casted = rewriter.create( - loc, - cudaq::opt::factory::getPointerType( - cudaq::opt::factory::getPointerType(context)), - adaptor.getOperands().back()); - - casted = rewriter.create( - loc, cudaq::opt::factory::getPointerType(context), casted); - operands = adaptor.getOperands().drop_back(); - operands.push_back(casted); - } - + // Make sure to drop any length information from the type of the Pauli word. + auto pauliWord = operands.back(); + operands.pop_back(); + auto castedPauli = rewriter.create( + loc, cudaq::opt::factory::getPointerType(context), pauliWord); + operands.push_back(castedPauli); rewriter.replaceOpWithNewOp(instOp, TypeRange{}, symbolRef, operands); return success(); @@ -1368,8 +1351,6 @@ class CreateStringLiteralOpPattern ConversionPatternRewriter &rewriter) const override { auto loc = stringLiteralOp.getLoc(); auto parentModule = stringLiteralOp->getParentOfType(); - auto context = parentModule->getContext(); - StringRef stringLiteral = stringLiteralOp.getStringLiteral(); // Write to the module body @@ -1384,14 +1365,11 @@ class CreateStringLiteralOpPattern // Shift back to the function rewriter.restoreInsertionPoint(insertPoint); - // Get the string address and bit cast - auto slRef = rewriter.create( - loc, cudaq::opt::factory::getPointerType(slGlobal.getType()), + // Get the string address + rewriter.replaceOpWithNewOp( + stringLiteralOp, + cudaq::opt::factory::getPointerType(slGlobal.getType()), slGlobal.getSymName()); - Value castedSlRef = rewriter.create( - loc, cudaq::opt::factory::getPointerType(context), slRef); - - rewriter.replaceOp(stringLiteralOp, castedSlRef); return success(); } @@ -1507,26 +1485,26 @@ class QuakeToQIRRewrite : public cudaq::opt::QuakeToQIRBase { patterns.insert( context); - patterns.insert< - AllocaOpRewrite, AllocaOpPattern, CallableClosureOpPattern, - CallableFuncOpPattern, CallCallableOpPattern, CastOpPattern, - ComputePtrOpPattern, ConcatOpRewrite, DeallocOpRewrite, - CreateStringLiteralOpPattern, ExtractQubitOpRewrite, - ExtractValueOpPattern, FuncToPtrOpPattern, InsertValueOpPattern, - InstantiateCallableOpPattern, LoadOpPattern, ExpPauliRewrite, - OneTargetRewrite, OneTargetRewrite, - OneTargetRewrite, OneTargetRewrite, - OneTargetRewrite, OneTargetRewrite, - OneTargetOneParamRewrite, - OneTargetTwoParamRewrite, - OneTargetOneParamRewrite, - OneTargetOneParamRewrite, - OneTargetOneParamRewrite, - OneTargetTwoParamRewrite, - OneTargetTwoParamRewrite, ResetRewrite, - StdvecDataOpPattern, StdvecInitOpPattern, StdvecSizeOpPattern, - StoreOpPattern, SubveqOpRewrite, TwoTargetRewrite, - UndefOpPattern>(typeConverter); + patterns + .insert, OneTargetRewrite, + OneTargetRewrite, OneTargetRewrite, + OneTargetRewrite, OneTargetRewrite, + OneTargetOneParamRewrite, + OneTargetTwoParamRewrite, + OneTargetOneParamRewrite, + OneTargetOneParamRewrite, + OneTargetOneParamRewrite, + OneTargetTwoParamRewrite, + OneTargetTwoParamRewrite, ResetRewrite, + StdvecDataOpPattern, StdvecInitOpPattern, StdvecSizeOpPattern, + StoreOpPattern, SubveqOpRewrite, + TwoTargetRewrite, UndefOpPattern>(typeConverter); patterns.insert>(typeConverter, measureCounter); target.addLegalDialect(); @@ -1575,9 +1553,6 @@ class QuakeToQIRRewrite : public cudaq::opt::QuakeToQIRBase { members.push_back(typeConverter.convertType(t)); return LLVM::LLVMStructType::getLiteral(type.getContext(), members); }); - typeConverter.addConversion([&](cudaq::cc::StringType type) { - return cudaq::opt::factory::getPointerType(type.getContext()); - }); } }; diff --git a/lib/Optimizer/Dialect/CC/CCTypes.cpp b/lib/Optimizer/Dialect/CC/CCTypes.cpp index 92f5fed55d..95fe5e1fd4 100644 --- a/lib/Optimizer/Dialect/CC/CCTypes.cpp +++ b/lib/Optimizer/Dialect/CC/CCTypes.cpp @@ -133,17 +133,16 @@ void cc::ArrayType::print(AsmPrinter &printer) const { #define GET_TYPEDEF_CLASSES #include "cudaq/Optimizer/Dialect/CC/CCTypes.cpp.inc" +//===----------------------------------------------------------------------===// + namespace cudaq { cc::CallableType cc::CallableType::getNoSignature(MLIRContext *ctx) { return CallableType::get(ctx, FunctionType::get(ctx, {}, {})); } -//===----------------------------------------------------------------------===// - void cc::CCDialect::registerTypes() { - addTypes(); + addTypes(); } } // namespace cudaq diff --git a/runtime/cudaq/builder/kernel_builder.cpp b/runtime/cudaq/builder/kernel_builder.cpp index d024586566..a395d6bc97 100644 --- a/runtime/cudaq/builder/kernel_builder.cpp +++ b/runtime/cudaq/builder/kernel_builder.cpp @@ -188,8 +188,10 @@ void exp_pauli(ImplicitLocOpBuilder &builder, const QuakeValue &theta, "type as first argument."); cudaq::info("kernel_builder apply exp_pauli {}", pauliWord); + auto strLitTy = cc::PointerType::get(cc::ArrayType::get( + builder.getContext(), builder.getI8Type(), pauliWord.size() + 1)); Value stringLiteral = builder.create( - builder.getStringAttr(pauliWord)); + strLitTy, builder.getStringAttr(pauliWord)); SmallVector args{thetaVal, qubitsVal, stringLiteral}; builder.create(TypeRange{}, args); return; diff --git a/runtime/cudaq/builder/kernel_builder.h b/runtime/cudaq/builder/kernel_builder.h index 8f0814dfdd..8200d61ea9 100644 --- a/runtime/cudaq/builder/kernel_builder.h +++ b/runtime/cudaq/builder/kernel_builder.h @@ -547,9 +547,9 @@ class kernel_builder : public details::kernel_builder_base { /// @brief Apply a general pauli rotation, exp(-i theta P), /// takes a QuakeValue representing a register of qubits. - template + template void exp_pauli(const ParamT &theta, const QuakeValue &qubits, - const std::string &pauliWord) { + const char (&pauliWord)[Size]) { std::vector qubitValues{qubits}; if constexpr (std::is_floating_point_v) details::exp_pauli(*opBuilder, QuakeValue(*opBuilder, theta), qubitValues, @@ -560,8 +560,9 @@ class kernel_builder : public details::kernel_builder_base { /// @brief Apply a general pauli rotation, exp(-i theta P), /// takes a variadic list of QuakeValues representing a individual qubits. - template - void exp_pauli(const ParamT &theta, const std::string &pauliWord, + template + void exp_pauli(const ParamT &theta, const char (&pauliWord)[Size], QubitArgs &&...qubits) { std::vector qubitValues{qubits...}; if constexpr (std::is_floating_point_v) diff --git a/runtime/cudaq/qis/qubit_qis.h b/runtime/cudaq/qis/qubit_qis.h index 996f5c7b39..54d2e3feae 100644 --- a/runtime/cudaq/qis/qubit_qis.h +++ b/runtime/cudaq/qis/qubit_qis.h @@ -14,6 +14,7 @@ #include "qarray.h" #include "qreg.h" #include "qvector.h" +#include #include #define __qpu__ __attribute__((annotate("quantum"))) @@ -281,7 +282,7 @@ inline void ccx(qubit &q, qubit &r, qubit &s) { x(q, r, s); } template requires(std::ranges::range) inline void exp_pauli(double theta, QubitRange &&qubits, - const std::string &pauliWord) { + const char *pauliWord) { std::vector quditInfos; std::transform(qubits.begin(), qubits.end(), std::back_inserter(quditInfos), [](auto &q) { return cudaq::qubitToQuditInfo(q); }); @@ -292,10 +293,10 @@ inline void exp_pauli(double theta, QubitRange &&qubits, /// @brief Apply a general Pauli rotation, takes a variadic set of /// qubits, and the number of qubits must be equal to the pauli word length. template -inline void exp_pauli(double theta, const std::string &pauliWord, +inline void exp_pauli(double theta, const char *pauliWord, QubitArgs &...qubits) { - if (sizeof...(QubitArgs) != pauliWord.length()) + if (sizeof...(QubitArgs) != std::strlen(pauliWord)) throw std::runtime_error( "Invalid exp_pauli call, number of qubits != size of pauliWord."); diff --git a/test/AST-Quake/exp-pauli-1.cpp b/test/AST-Quake/exp-pauli-1.cpp index 4e8496ab45..88b80b06b3 100644 --- a/test/AST-Quake/exp-pauli-1.cpp +++ b/test/AST-Quake/exp-pauli-1.cpp @@ -30,7 +30,7 @@ int main() { // CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref // CHECK: quake.x %[[VAL_4]] : (!quake.ref) -> () // CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_1]] : !cc.ptr -// CHECK: %[[VAL_6:.*]] = cc.string_literal{"XXXY"} : () -> !cc.string -// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_2]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: %[[VAL_6:.*]] = cc.string_literal "XXXY" : !cc.ptr> +// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_2]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.ptr>) -> () // CHECK: return -// CHECK: } \ No newline at end of file +// CHECK: } diff --git a/test/AST-Quake/exp-pauli-2.cpp b/test/AST-Quake/exp-pauli-2.cpp index 39706363e6..4404bb32d1 100644 --- a/test/AST-Quake/exp-pauli-2.cpp +++ b/test/AST-Quake/exp-pauli-2.cpp @@ -11,7 +11,7 @@ #include int main() { - auto kernel2 = [](double theta, std::string pauli) __qpu__ { + auto kernel2 = [](double theta, const char *pauli) __qpu__ { cudaq::qreg q(4); x(q[0]); x(q[1]); @@ -23,7 +23,7 @@ int main() { // CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( // CHECK-SAME: %[[VAL_0:.*]]: f64, -// CHECK-SAME: %[[VAL_1:.*]]: !cc.string) attributes {"cudaq-entrypoint", "cudaq-kernel"} { +// CHECK-SAME: %[[VAL_1:.*]]: !cc.ptr) attributes {"cudaq-entrypoint", "cudaq-kernel"} { // CHECK: %[[VAL_2:.*]] = cc.alloca f64 // CHECK: cc.store %[[VAL_0]], %[[VAL_2]] : !cc.ptr // CHECK: %[[VAL_3:.*]] = quake.alloca !quake.veq<4> @@ -32,7 +32,7 @@ int main() { // CHECK: %[[VAL_5:.*]] = quake.extract_ref %[[VAL_3]][1] : (!quake.veq<4>) -> !quake.ref // CHECK: quake.x %[[VAL_5]] : (!quake.ref) -> () // CHECK: %[[VAL_6:.*]] = cc.load %[[VAL_2]] : !cc.ptr -// CHECK: quake.exp_pauli(%[[VAL_6]]) %[[VAL_3]], %[[VAL_1]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: quake.exp_pauli(%[[VAL_6]]) %[[VAL_3]], %[[VAL_1]] : (f64, !quake.veq<4>, !cc.ptr) -> () // CHECK: return // CHECK: } diff --git a/test/AST-Quake/exp-pauli-3.cpp b/test/AST-Quake/exp-pauli-3.cpp index b701101915..aa5aee93d0 100644 --- a/test/AST-Quake/exp-pauli-3.cpp +++ b/test/AST-Quake/exp-pauli-3.cpp @@ -20,7 +20,7 @@ int main() { } // CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( -// CHECK-SAME: %[[VAL_0:.*]]: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { +// CHECK-SAME: %[[VAL_0:.*]]: f64) attributes // CHECK: %[[VAL_1:.*]] = cc.alloca f64 // CHECK: cc.store %[[VAL_0]], %[[VAL_1]] : !cc.ptr // CHECK: %[[VAL_2:.*]] = quake.alloca !quake.veq<4> @@ -29,12 +29,12 @@ int main() { // CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref // CHECK: quake.x %[[VAL_4]] : (!quake.ref) -> () // CHECK: %[[VAL_5:.*]] = cc.load %[[VAL_1]] : !cc.ptr -// CHECK: %[[VAL_6:.*]] = cc.string_literal{"XXXY"} : () -> !cc.string +// CHECK: %[[VAL_6:.*]] = cc.string_literal "XXXY" : !cc.ptr> // CHECK: %[[VAL_7:.*]] = quake.extract_ref %[[VAL_2]][0] : (!quake.veq<4>) -> !quake.ref // CHECK: %[[VAL_8:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<4>) -> !quake.ref // CHECK: %[[VAL_9:.*]] = quake.extract_ref %[[VAL_2]][2] : (!quake.veq<4>) -> !quake.ref // CHECK: %[[VAL_10:.*]] = quake.extract_ref %[[VAL_2]][3] : (!quake.veq<4>) -> !quake.ref // CHECK: %[[VAL_11:.*]] = quake.concat %[[VAL_7]], %[[VAL_8]], %[[VAL_9]], %[[VAL_10]] : (!quake.ref, !quake.ref, !quake.ref, !quake.ref) -> !quake.veq<4> -// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_11]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.string) -> () +// CHECK: quake.exp_pauli(%[[VAL_5]]) %[[VAL_11]], %[[VAL_6]] : (f64, !quake.veq<4>, !cc.ptr>) -> () // CHECK: return -// CHECK: } \ No newline at end of file +// CHECK: } diff --git a/test/Quake-QIR/exp-pauli-qir-1.qke b/test/Quake-QIR/exp-pauli-qir-1.qke index e1ac68569a..c646cbf6f0 100644 --- a/test/Quake-QIR/exp-pauli-qir-1.qke +++ b/test/Quake-QIR/exp-pauli-qir-1.qke @@ -18,8 +18,8 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref quake.x %3 : (!quake.ref) -> () %4 = cc.load %0 : !cc.ptr - %5 = cc.string_literal{"XXXY"} : () -> !cc.string - quake.exp_pauli(%4) %1, %5 : (f64, !quake.veq<4>, !cc.string) -> () + %5 = cc.string_literal "XXXY" : !cc.ptr> + quake.exp_pauli(%4) %1, %5 : (f64, !quake.veq<4>, !cc.ptr>) -> () return } } @@ -36,4 +36,4 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z // CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_9]]) // CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_10:.*]], %[[VAL_1]]* %[[VAL_2]], i8* nonnull getelementptr inbounds ([5 x i8], [5 x i8]* @cstr.5858585900, i64 0, i64 0)) // CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) -// CHECK: ret void \ No newline at end of file +// CHECK: ret void diff --git a/test/Quake-QIR/exp-pauli-qir-2.qke b/test/Quake-QIR/exp-pauli-qir-2.qke index a676ae9bd7..8a8583f656 100644 --- a/test/Quake-QIR/exp-pauli-qir-2.qke +++ b/test/Quake-QIR/exp-pauli-qir-2.qke @@ -10,7 +10,7 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_ZZ4mainENK3$_0clEdNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"}} { - func.func @__nvqpp__mlirgen__Z4mainE3$_0(%arg0: f64, %arg1: !cc.string) attributes {"cudaq-entrypoint", "cudaq-kernel"} { + func.func @__nvqpp__mlirgen__Z4mainE3$_0(%arg0: f64, %arg1: !cc.ptr) attributes {"cudaq-entrypoint", "cudaq-kernel"} { %0 = cc.alloca f64 cc.store %arg0, %0 : !cc.ptr %1 = quake.alloca !quake.veq<4> @@ -19,7 +19,7 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref quake.x %3 : (!quake.ref) -> () %4 = cc.load %0 : !cc.ptr - quake.exp_pauli(%4) %1, %arg1 : (f64, !quake.veq<4>, !cc.string) -> () + quake.exp_pauli(%4) %1, %arg1 : (f64, !quake.veq<4>, !cc.ptr) -> () return } } @@ -35,8 +35,6 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z // CHECK: %[[VAL_8:.*]] = bitcast i8* %[[VAL_7]] to %[[VAL_5]]** // CHECK: %[[VAL_9:.*]] = load %[[VAL_5]]*, %[[VAL_5]]** %[[VAL_8]], align 8 // CHECK: tail call void @__quantum__qis__x(%[[VAL_5]]* %[[VAL_9]]) -// CHECK: %[[VAL_10:.*]] = bitcast i8* %[[VAL_11:.*]] to i8** -// CHECK: %[[VAL_12:.*]] = load i8*, i8** %[[VAL_10]], align 8 -// CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_13:.*]], %[[VAL_1]]* %[[VAL_2]], i8* %[[VAL_12]]) +// CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_13:.*]], %[[VAL_1]]* %[[VAL_2]], i8* %[[VAL_12:.*]]) // CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) -// CHECK: ret void \ No newline at end of file +// CHECK: ret void diff --git a/test/Quake-QIR/exp-pauli-qir-3.qke b/test/Quake-QIR/exp-pauli-qir-3.qke index 7f0d5b90b2..5270316b4e 100644 --- a/test/Quake-QIR/exp-pauli-qir-3.qke +++ b/test/Quake-QIR/exp-pauli-qir-3.qke @@ -19,13 +19,13 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z %3 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref quake.x %3 : (!quake.ref) -> () %4 = cc.load %0 : !cc.ptr - %5 = cc.string_literal{"XXXY"} : () -> !cc.string + %5 = cc.string_literal "XXXY" : !cc.ptr> %6 = quake.extract_ref %1[0] : (!quake.veq<4>) -> !quake.ref %7 = quake.extract_ref %1[1] : (!quake.veq<4>) -> !quake.ref %8 = quake.extract_ref %1[2] : (!quake.veq<4>) -> !quake.ref %9 = quake.extract_ref %1[3] : (!quake.veq<4>) -> !quake.ref %10 = quake.concat %6, %7, %8, %9 : (!quake.ref, !quake.ref, !quake.ref, !quake.ref) -> !quake.veq<4> - quake.exp_pauli(%4) %10, %5 : (f64, !quake.veq<4>, !cc.string) -> () + quake.exp_pauli(%4) %10, %5 : (f64, !quake.veq<4>, !cc.ptr>) -> () return } } @@ -67,4 +67,4 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__Z4mainE3$_0 = "_Z // CHECK: %[[VAL_29:.*]] = tail call %[[VAL_1]]* @__quantum__rt__array_concatenate(%[[VAL_1]]* %[[VAL_25]], %[[VAL_1]]* %[[VAL_26]]) // CHECK: tail call void @__quantum__qis__exp_pauli(double %[[VAL_30:.*]], %[[VAL_1]]* %[[VAL_29]], i8* nonnull getelementptr inbounds ([5 x i8], [5 x i8]* @cstr.5858585900, i64 0, i64 0)) // CHECK: tail call void @__quantum__rt__qubit_release_array(%[[VAL_1]]* %[[VAL_0]]) -// CHECK: ret void \ No newline at end of file +// CHECK: ret void diff --git a/test/Quake/roundtrip-ops.qke b/test/Quake/roundtrip-ops.qke index 7ec7e5e53c..432c8327d8 100644 --- a/test/Quake/roundtrip-ops.qke +++ b/test/Quake/roundtrip-ops.qke @@ -145,8 +145,8 @@ func.func @quantum_ops() { // Exp Pauli and StringLiteral %f2 = arith.constant 12.0 : f64 - %48 = cc.string_literal{"XXY"} : () -> !cc.string - quake.exp_pauli (%f2) %46, %48 : (f64, !quake.veq<3>, !cc.string) -> () + %48 = cc.string_literal "XXY" : !cc.ptr> + quake.exp_pauli (%f2) %46, %48 : (f64, !quake.veq<3>, !cc.ptr>) -> () return } @@ -256,8 +256,8 @@ func.func @quantum_ops() { // CHECK: quake.u2 (%[[VAL_20]], %[[VAL_21]]) {{\[}}%[[VAL_54]]] %[[VAL_10]] : (f32, f32, !quake.veq<3>, !quake.ref) -> () // CHECK: quake.u3 (%[[VAL_20]], %[[VAL_21]], %[[VAL_53]]) {{\[}}%[[VAL_54]]] %[[VAL_10]] : (f32, f32, f64, !quake.veq<3>, !quake.ref) -> () // CHECK: %[[VAL_59:.*]] = arith.constant 1.200000e+01 : f64 -// CHECK: %[[VAL_60:.*]] = cc.string_literal{"XXY"} : () -> !cc.string -// CHECK: quake.exp_pauli(%[[VAL_59]]) %[[VAL_54]], %[[VAL_60]] : (f64, !quake.veq<3>, !cc.string) -> () +// CHECK: %[[VAL_60:.*]] = cc.string_literal "XXY" : !cc.ptr> +// CHECK: quake.exp_pauli(%[[VAL_59]]) %[[VAL_54]], %[[VAL_60]] : (f64, !quake.veq<3>, !cc.ptr>) -> () // CHECK: return // CHECK: } From 6f97216f70497b50e0d0764259901ffc7bcdedc0 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Wed, 27 Sep 2023 22:52:08 +0000 Subject: [PATCH 3/8] build fixes, still have to fix the wheel validation Signed-off-by: Alex McCaskey --- runtime/cudaq/builder/kernel_builder.h | 9 ++- runtime/nvqir/CircuitSimulator.h | 81 +++++++++++++------------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/runtime/cudaq/builder/kernel_builder.h b/runtime/cudaq/builder/kernel_builder.h index 8200d61ea9..8f0814dfdd 100644 --- a/runtime/cudaq/builder/kernel_builder.h +++ b/runtime/cudaq/builder/kernel_builder.h @@ -547,9 +547,9 @@ class kernel_builder : public details::kernel_builder_base { /// @brief Apply a general pauli rotation, exp(-i theta P), /// takes a QuakeValue representing a register of qubits. - template + template void exp_pauli(const ParamT &theta, const QuakeValue &qubits, - const char (&pauliWord)[Size]) { + const std::string &pauliWord) { std::vector qubitValues{qubits}; if constexpr (std::is_floating_point_v) details::exp_pauli(*opBuilder, QuakeValue(*opBuilder, theta), qubitValues, @@ -560,9 +560,8 @@ class kernel_builder : public details::kernel_builder_base { /// @brief Apply a general pauli rotation, exp(-i theta P), /// takes a variadic list of QuakeValues representing a individual qubits. - template - void exp_pauli(const ParamT &theta, const char (&pauliWord)[Size], + template + void exp_pauli(const ParamT &theta, const std::string &pauliWord, QubitArgs &&...qubits) { std::vector qubitValues{qubits...}; if constexpr (std::is_floating_point_v) diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index df818b7c23..739237f6f8 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -66,7 +66,45 @@ class CircuitSimulator { /// This must be provided by subclasses. virtual void applyExpPauli(double theta, const std::vector &qubitIds, - const cudaq::spin_op &op) = 0; + const cudaq::spin_op &op) { + cudaq::info(" [decomposing] exp_pauli({}, {})", theta, op.to_string(false)); + std::vector qubitSupport; + std::vector> basisChange; + op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { + if (type != cudaq::pauli::I) + qubitSupport.push_back(qubitIds[qubitIdx]); + + if (type == cudaq::pauli::Y) + basisChange.emplace_back([&, qubitIdx](bool reverse) { + rx(!reverse ? M_PI_2 : -M_PI_2, qubitIds[qubitIdx]); + }); + else if (type == cudaq::pauli::X) + basisChange.emplace_back( + [&, qubitIdx](bool) { h(qubitIds[qubitIdx]); }); + }); + + if (!basisChange.empty()) + for (auto &basis : basisChange) + basis(false); + + std::vector> toReverse; + for (std::size_t i = 0; i < qubitSupport.size() - 1; i++) { + x({qubitSupport[i]}, qubitSupport[i + 1]); + toReverse.emplace_back(qubitSupport[i], qubitSupport[i + 1]); + } + + rz(theta, qubitSupport.back()); + + std::reverse(toReverse.begin(), toReverse.end()); + for (auto &[i, j] : toReverse) + x({i}, j); + + if (!basisChange.empty()) { + std::reverse(basisChange.begin(), basisChange.end()); + for (auto &basis : basisChange) + basis(true); + } + } /// @brief Compute the expected value of the given spin op /// with respect to the current state, . @@ -627,47 +665,6 @@ class CircuitSimulatorBase : public CircuitSimulator { /// @brief The destructor virtual ~CircuitSimulatorBase() = default; - void applyExpPauli(double theta, const std::vector &qubitIds, - const cudaq::spin_op &op) override { - cudaq::info(" [decomposing] exp_pauli({}, {})", theta, op.to_string(false)); - std::vector qubitSupport; - std::vector> basisChange; - op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { - if (type != cudaq::pauli::I) - qubitSupport.push_back(qubitIds[qubitIdx]); - - if (type == cudaq::pauli::Y) - basisChange.emplace_back([&, qubitIdx](bool reverse) { - rx(!reverse ? M_PI_2 : -M_PI_2, qubitIds[qubitIdx]); - }); - else if (type == cudaq::pauli::X) - basisChange.emplace_back( - [&, qubitIdx](bool) { h(qubitIds[qubitIdx]); }); - }); - - if (!basisChange.empty()) - for (auto &basis : basisChange) - basis(false); - - std::vector> toReverse; - for (std::size_t i = 0; i < qubitSupport.size() - 1; i++) { - x({qubitSupport[i]}, qubitSupport[i + 1]); - toReverse.emplace_back(qubitSupport[i], qubitSupport[i + 1]); - } - - rz(theta, qubitSupport.back()); - - std::reverse(toReverse.begin(), toReverse.end()); - for (auto &[i, j] : toReverse) - x({i}, j); - - if (!basisChange.empty()) { - std::reverse(basisChange.begin(), basisChange.end()); - for (auto &basis : basisChange) - basis(true); - } - } - /// @brief Set the current noise model to consider when /// simulating the state. This should be overridden by /// simulation strategies that support noise modeling. From 5622a0de17908a8d382f9ee3e0e40b50690ef47a Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Wed, 27 Sep 2023 23:05:18 +0000 Subject: [PATCH 4/8] cleanup, add qpp applyExpPauli impl Signed-off-by: Alex McCaskey --- .github/workflows/python_wheels.yml | 2 +- include/cudaq/Optimizer/Dialect/CC/CCTypes.td | 1 - .../default/DefaultExecutionManager.cpp | 7 +-- runtime/nvqir/CircuitSimulator.h | 39 +---------------- .../custatevec/CuStateVecCircuitSimulator.cu | 6 ++- runtime/nvqir/qpp/QppCircuitSimulator.cpp | 43 +++++++++++++++++++ 6 files changed, 52 insertions(+), 46 deletions(-) diff --git a/.github/workflows/python_wheels.yml b/.github/workflows/python_wheels.yml index c420861303..eafb567d83 100644 --- a/.github/workflows/python_wheels.yml +++ b/.github/workflows/python_wheels.yml @@ -197,7 +197,7 @@ jobs: image: wheel_validation:local shell: bash run: | - python${{ inputs.python_version }} -m pytest -v -s /tmp/tests/ \ + python${{ inputs.python_version }} -m pytest -v /tmp/tests/ \ --ignore python/tests/backends pytest_status=$? if [ ! $pytest_status -eq 0 ]; then diff --git a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td index 16b9bbb13a..5c2ef5af78 100644 --- a/include/cudaq/Optimizer/Dialect/CC/CCTypes.td +++ b/include/cudaq/Optimizer/Dialect/CC/CCTypes.td @@ -23,7 +23,6 @@ class CCType traits = [], let mnemonic = typeMnemonic; } - //===----------------------------------------------------------------------===// // PointerType //===----------------------------------------------------------------------===// diff --git a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp index af57513b7c..c27f6f09d2 100644 --- a/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp +++ b/runtime/cudaq/qis/managers/default/DefaultExecutionManager.cpp @@ -112,11 +112,6 @@ class DefaultExecutionManager : public cudaq::BasicExecutionManager { std::transform(controls.begin(), controls.end(), std::back_inserter(localC), [](auto &&el) { return el.id; }); - if (gateName == "exp_pauli") { - simulator()->applyExpPauli(parameters[0], localT, op); - return; - } - // Apply the gate llvm::StringSwitch>(gateName) .Case("h", [&]() { simulator()->h(localC, localT[0]); }) @@ -144,6 +139,8 @@ class DefaultExecutionManager : public cudaq::BasicExecutionManager { }) .Case("swap", [&]() { simulator()->swap(localC, localT[0], localT[1]); }) + .Case("exp_pauli", + [&]() { simulator()->applyExpPauli(parameters[0], localT, op); }) .Default([&]() { throw std::runtime_error("[DefaultExecutionManager] invalid gate " "application requested " + diff --git a/runtime/nvqir/CircuitSimulator.h b/runtime/nvqir/CircuitSimulator.h index 739237f6f8..9c952e69e2 100644 --- a/runtime/nvqir/CircuitSimulator.h +++ b/runtime/nvqir/CircuitSimulator.h @@ -67,43 +67,8 @@ class CircuitSimulator { virtual void applyExpPauli(double theta, const std::vector &qubitIds, const cudaq::spin_op &op) { - cudaq::info(" [decomposing] exp_pauli({}, {})", theta, op.to_string(false)); - std::vector qubitSupport; - std::vector> basisChange; - op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { - if (type != cudaq::pauli::I) - qubitSupport.push_back(qubitIds[qubitIdx]); - - if (type == cudaq::pauli::Y) - basisChange.emplace_back([&, qubitIdx](bool reverse) { - rx(!reverse ? M_PI_2 : -M_PI_2, qubitIds[qubitIdx]); - }); - else if (type == cudaq::pauli::X) - basisChange.emplace_back( - [&, qubitIdx](bool) { h(qubitIds[qubitIdx]); }); - }); - - if (!basisChange.empty()) - for (auto &basis : basisChange) - basis(false); - - std::vector> toReverse; - for (std::size_t i = 0; i < qubitSupport.size() - 1; i++) { - x({qubitSupport[i]}, qubitSupport[i + 1]); - toReverse.emplace_back(qubitSupport[i], qubitSupport[i + 1]); - } - - rz(theta, qubitSupport.back()); - - std::reverse(toReverse.begin(), toReverse.end()); - for (auto &[i, j] : toReverse) - x({i}, j); - - if (!basisChange.empty()) { - std::reverse(basisChange.begin(), basisChange.end()); - for (auto &basis : basisChange) - basis(true); - } + throw std::runtime_error("CircuitSimulator::applyExpPauli not implemented, " + "must be implemented by subclasses."); } /// @brief Compute the expected value of the given spin op diff --git a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu index f502ebd45e..6e28c7b391 100644 --- a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu +++ b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu @@ -368,6 +368,8 @@ public: void applyExpPauli(double theta, const std::vector &qubits, const cudaq::spin_op &op) override { flushGateQueue(); + cudaq::info(" [cusv decomposing] exp_pauli({}, {})", theta, + op.to_string(false)); std::vector controls, targets; std::vector paulis; op.for_each_pauli([&](cudaq::pauli p, std::size_t i) { @@ -385,8 +387,8 @@ public: custatevecApplyPauliRotation( handle, deviceStateVector, cuStateVecCudaDataType, nQubitsAllocated, - theta, paulis.data(), targets.data(), targets.size(), - controls.data(), nullptr, controls.size()); + theta, paulis.data(), targets.data(), targets.size(), controls.data(), + nullptr, controls.size()); } /// @brief Compute the operator expectation value, with respect to diff --git a/runtime/nvqir/qpp/QppCircuitSimulator.cpp b/runtime/nvqir/qpp/QppCircuitSimulator.cpp index bf3b3e225f..1ed0669aad 100644 --- a/runtime/nvqir/qpp/QppCircuitSimulator.cpp +++ b/runtime/nvqir/qpp/QppCircuitSimulator.cpp @@ -181,6 +181,49 @@ class QppCircuitSimulator : public nvqir::CircuitSimulatorBase { qpp::RandomDevices::get_instance().get_prng().seed(seed); } + void applyExpPauli(double theta, const std::vector &qubitIds, + const cudaq::spin_op &op) override { + flushGateQueue(); + cudaq::info(" [qpp decomposing] exp_pauli({}, {})", theta, + op.to_string(false)); + std::vector qubitSupport; + std::vector> basisChange; + op.for_each_pauli([&](cudaq::pauli type, std::size_t qubitIdx) { + if (type != cudaq::pauli::I) + qubitSupport.push_back(qubitIds[qubitIdx]); + + if (type == cudaq::pauli::Y) + basisChange.emplace_back([&, qubitIdx](bool reverse) { + rx(!reverse ? M_PI_2 : -M_PI_2, qubitIds[qubitIdx]); + }); + else if (type == cudaq::pauli::X) + basisChange.emplace_back( + [&, qubitIdx](bool) { h(qubitIds[qubitIdx]); }); + }); + + if (!basisChange.empty()) + for (auto &basis : basisChange) + basis(false); + + std::vector> toReverse; + for (std::size_t i = 0; i < qubitSupport.size() - 1; i++) { + x({qubitSupport[i]}, qubitSupport[i + 1]); + toReverse.emplace_back(qubitSupport[i], qubitSupport[i + 1]); + } + + rz(theta, qubitSupport.back()); + + std::reverse(toReverse.begin(), toReverse.end()); + for (auto &[i, j] : toReverse) + x({i}, j); + + if (!basisChange.empty()) { + std::reverse(basisChange.begin(), basisChange.end()); + for (auto &basis : basisChange) + basis(true); + } + } + bool canHandleObserve() override { // Do not compute from matrix if shots based sampling requested if (executionContext && From 3ec4ce8161eaf82a6f92ea3de212bfc2eeabfe74 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Wed, 27 Sep 2023 23:41:21 +0000 Subject: [PATCH 5/8] fix docs gen Signed-off-by: Alex McCaskey --- docs/sphinx/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index f2298889f0..08d7d83d3e 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -169,6 +169,7 @@ def setup(app): ('cpp:identifier', 'mlir::ImplicitLocOpBuilder'), ('cpp:identifier', 'BinarySymplecticForm'), ('cpp:identifier', 'CountsDictionary'), + ('cpp:identifier', 'QuakeValueOrNumericType'), ('py:class', 'function'), ('py:class', 'type'), ('py:class', 'cudaq::spin_op'), From d3a1f012fb20b2ddda5a4fcfc877bd85c8a05137 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Sep 2023 23:23:40 +0000 Subject: [PATCH 6/8] clean up Signed-off-by: Alex McCaskey --- runtime/cudaq/builder/kernel_builder.cpp | 1 - test/AST-Quake/exp-pauli-1.cpp | 1 - test/AST-Quake/exp-pauli-2.cpp | 2 -- unittests/CMakeLists.txt | 2 +- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/runtime/cudaq/builder/kernel_builder.cpp b/runtime/cudaq/builder/kernel_builder.cpp index a395d6bc97..676d9bc0b0 100644 --- a/runtime/cudaq/builder/kernel_builder.cpp +++ b/runtime/cudaq/builder/kernel_builder.cpp @@ -194,7 +194,6 @@ void exp_pauli(ImplicitLocOpBuilder &builder, const QuakeValue &theta, strLitTy, builder.getStringAttr(pauliWord)); SmallVector args{thetaVal, qubitsVal, stringLiteral}; builder.create(TypeRange{}, args); - return; } /// @brief Search the given `FuncOp` for all `CallOps` recursively. diff --git a/test/AST-Quake/exp-pauli-1.cpp b/test/AST-Quake/exp-pauli-1.cpp index 88b80b06b3..b1e17236b1 100644 --- a/test/AST-Quake/exp-pauli-1.cpp +++ b/test/AST-Quake/exp-pauli-1.cpp @@ -19,7 +19,6 @@ int main() { }; } - // CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( // CHECK-SAME: %[[VAL_0:.*]]: f64) attributes {"cudaq-entrypoint", "cudaq-kernel"} { // CHECK: %[[VAL_1:.*]] = cc.alloca f64 diff --git a/test/AST-Quake/exp-pauli-2.cpp b/test/AST-Quake/exp-pauli-2.cpp index 4404bb32d1..346e8d3664 100644 --- a/test/AST-Quake/exp-pauli-2.cpp +++ b/test/AST-Quake/exp-pauli-2.cpp @@ -19,8 +19,6 @@ int main() { }; } - - // CHECK-LABEL: func.func @__nvqpp__mlirgen__Z4mainE3$_0( // CHECK-SAME: %[[VAL_0:.*]]: f64, // CHECK-SAME: %[[VAL_1:.*]]: !cc.ptr) attributes {"cudaq-entrypoint", "cudaq-kernel"} { diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index ef06ce7e55..6a12a22397 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -64,7 +64,7 @@ macro (create_tests_with_backend NVQIR_BACKEND EXTRA_BACKEND_TESTER) nvqir-${NVQIR_BACKEND} nvqir cudaq fmt::fmt-header-only cudaq-platform-default - cudaq-builder + cudaq-builder gtest_main) if (${NVQIR_BACKEND} STREQUAL "dm") target_compile_definitions(${TEST_EXE_NAME} PRIVATE -DCUDAQ_BACKEND_DM) From 95f2641180392c2ff0cd608647b627d9ef02a614 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Fri, 29 Sep 2023 12:54:31 +0000 Subject: [PATCH 7/8] clean up, provide docs Signed-off-by: Alex McCaskey --- docs/sphinx/api/languages/python_api.rst | 1 + .../cudaq/builder/py_kernel_builder.cpp | 27 ++++++++++++------- python/runtime/cudaq/spin/py_spin_op.cpp | 8 +++++- python/tests/unittests/test_kernel_builder.py | 13 +++++++-- runtime/cudaq/builder/kernel_builder.h | 4 +-- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/docs/sphinx/api/languages/python_api.rst b/docs/sphinx/api/languages/python_api.rst index 160d2f2e9f..fb9b2b715c 100644 --- a/docs/sphinx/api/languages/python_api.rst +++ b/docs/sphinx/api/languages/python_api.rst @@ -33,6 +33,7 @@ Program Construction .. automethod:: rz .. automethod:: r1 .. automethod:: swap + .. automethod:: exp_pauli .. automethod:: mx .. automethod:: my .. automethod:: mz diff --git a/python/runtime/cudaq/builder/py_kernel_builder.cpp b/python/runtime/cudaq/builder/py_kernel_builder.cpp index c89120eace..8884f47e39 100644 --- a/python/runtime/cudaq/builder/py_kernel_builder.cpp +++ b/python/runtime/cudaq/builder/py_kernel_builder.cpp @@ -894,16 +894,23 @@ provided `function` will be applied within `self` at each iteration. .def("__str__", &kernel_builder<>::to_quake, "Return the :class:`Kernel` as a string in its MLIR representation " "using the Quake dialect.\n") - .def("exp_pauli", - [](kernel_builder<> &self, double theta, const QuakeValue &qubits, - const std::string &pauliWord) { - self.exp_pauli(theta, qubits, pauliWord); - }) - .def("exp_pauli", - [](kernel_builder<> &self, const QuakeValue &theta, - const QuakeValue &qubits, const std::string &pauliWord) { - self.exp_pauli(theta, qubits, pauliWord); - }); + .def( + "exp_pauli", + [](kernel_builder<> &self, py::object theta, const QuakeValue &qubits, + const std::string &pauliWord) { + if (py::isinstance(theta)) + self.exp_pauli(theta.cast(), qubits, pauliWord); + else if (py::isinstance(theta)) + self.exp_pauli(theta.cast(), qubits, pauliWord); + else + throw std::runtime_error( + "Invalid `theta` argument type. Must be a " + "`float` or a `QuakeValue`."); + }, + "Apply a general Pauli tensor product rotation, `exp(i theta P)`, on " + "the specified qubit register. The Pauli tensor product is provided " + "as a string, e.g. `XXYX` for a 4-qubit term. The angle parameter " + "can be provided as a concrete float or a `QuakeValue`."); } void bindBuilder(py::module &mod) { diff --git a/python/runtime/cudaq/spin/py_spin_op.cpp b/python/runtime/cudaq/spin/py_spin_op.cpp index 64a3905bef..d85f58dcff 100644 --- a/python/runtime/cudaq/spin/py_spin_op.cpp +++ b/python/runtime/cudaq/spin/py_spin_op.cpp @@ -80,7 +80,13 @@ void bindSpinOperator(py::module &mod) { [](py::object o) { return fromOpenFermionQubitOperator(o); }), "Create from OpenFermion QubitOperator.") .def(py::init &, std::size_t>(), py::arg("data"), - py::arg("num_qubits"), "") + py::arg("num_qubits"), + "Construct a :class:`SpinOperator` from a list of numeric values. " + "The encoding is as follows: for each term, a list of doubles where " + "element `i` is a 3.0 for a `Y`, a 1.0 for a `X`, and a 2.0 for a " + "`Z` on qubit `i`, followed by the real and imaginary part of the " + "coefficient. Each set of term elements is appended to the one " + "list. The list is ended with the total number of terms.") /// @brief Bind the member functions. .def("get_raw_data", &cudaq::spin_op::get_raw_data, diff --git a/python/tests/unittests/test_kernel_builder.py b/python/tests/unittests/test_kernel_builder.py index 1ba6e904f9..d36d16a156 100644 --- a/python/tests/unittests/test_kernel_builder.py +++ b/python/tests/unittests/test_kernel_builder.py @@ -284,11 +284,12 @@ def test_from_state(): def test_exp_pauli(): cudaq.reset_target() - kernel, theta = cudaq.make_kernel(float) + kernel = cudaq.make_kernel() qubits = kernel.qalloc(4) kernel.x(qubits[0]) kernel.x(qubits[1]) - kernel.exp_pauli(theta, qubits, "XXXY") + print(type(-.22)) + kernel.exp_pauli(-.22, qubits, "XXXY") print(kernel) h2_data = [ 3, 1, 1, 3, 0.0454063, 0, 2, 0, 0, 0, 0.17028, 0, @@ -301,5 +302,13 @@ def test_exp_pauli(): 1, 1, 3, 3, -0.0454063, -0, 15 ] h = cudaq.SpinOperator(h2_data, 4) + want_exp = cudaq.observe(kernel, h).expectation_z() + assert np.isclose(want_exp, -1.13, atol=1e-2) + + kernel, theta = cudaq.make_kernel(float) + qubits = kernel.qalloc(4) + kernel.x(qubits[0]) + kernel.x(qubits[1]) + kernel.exp_pauli(theta, qubits, "XXXY") want_exp = cudaq.observe(kernel, h, -.22).expectation_z() assert np.isclose(want_exp, -1.13, atol=1e-2) \ No newline at end of file diff --git a/runtime/cudaq/builder/kernel_builder.h b/runtime/cudaq/builder/kernel_builder.h index 8f0814dfdd..4e181a8ac1 100644 --- a/runtime/cudaq/builder/kernel_builder.h +++ b/runtime/cudaq/builder/kernel_builder.h @@ -545,7 +545,7 @@ class kernel_builder : public details::kernel_builder_base { details::c_if(*opBuilder, result, thenFunctor); } - /// @brief Apply a general pauli rotation, exp(-i theta P), + /// @brief Apply a general pauli rotation, exp(i theta P), /// takes a QuakeValue representing a register of qubits. template void exp_pauli(const ParamT &theta, const QuakeValue &qubits, @@ -558,7 +558,7 @@ class kernel_builder : public details::kernel_builder_base { details::exp_pauli(*opBuilder, theta, qubitValues, pauliWord); } - /// @brief Apply a general pauli rotation, exp(-i theta P), + /// @brief Apply a general pauli rotation, exp(i theta P), /// takes a variadic list of QuakeValues representing a individual qubits. template void exp_pauli(const ParamT &theta, const std::string &pauliWord, From 761ff40488039a396e569ce24613db4f9d88c1f6 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Fri, 29 Sep 2023 17:29:20 -0400 Subject: [PATCH 8/8] Update runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu Co-authored-by: Thien Nguyen <58006629+1tnguyen@users.noreply.github.com> --- runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu index 6e28c7b391..d18f690a05 100644 --- a/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu +++ b/runtime/nvqir/custatevec/CuStateVecCircuitSimulator.cu @@ -385,10 +385,10 @@ public: targets.push_back(qubits[i]); }); - custatevecApplyPauliRotation( + HANDLE_ERROR(custatevecApplyPauliRotation( handle, deviceStateVector, cuStateVecCudaDataType, nQubitsAllocated, theta, paulis.data(), targets.data(), targets.size(), controls.data(), - nullptr, controls.size()); + nullptr, controls.size())); } /// @brief Compute the operator expectation value, with respect to