diff --git a/lib/Optimizer/Transforms/UnitarySynthesis.cpp b/lib/Optimizer/Transforms/UnitarySynthesis.cpp index cfa64995b5..8e57af4e90 100644 --- a/lib/Optimizer/Transforms/UnitarySynthesis.cpp +++ b/lib/Optimizer/Transforms/UnitarySynthesis.cpp @@ -67,14 +67,16 @@ struct BasisZYZ { std::array, 4> matrix; EulerAngles angles; - double phase; + /// Global phase is ignored + // double phase; /// This logic is based on https://arxiv.org/pdf/quant-ph/9503016 and its /// corresponding explanation in https://threeplusone.com/pubs/on_gates.pdf, /// Section 4. void decompose() { - auto det = (matrix[0] * matrix[3]) - (matrix[1] * matrix[2]); - phase = 0.5 * std::atan2(det.imag(), det.real()); + + // auto det = (matrix[0] * matrix[3]) - (matrix[1] * matrix[2]); + // phase = 0.5 * std::atan2(det.imag(), det.real()); auto abs_00 = std::abs(matrix[0]); auto abs_01 = std::abs(matrix[1]); @@ -118,60 +120,78 @@ class CustomUnitaryPattern LogicalResult matchAndRewrite(quake::CustomUnitarySymbolOp customOp, PatternRewriter &rewriter) const override { - // Fetch the unitary matrix generator for this custom operation auto parentModule = customOp->getParentOfType(); - auto sref = customOp.getGenerator(); - StringRef generatorName = sref.getRootReference(); - auto globalOp = - parentModule.lookupSymbol(generatorName); - auto unitary = readGlobalConstantArray(globalOp); - - if (unitary.size() != 4) - return customOp.emitError( - "Decomposition of only single qubit custom operations supported."); - /// TODO: Maintain a cache of decomposed custom operations - - // Use Euler angle decomposition for single qubit operation - auto zyz = BasisZYZ(unitary); - zyz.decompose(); - - // op info Location loc = customOp->getLoc(); auto targets = customOp.getTargets(); auto controls = customOp.getControls(); - /// TODO: Handle adjoint case - - auto floatTy = cast(rewriter.getF64Type()); - - if (0. != zyz.angles.alpha) { - auto alpha = cudaq::opt::factory::createFloatConstant( - loc, rewriter, zyz.angles.alpha, floatTy); - rewriter.create(loc, alpha, controls, targets); - } - - if (0. != zyz.angles.beta) { - auto beta = cudaq::opt::factory::createFloatConstant( - loc, rewriter, zyz.angles.beta, floatTy); - rewriter.create(loc, beta, controls, targets); - } + /// Get the global constant holding the concrete matrix corresponding to + /// this custom operation invocation + StringRef generatorName = customOp.getGenerator().getRootReference(); + auto globalOp = + parentModule.lookupSymbol(generatorName); - if (0. != zyz.angles.gamma) { - auto gamma = cudaq::opt::factory::createFloatConstant( - loc, rewriter, zyz.angles.gamma, floatTy); - rewriter.create(loc, gamma, controls, targets); + /// The decomposed sequence of quantum operations are in a function + auto pair = generatorName.split(".rodata"); + std::string funcName = pair.first.str() + ".kernel" + pair.second.str(); + + /// If the replacement function doesn't exist, create it here + if (!parentModule.lookupSymbol(funcName)) { + + auto unitary = readGlobalConstantArray(globalOp); + + /// TODO: Expand the logic to decompose upto 4-qubit operations + if (unitary.size() != 4) + return customOp.emitError( + "Decomposition of only single qubit custom operations supported."); + + /// Controls are handled via apply specialization + auto funcTy = + FunctionType::get(parentModule.getContext(), targets.getTypes(), {}); + auto insPt = rewriter.saveInsertionPoint(); + rewriter.setInsertionPointToStart(parentModule.getBody()); + auto func = rewriter.create(parentModule->getLoc(), + funcName, funcTy); + rewriter.restoreInsertionPoint(insPt); + + insPt = rewriter.saveInsertionPoint(); + auto *block = func.addEntryBlock(); + rewriter.setInsertionPointToStart(block); + + /// Use Euler angle decomposition for single qubit operation + auto zyz = BasisZYZ(unitary); + zyz.decompose(); + + /// For 1-qubit operation, apply on 'all' the targets + auto arguments = func.getArguments(); + auto floatTy = cast(rewriter.getF64Type()); + + if (0. != zyz.angles.alpha) { + auto alpha = cudaq::opt::factory::createFloatConstant( + loc, rewriter, zyz.angles.alpha, floatTy); + rewriter.create(loc, alpha, ValueRange{}, arguments); + } + + if (0. != zyz.angles.beta) { + auto beta = cudaq::opt::factory::createFloatConstant( + loc, rewriter, zyz.angles.beta, floatTy); + rewriter.create(loc, beta, ValueRange{}, arguments); + } + + if (0. != zyz.angles.gamma) { + auto gamma = cudaq::opt::factory::createFloatConstant( + loc, rewriter, zyz.angles.gamma, floatTy); + rewriter.create(loc, gamma, ValueRange{}, arguments); + } + + rewriter.create(loc); + rewriter.restoreInsertionPoint(insPt); } - /// ASKME: Apply global phase? - // if (0. != zyz.phase) { - // auto phase = cudaq::opt::factory::createFloatConstant(loc, rewriter, - // zyz.phase, - // floatTy); - // Value negPhase = rewriter.create(loc, phase); - // rewriter.create(loc, phase, controls, targets); - // rewriter.create(loc, negPhase, controls, targets); - // } + rewriter.create( + loc, TypeRange{}, SymbolRefAttr::get(rewriter.getContext(), funcName), + customOp.isAdj(), controls, targets); rewriter.eraseOp(customOp); return success(); diff --git a/runtime/cudaq/platform/default/rest/helpers/quantinuum/quantinuum.yml b/runtime/cudaq/platform/default/rest/helpers/quantinuum/quantinuum.yml index b3e2b28fc5..58a4b213c0 100644 --- a/runtime/cudaq/platform/default/rest/helpers/quantinuum/quantinuum.yml +++ b/runtime/cudaq/platform/default/rest/helpers/quantinuum/quantinuum.yml @@ -16,7 +16,7 @@ config: # Add the rest-qpu library to the link list link-libs: ["-lcudaq-rest-qpu"] # Define the lowering pipeline - platform-lowering-config: "const-prop-complex,canonicalize,cse,lift-array-value,state-prep,expand-measurements,unrolling-pipeline,func.func(unitary-synthesis),decomposition{enable-patterns=U3ToRotations},func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),quantinuum-gate-set-mapping" + platform-lowering-config: "const-prop-complex,canonicalize,cse,lift-array-value,state-prep,expand-measurements,unrolling-pipeline,func.func(unitary-synthesis),canonicalize,apply-op-specialization,decomposition{enable-patterns=U3ToRotations},func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),quantinuum-gate-set-mapping" # Tell the rest-qpu that we are generating Adaptive QIR. codegen-emission: qir-adaptive # Library mode is only for simulators, physical backends must turn this off diff --git a/targettests/TargetConfig/RegressionValidation/quantinuum.config b/targettests/TargetConfig/RegressionValidation/quantinuum.config index b5f0d36998..1df9a449bf 100644 --- a/targettests/TargetConfig/RegressionValidation/quantinuum.config +++ b/targettests/TargetConfig/RegressionValidation/quantinuum.config @@ -18,7 +18,7 @@ # CHECK-DAG: LINKLIBS="${LINKLIBS} -lcudaq-rest-qpu" # Define the lowering pipeline, here we lower to Adaptive QIR -# CHECK-DAG: PLATFORM_LOWERING_CONFIG="const-prop-complex,canonicalize,cse,lift-array-value,state-prep,expand-measurements,unrolling-pipeline,func.func(unitary-synthesis),decomposition{enable-patterns=U3ToRotations},func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),quantinuum-gate-set-mapping" +# CHECK-DAG: PLATFORM_LOWERING_CONFIG="const-prop-complex,canonicalize,cse,lift-array-value,state-prep,expand-measurements,unrolling-pipeline,func.func(unitary-synthesis),canonicalize,apply-op-specialization,decomposition{enable-patterns=U3ToRotations},func.func(lower-to-cfg),canonicalize,func.func(multicontrol-decomposition),quantinuum-gate-set-mapping" # Tell the rest-qpu that we are generating QIR. # CHECK-DAG: CODEGEN_EMISSION=qir-adaptive diff --git a/test/Transforms/UnitarySynthesis/basic_test.qke b/test/Transforms/UnitarySynthesis/basic_test.qke index 72b44e27f4..b677319ddf 100644 --- a/test/Transforms/UnitarySynthesis/basic_test.qke +++ b/test/Transforms/UnitarySynthesis/basic_test.qke @@ -17,9 +17,15 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__k1 = "__nvqpp__ml cc.global constant @__nvqpp__mlirgen__custom_x_generator_1.rodata (dense<[(0.000000e+00,0.000000e+00), (1.000000e+00,0.000000e+00), (1.000000e+00,0.000000e+00), (0.000000e+00,0.000000e+00)]> : tensor<4xcomplex>) : !cc.array x 4> } +// CHECK-LABEL: func.func @__nvqpp__mlirgen__custom_x_generator_1.kernel( +// CHECK-SAME: %[[VAL_0:.*]]: !quake.ref) { +// CHECK: %[[VAL_1:.*]] = arith.constant 3.1415926535897931 : f64 +// CHECK: quake.ry (%[[VAL_1]]) %[[VAL_0]] : (f64, !quake.ref) -> () +// CHECK: return +// CHECK: } + // CHECK-LABEL: func.func @__nvqpp__mlirgen__k1() attributes {"cudaq-entrypoint"} { -// CHECK: %[[VAL_0:.*]] = arith.constant 3.1415926535897931 : f64 -// CHECK: %[[VAL_1:.*]] = quake.alloca !quake.ref -// CHECK: quake.ry (%[[VAL_0]]) %[[VAL_1]] : (f64, !quake.ref) -> () +// CHECK: %[[VAL_0:.*]] = quake.alloca !quake.ref +// CHECK: quake.apply @__nvqpp__mlirgen__custom_x_generator_1.kernel %[[VAL_0]] : (!quake.ref) -> () // CHECK: return // CHECK: } diff --git a/test/Transforms/UnitarySynthesis/bell_pair.qke b/test/Transforms/UnitarySynthesis/bell_pair.qke index f15e9054a1..d40f05d5c8 100644 --- a/test/Transforms/UnitarySynthesis/bell_pair.qke +++ b/test/Transforms/UnitarySynthesis/bell_pair.qke @@ -6,7 +6,7 @@ // the terms of the Apache License 2.0 which accompanies this distribution. // // ========================================================================== // -// RUN: cudaq-opt --unitary-synthesis %s | FileCheck %s +// RUN: cudaq-opt --unitary-synthesis --canonicalize %s | FileCheck %s module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__bell = "__nvqpp__mlirgen__bell_PyKernelEntryPointRewrite"}} { func.func @__nvqpp__mlirgen__bell() attributes {"cudaq-entrypoint"} { @@ -21,15 +21,28 @@ module attributes {quake.mangled_name_map = {__nvqpp__mlirgen__bell = "__nvqpp__ cc.global constant @__nvqpp__mlirgen__custom_x_generator_1.rodata (dense<[(0.000000e+00,0.000000e+00), (1.000000e+00,0.000000e+00), (1.000000e+00,0.000000e+00), (0.000000e+00,0.000000e+00)]> : tensor<4xcomplex>) : !cc.array x 4> } -// CHECK-LABEL: func.func @__nvqpp__mlirgen__bell() attributes {"cudaq-entrypoint"} { -// CHECK: %[[VAL_0:.*]] = arith.constant 3.1415926535897931 : f64 +// CHECK-LABEL: func.func @__nvqpp__mlirgen__custom_h_generator_1.kernel( +// CHECK-SAME: %[[VAL_0:.*]]: !quake.ref) { // CHECK: %[[VAL_1:.*]] = arith.constant 1.5707963267948968 : f64 -// CHECK: %[[VAL_2:.*]] = quake.alloca !quake.veq<2> -// CHECK: %[[VAL_3:.*]] = quake.extract_ref %[[VAL_2]][0] : (!quake.veq<2>) -> !quake.ref -// CHECK: quake.rz (%[[VAL_0]]) %[[VAL_3]] : (f64, !quake.ref) -> () -// CHECK: quake.ry (%[[VAL_1]]) %[[VAL_3]] : (f64, !quake.ref) -> () -// CHECK: quake.rz (%[[VAL_0]]) %[[VAL_3]] : (f64, !quake.ref) -> () -// CHECK: %[[VAL_4:.*]] = quake.extract_ref %[[VAL_2]][1] : (!quake.veq<2>) -> !quake.ref -// CHECK: quake.ry (%[[VAL_0]]) {{\[}}%[[VAL_3]]] %[[VAL_4]] : (f64, !quake.ref, !quake.ref) -> () +// CHECK: %[[VAL_2:.*]] = arith.constant 3.1415926535897931 : f64 +// CHECK: quake.rz (%[[VAL_2]]) %[[VAL_0]] : (f64, !quake.ref) -> () +// CHECK: quake.ry (%[[VAL_1]]) %[[VAL_0]] : (f64, !quake.ref) -> () +// CHECK: quake.rz (%[[VAL_2]]) %[[VAL_0]] : (f64, !quake.ref) -> () +// CHECK: return +// CHECK: } + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__custom_x_generator_1.kernel( +// CHECK-SAME: %[[VAL_0:.*]]: !quake.ref) { +// CHECK: %[[VAL_1:.*]] = arith.constant 3.1415926535897931 : f64 +// CHECK: quake.ry (%[[VAL_1]]) %[[VAL_0]] : (f64, !quake.ref) -> () +// CHECK: return +// CHECK: } + +// CHECK-LABEL: func.func @__nvqpp__mlirgen__bell() attributes {"cudaq-entrypoint"} { +// CHECK: %[[VAL_0:.*]] = quake.alloca !quake.veq<2> +// CHECK: %[[VAL_1:.*]] = quake.extract_ref %[[VAL_0]][0] : (!quake.veq<2>) -> !quake.ref +// CHECK: quake.apply @__nvqpp__mlirgen__custom_h_generator_1.kernel %[[VAL_1]] : (!quake.ref) -> () +// CHECK: %[[VAL_2:.*]] = quake.extract_ref %[[VAL_0]][1] : (!quake.veq<2>) -> !quake.ref +// CHECK: quake.apply @__nvqpp__mlirgen__custom_x_generator_1.kernel {{\[}}%[[VAL_1]]] %[[VAL_2]] : (!quake.ref, !quake.ref) -> () // CHECK: return // CHECK: }