Skip to content

Commit dbb5c2c

Browse files
authored
Add semantic conversion support to quantum.operator (#2951)
Follow up on #2943. [sc-120867]
1 parent ccf1612 commit dbb5c2c

5 files changed

Lines changed: 164 additions & 0 deletions

File tree

doc/releases/changelog-dev.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@
277277
quantum operators, including operators with frontend-end specific data.
278278
[(#2883)](https://github.com/PennyLaneAI/catalyst/pull/2883)
279279
[(#2943)](https://github.com/PennyLaneAI/catalyst/pull/2943)
280+
[(#2951)](https://github.com/PennyLaneAI/catalyst/pull/2951)
280281

281282
* In order to support T gates and π/8 PPRs in the experimental QEC pipeline, the following new
282283
operations have been added:

mlir/lib/QRef/Transforms/value_semantics_conversion.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,26 @@ void handleGate(IRRewriter &builder, qref::QuantumOperation rGateOp, QubitValueT
11221122
vGateOp = migrateOpToValueSemantics<quantum::SetBasisStateOp>(builder, rSetBasisStateOp,
11231123
tracker, qubitResultsType);
11241124
}
1125+
else if (auto rOperatorOp = dyn_cast<qref::OperatorOp>(_rGateOp)) {
1126+
// Special case for the register mode of this op (existing vector only gathers qubits).
1127+
if (rOperatorOp.getQreg()) {
1128+
qubitResultsType.push_back(quantum::QuregType::get(ctx));
1129+
}
1130+
1131+
auto vGateOp = migrateOpToValueSemantics<quantum::OperatorOp>(builder, rOperatorOp, tracker,
1132+
qubitResultsType);
1133+
// quantum.operator has three result segments: out_qubits, out_ctrl_qubits, out_qreg.
1134+
int32_t nTargets = rOperatorOp.getNonCtrlQubitOperands().size();
1135+
int32_t nCtrls = rOperatorOp.getCtrlQubitOperands().size();
1136+
int32_t nQreg = rOperatorOp.getQreg() ? 1 : 0;
1137+
vGateOp->setAttr("resultSegmentSizes",
1138+
builder.getDenseI32ArrayAttr({nTargets, nCtrls, nQreg}));
1139+
1140+
// Properties are not handled via the generic attribute fields, so we set them separately.
1141+
vGateOp.setOpName(rOperatorOp.getOpName());
1142+
vGateOp.setAdjoint(rOperatorOp.getAdjoint());
1143+
vGateOp.setUID(rOperatorOp.getUID());
1144+
}
11251145
else {
11261146
rGateOp->emitOpError("unknown gate op in qref dialect");
11271147
}

mlir/lib/Quantum/Transforms/reference_semantics_conversion.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,16 @@ void handleGate(IRRewriter &builder, quantum::QuantumOperation vGateOp, QubitVal
338338
rGateOp = migrateOpToReferenceSemantics<qref::SetBasisStateOp>(builder, vSetBasisStateOp,
339339
tracker);
340340
}
341+
else if (auto vOperatorOp = dyn_cast<quantum::OperatorOp>(_vGateOp)) {
342+
auto rGateOp =
343+
migrateOpToReferenceSemantics<qref::OperatorOp>(builder, vOperatorOp, tracker);
344+
rGateOp->removeAttr("resultSegmentSizes");
345+
346+
// Properties are not handled via the generic attribute fields, so we set them separately.
347+
rGateOp.setOpName(vOperatorOp.getOpName());
348+
rGateOp.setAdjoint(vOperatorOp.getAdjoint());
349+
rGateOp.setUID(vOperatorOp.getUID());
350+
}
341351
else {
342352
vGateOp->emitOpError("unknown gate op in quantum dialect");
343353
}

mlir/test/QRef/SemanticConversion/TestFlatCircuits.mlir

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,72 @@ func.func @test_adjoint_op_nested() attributes {quantum.node}
551551
// -----
552552

553553

554+
// CHECK-LABEL: test_operator_qubits
555+
func.func @test_operator_qubits(%arg0: f64, %cv: i1, %fwd: i64) attributes {quantum.node} {
556+
// CHECK: [[qreg:%.+]] = quantum.alloc( 2) : !quantum.reg
557+
%a = qref.alloc(2) : !qref.reg<2>
558+
%q0 = qref.get %a[0] : !qref.reg<2> -> !qref.bit
559+
%q1 = qref.get %a[1] : !qref.reg<2> -> !qref.bit
560+
561+
// CHECK: [[bit0:%.+]] = quantum.extract [[qreg]][ 0] : !quantum.reg -> !quantum.bit
562+
// CHECK: [[bit1:%.+]] = quantum.extract [[qreg]][ 1] : !quantum.reg -> !quantum.bit
563+
564+
// CHECK: [[op1:%.+]]:2 = quantum.operator "MyOp"(%arg0: f64) adj qubits([[bit0]], [[bit1]])
565+
// CHECK-NEXT: static_data = {pauli_word = "XY"}
566+
// CHECK-NEXT: param_map = {theta = [0]} qubit_map = {pair = [0, 1]}
567+
qref.operator "MyOp"(%arg0 : f64) adj qubits(%q0, %q1)
568+
static_data = {pauli_word = "XY"}
569+
param_map = {theta = [0]} qubit_map = {pair = [0, 1]}
570+
571+
// CHECK: [[op2:%.+]], [[op2c:%.+]] = quantum.operator "MyOp"() qubits([[op1]]#0)
572+
// CHECK-NEXT: ctrls([[op1]]#1) ctrl_vals(%arg1)
573+
qref.operator "MyOp"() qubits(%q0)
574+
ctrls(%q1) ctrl_vals(%cv)
575+
576+
// CHECK: [[op3:%.+]]:2 = quantum.operator "MyOp"() qubits([[op2]], [[op2c]])
577+
// CHECK-NEXT: UID(42) forward(%arg2: i64)
578+
qref.operator "MyOp"() qubits(%q0, %q1)
579+
UID(42) forward(%fwd : i64)
580+
581+
// CHECK: [[insert0:%.+]] = quantum.insert [[qreg]][ 0], [[op3]]#0 : !quantum.reg, !quantum.bit
582+
// CHECK: [[insert1:%.+]] = quantum.insert [[insert0]][ 1], [[op3]]#1 : !quantum.reg, !quantum.bit
583+
// CHECK: quantum.dealloc [[insert1]] : !quantum.reg
584+
qref.dealloc %a : !qref.reg<2>
585+
return
586+
}
587+
588+
589+
// -----
590+
591+
592+
// CHECK-LABEL: test_operator_register
593+
func.func @test_operator_register(%idx0: tensor<2xi64>, %idx1: tensor<1xi64>, %cidx: tensor<2xi64>, %cval: tensor<2xi1>) attributes {quantum.node} {
594+
// CHECK: [[qreg:%.+]] = quantum.alloc( 4) : !quantum.reg
595+
%a = qref.alloc(4) : !qref.reg<4>
596+
597+
// CHECK: [[op1:%.+]] = quantum.operator "MyOp"()
598+
// CHECK-NEXT: quregs([[qreg]]) indices(%arg0: tensor<2xi64>, %arg1: tensor<1xi64>)
599+
// CHECK-NEXT: qubit_map = {qi0 = [0], qi1 = [1]}
600+
qref.operator "MyOp"()
601+
quregs(%a : !qref.reg<4>) indices(%idx0 : tensor<2xi64>, %idx1 : tensor<1xi64>)
602+
qubit_map = {qi0 = [0], qi1 = [1]}
603+
604+
// CHECK: [[op2:%.+]] = quantum.operator "MyOp"()
605+
// CHECK-NEXT: quregs([[op1]]) indices(%arg0: tensor<2xi64>)
606+
// CHECK-NEXT: ctrls(%arg2: tensor<2xi64>) ctrl_vals(%arg3: tensor<2xi1>)
607+
qref.operator "MyOp"()
608+
quregs(%a : !qref.reg<4>) indices(%idx0 : tensor<2xi64>)
609+
ctrls(%cidx : tensor<2xi64>) ctrl_vals(%cval : tensor<2xi1>)
610+
611+
// CHECK: quantum.dealloc [[op2]] : !quantum.reg
612+
qref.dealloc %a : !qref.reg<4>
613+
return
614+
}
615+
616+
617+
// -----
618+
619+
554620
// CHECK-LABEL: test_aliasing_getops
555621
func.func @test_aliasing_getops() attributes {quantum.node} {
556622

mlir/test/Quantum/SemanticsConversion/TestFlatCircuits.mlir

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,3 +684,70 @@ func.func public @test_adjoint_with_allocation(%arg0: i64) attributes {quantum.n
684684
quantum.dealloc %8 : !quantum.reg
685685
return
686686
}
687+
688+
689+
// -----
690+
691+
// CHECK-LABEL: test_operator_qubits
692+
func.func @test_operator_qubits(%arg0: f64, %cv: i1, %fwd: i64) attributes {quantum.node} {
693+
// CHECK: [[qreg:%.+]] = qref.alloc( 2) : !qref.reg<2>
694+
%0 = quantum.alloc( 2) : !quantum.reg
695+
696+
// CHECK: [[q0:%.+]] = qref.get [[qreg]][ 0] : !qref.reg<2> -> !qref.bit
697+
// CHECK: [[q1:%.+]] = qref.get [[qreg]][ 1] : !qref.reg<2> -> !qref.bit
698+
%1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
699+
%2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit
700+
701+
// CHECK: qref.operator "MyOp"(%arg0: f64) adj qubits([[q0]], [[q1]])
702+
// CHECK-NEXT: static_data = {pauli_word = "XY"}
703+
// CHECK-NEXT: param_map = {theta = [0]} qubit_map = {pair = [0, 1]}
704+
%out1:2 = quantum.operator "MyOp"(%arg0: f64) adj qubits(%1, %2)
705+
static_data = {pauli_word = "XY"}
706+
param_map = {theta = [0]} qubit_map = {pair = [0, 1]}
707+
708+
// CHECK: qref.operator "MyOp"() qubits([[q0]])
709+
// CHECK-NEXT: ctrls([[q1]]) ctrl_vals(%arg1)
710+
%oq, %ocq = quantum.operator "MyOp"() qubits(%out1#0)
711+
ctrls(%out1#1) ctrl_vals(%cv)
712+
713+
// CHECK: qref.operator "MyOp"() qubits([[q0]], [[q1]])
714+
// CHECK-NEXT: UID(42) forward(%arg2: i64)
715+
%out3:2 = quantum.operator "MyOp"() qubits(%oq, %ocq)
716+
UID(42) forward(%fwd : i64)
717+
718+
// CHECK-NOT: quantum.insert
719+
%3 = quantum.insert %0[ 0], %out3#0 : !quantum.reg, !quantum.bit
720+
%4 = quantum.insert %3[ 1], %out3#1 : !quantum.reg, !quantum.bit
721+
722+
// CHECK: qref.dealloc [[qreg]] : !qref.reg<2>
723+
quantum.dealloc %4 : !quantum.reg
724+
return
725+
}
726+
727+
728+
// -----
729+
730+
731+
// CHECK-LABEL: test_operator_register
732+
func.func @test_operator_register(%idx0: tensor<2xi64>, %idx1: tensor<1xi64>, %cidx: tensor<2xi64>, %cval: tensor<2xi1>) attributes {quantum.node} {
733+
// CHECK: [[qreg:%.+]] = qref.alloc( 4) : !qref.reg<4>
734+
%0 = quantum.alloc( 4) : !quantum.reg
735+
736+
// CHECK: qref.operator "MyOp"()
737+
// CHECK-NEXT: quregs([[qreg]] : !qref.reg<4>) indices(%arg0: tensor<2xi64>, %arg1: tensor<1xi64>)
738+
// CHECK-NEXT: qubit_map = {qi0 = [0], qi1 = [1]}
739+
%out1 = quantum.operator "MyOp"()
740+
quregs(%0) indices(%idx0 : tensor<2xi64>, %idx1 : tensor<1xi64>)
741+
qubit_map = {qi0 = [0], qi1 = [1]}
742+
743+
// CHECK: qref.operator "MyOp"()
744+
// CHECK-NEXT: quregs([[qreg]] : !qref.reg<4>) indices(%arg0: tensor<2xi64>)
745+
// CHECK-NEXT: ctrls(%arg2: tensor<2xi64>) ctrl_vals(%arg3: tensor<2xi1>)
746+
%out2 = quantum.operator "MyOp"()
747+
quregs(%out1) indices(%idx0 : tensor<2xi64>)
748+
ctrls(%cidx : tensor<2xi64>) ctrl_vals(%cval : tensor<2xi1>)
749+
750+
// CHECK: qref.dealloc [[qreg]] : !qref.reg<4>
751+
quantum.dealloc %out2 : !quantum.reg
752+
return
753+
}

0 commit comments

Comments
 (0)