From c4ba2937b7563348f8b9dfc9aeb76106e9d79c51 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 13:27:03 +0000 Subject: [PATCH 1/9] Fix issue 1446 where bad attribute name was allowed Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 187 +++++++++++--------- python/tests/kernel/test_kernel_features.py | 59 +++++- 2 files changed, 162 insertions(+), 84 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 8b10c4ac22..64b1b906f5 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -1617,26 +1617,69 @@ def bodyBuilder(iterVal): index=zero).result) return + def maybeProposeOpAttrFix(opName, attrName): + # TODO Add more possibilities in the future... + if attrName in ['control' + ] or 'control' in attrName or 'ctrl' in attrName: + return f'Did you mean {opName}.ctrl(...)?' + + if attrName in ['adjoint' + ] or 'adjoint' in attrName or 'adj' in attrName: + return f'Did you mean {opName}.adj(...)?' + + return '' + # We have a `func_name.ctrl` - if node.func.value.id in ['h', 'x', 'y', 'z', 's', 't' - ] and node.func.attr == 'ctrl': - target = self.popValue() - # Should be number of arguments minus one for the controls - controls = [self.popValue() for i in range(len(node.args) - 1)] - negatedControlQubits = None - if len(self.controlNegations): - negCtrlBools = [None] * len(controls) - for i, c in enumerate(controls): - negCtrlBools[i] = c in self.controlNegations - negatedControlQubits = DenseBoolArrayAttr.get(negCtrlBools) - self.controlNegations.clear() + if node.func.value.id in ['h', 'x', 'y', 'z', 's', 't']: + if node.func.attr == 'ctrl': + target = self.popValue() + # Should be number of arguments minus one for the controls + controls = [ + self.popValue() for i in range(len(node.args) - 1) + ] + negatedControlQubits = None + if len(self.controlNegations): + negCtrlBools = [None] * len(controls) + for i, c in enumerate(controls): + negCtrlBools[i] = c in self.controlNegations + negatedControlQubits = DenseBoolArrayAttr.get( + negCtrlBools) + self.controlNegations.clear() + + opCtor = getattr(quake, + '{}Op'.format(node.func.value.id.title())) + opCtor([], [], + controls, [target], + negated_qubit_controls=negatedControlQubits) + return + if node.func.attr == 'adj': + target = self.popValue() + opCtor = getattr(quake, + '{}Op'.format(node.func.value.id.title())) + if quake.VeqType.isinstance(target.type): + + def bodyBuilder(iterVal): + q = quake.ExtractRefOp(self.getRefType(), + target, + -1, + index=iterVal).result + opCtor([], [], [], [q], is_adj=True) - opCtor = getattr(quake, - '{}Op'.format(node.func.value.id.title())) - opCtor([], [], - controls, [target], - negated_qubit_controls=negatedControlQubits) - return + veqSize = quake.VeqSizeOp(self.getIntegerType(), + target).result + self.createInvariantForLoop(veqSize, bodyBuilder) + return + elif quake.RefType.isinstance(target.type): + opCtor([], [], [], [target], is_adj=True) + return + else: + self.emitFatalError( + 'adj quantum operation on incorrect type {}.'. + format(target.type), node) + + self.emitFatalError( + f'Unknown attribute on quantum operation {node.func.value.id} ({node.func.attr}). {maybeProposeOpAttrFix(node.func.value.id, node.func.attr)}' + ) # We have a `func_name.ctrl` if node.func.value.id == 'swap' and node.func.attr == 'ctrl': @@ -1650,75 +1693,53 @@ def bodyBuilder(iterVal): opCtor([], [], controls, [targetA, targetB]) return - if node.func.value.id in ['rx', 'ry', 'rz', 'r1' - ] and node.func.attr == 'ctrl': - target = self.popValue() - controls = [ - self.popValue() for i in range(len(self.valueStack)) - ] - param = controls[-1] - if IntegerType.isinstance(param.type): - param = arith.SIToFPOp(self.getFloatType(), param).result - opCtor = getattr(quake, - '{}Op'.format(node.func.value.id.title())) - opCtor([], [param], controls[:-1], [target]) - return - - # We have a `func_name.adj` - if node.func.value.id in ['h', 'x', 'y', 'z', 's', 't' - ] and node.func.attr == 'adj': - target = self.popValue() - opCtor = getattr(quake, - '{}Op'.format(node.func.value.id.title())) - if quake.VeqType.isinstance(target.type): - - def bodyBuilder(iterVal): - q = quake.ExtractRefOp(self.getRefType(), - target, - -1, - index=iterVal).result - opCtor([], [], [], [q], is_adj=True) - - veqSize = quake.VeqSizeOp(self.getIntegerType(), - target).result - self.createInvariantForLoop(veqSize, bodyBuilder) - return - elif quake.RefType.isinstance(target.type): - opCtor([], [], [], [target], is_adj=True) + if node.func.value.id in ['rx', 'ry', 'rz', 'r1']: + if node.func.attr == 'ctrl': + target = self.popValue() + controls = [ + self.popValue() for i in range(len(self.valueStack)) + ] + param = controls[-1] + if IntegerType.isinstance(param.type): + param = arith.SIToFPOp(self.getFloatType(), + param).result + opCtor = getattr(quake, + '{}Op'.format(node.func.value.id.title())) + opCtor([], [param], controls[:-1], [target]) return - else: - self.emitFatalError( - 'adj quantum operation on incorrect type {}.'.format( - target.type), node) - if node.func.value.id in ['rx', 'ry', 'rz', 'r1' - ] and node.func.attr == 'adj': - target = self.popValue() - param = self.popValue() - if IntegerType.isinstance(param.type): - param = arith.SIToFPOp(self.getFloatType(), param).result - opCtor = getattr(quake, - '{}Op'.format(node.func.value.id.title())) - if quake.VeqType.isinstance(target.type): + if node.func.attr == 'adj': + target = self.popValue() + param = self.popValue() + if IntegerType.isinstance(param.type): + param = arith.SIToFPOp(self.getFloatType(), + param).result + opCtor = getattr(quake, + '{}Op'.format(node.func.value.id.title())) + if quake.VeqType.isinstance(target.type): + + def bodyBuilder(iterVal): + q = quake.ExtractRefOp(self.getRefType(), + target, + -1, + index=iterVal).result + opCtor([], [param], [], [q], is_adj=True) - def bodyBuilder(iterVal): - q = quake.ExtractRefOp(self.getRefType(), - target, - -1, - index=iterVal).result - opCtor([], [param], [], [q], is_adj=True) + veqSize = quake.VeqSizeOp(self.getIntegerType(), + target).result + self.createInvariantForLoop(veqSize, bodyBuilder) + return + elif quake.RefType.isinstance(target.type): + opCtor([], [param], [], [target], is_adj=True) + return + else: + self.emitFatalError( + 'adj quantum operation on incorrect type {}.'. + format(target.type), node) - veqSize = quake.VeqSizeOp(self.getIntegerType(), - target).result - self.createInvariantForLoop(veqSize, bodyBuilder) - return - elif quake.RefType.isinstance(target.type): - opCtor([], [param], [], [target], is_adj=True) - return - else: - self.emitFatalError( - 'adj quantum operation on incorrect type {}.'.format( - target.type), node) + self.emitFatalError( + f'Unknown attribute on quantum operation {node.func.value.id} ({node.func.attr}). {maybeProposeOpAttrFix(node.func.value.id, node.func.attr)}' + ) def visit_ListComp(self, node): """ diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index f6e0f93eac..9b62d360ef 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -189,6 +189,7 @@ def grover(N: int, M: int, oracle: Callable[[cudaq.qview], None]): assert '101' in counts assert '011' in counts + @skipIfPythonLessThan39 def test_pauli_word_input(): @@ -916,6 +917,7 @@ def test2() -> int: assert test2() == 10 + @skipIfPythonLessThan39 def test_empty_lists(): @@ -1043,4 +1045,59 @@ def kernel(): simple(4) kernel.compile() - print(kernel) \ No newline at end of file + print(kernel) + + +def test_user_error_op_attr_1446(): + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # behavior is strange, applies h to 6 qubits now + x.control(qubits[0], qubits[1]) + h(qubits) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'Unknown attribute on quantum' in repr( + e) and 'Did you mean x.ctrl(...)?' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # behavior is strange, applies h to 6 qubits now + x.adjoint(qubits[0], qubits[1]) + h(qubits) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'Unknown attribute on quantum' in repr( + e) and 'Did you mean x.adj(...)?' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # behavior is strange, applies h to 6 qubits now + x.adjointBadAttr(qubits[0], qubits[1]) + h(qubits) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'Unknown attribute on quantum' in repr( + e) and 'Did you mean x.adj(...)?' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # behavior is strange, applies h to 6 qubits now + x.noIdeaWhatThisIs(qubits[0], qubits[1]) + h(qubits) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'Unknown attribute on quantum' in repr( + e) and 'Did you mean ' not in repr(e) From 1b5b2c91872c75507ccc68e3fb54afef76292a75 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 13:59:33 +0000 Subject: [PATCH 2/9] Fix 1447, better errors for bad qubit types Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 24 ++++ python/tests/kernel/test_kernel_features.py | 126 ++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 64b1b906f5..8e7f8028c5 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -479,6 +479,18 @@ def argumentsValidForFunction(self, values, functionTy): for i, ty in enumerate(FunctionType(functionTy).inputs) ] + def checkControlAndTargetTypes(self, controls, targets): + [ + self.emitFatalError(f'control operand {i} is not of quantum type.') + if not self.isQuantumType(control.type) else None + for i, control in enumerate(controls) + ] + [ + self.emitFatalError(f'target operand {i} is not of quantum type.') + if not self.isQuantumType(target.type) else None + for i, target in enumerate(targets) + ] + def createInvariantForLoop(self, endVal, bodyBuilder, @@ -1207,6 +1219,7 @@ def bodyBuilder(iterVar): negCtrlBool = control in self.controlNegations negatedControlQubits = DenseBoolArrayAttr.get(negCtrlBool) self.controlNegations.clear() + self.checkControlAndTargetTypes([control], [target]) # Map `cx` to `XOp`... opCtor = getattr( quake, '{}Op'.format(node.func.id.title()[1:].upper())) @@ -1235,6 +1248,7 @@ def bodyBuilder(iterVar): .format(node.func.id, len(node.args), MAX_ARGS)) target = self.popValue() control = self.popValue() + self.checkControlAndTargetTypes([control], [target]) param = self.popValue() if IntegerType.isinstance(param.type): param = arith.SIToFPOp(self.getFloatType(), param).result @@ -1246,6 +1260,7 @@ def bodyBuilder(iterVar): if node.func.id in ["sdg", "tdg"]: target = self.popValue() + self.checkControlAndTargetTypes([], [target]) # Map `sdg` to `SOp`... opCtor = getattr(quake, '{}Op'.format(node.func.id.title()[0])) if quake.VeqType.isinstance(target.type): @@ -1291,6 +1306,7 @@ def bodyBuilder(iterVal): node) registerName = userProvidedRegName.value.value qubit = self.popValue() + self.checkControlAndTargetTypes([], [qubit]) opCtor = getattr(quake, '{}Op'.format(node.func.id.title())) i1Ty = self.getIntegerType(1) resTy = i1Ty if quake.RefType.isinstance( @@ -1311,6 +1327,7 @@ def bodyBuilder(iterVal): # this is a vanilla Hadamard qubitB = self.popValue() qubitA = self.popValue() + self.checkControlAndTargetTypes([], [qubitA, qubitB]) opCtor = getattr(quake, '{}Op'.format(node.func.id.title())) opCtor([], [], [], [qubitA, qubitB]) return @@ -1349,6 +1366,7 @@ def bodyBuilder(iterVal): elif node.func.id == 'exp_pauli': pauliWord = self.popValue() qubits = self.popValue() + self.checkControlAndTargetTypes([], [qubits]) theta = self.popValue() if IntegerType.isinstance(theta.type): theta = arith.SIToFPOp(self.getFloatType(), theta).result @@ -1533,6 +1551,7 @@ def bodyBuilder(iterVal): f"incorrect number of runtime arguments for cudaq.control({otherFuncName},..) call.", node) controls = self.popValue() + self.checkControlAndTargetTypes([controls], []) quake.ApplyOp([], [], [controls], values, callee=FlatSymbolRefAttr.get(nvqppPrefix + @@ -1648,12 +1667,14 @@ def maybeProposeOpAttrFix(opName, attrName): opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) + self.checkControlAndTargetTypes(controls, [target]) opCtor([], [], controls, [target], negated_qubit_controls=negatedControlQubits) return if node.func.attr == 'adj': target = self.popValue() + self.checkControlAndTargetTypes([], [target]) opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) if quake.VeqType.isinstance(target.type): @@ -1690,6 +1711,7 @@ def bodyBuilder(iterVal): ] opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) + self.checkControlAndTargetTypes(controls, [targetA, targetB]) opCtor([], [], controls, [targetA, targetB]) return @@ -1705,6 +1727,7 @@ def bodyBuilder(iterVal): param).result opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) + self.checkControlAndTargetTypes(controls, [target]) opCtor([], [param], controls[:-1], [target]) return @@ -1716,6 +1739,7 @@ def bodyBuilder(iterVal): param).result opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) + self.checkControlAndTargetTypes([], [target]) if quake.VeqType.isinstance(target.type): def bodyBuilder(iterVal): diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index 9b62d360ef..c0562d187b 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1101,3 +1101,129 @@ def test_kernel(nQubits: int): test_kernel.compile() assert 'Unknown attribute on quantum' in repr( e) and 'Did you mean ' not in repr(e) + + +def test_ctrl_wrong_dtype_1447(): + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # should throw error for acting on ints + x.ctrl(0, 1) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'control operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # should throw error for acting on ints + x.ctrl(qubits[0], 1) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x(qubits) + # should throw error for acting on ints + swap(0, 1) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + cx(0, 1) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'control operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + h(22) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'quantum operation h on incorrect quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + crx(2.2, 0, 1) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'control operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + mz(22) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + rx.ctrl(2.2, 2, 3) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'control operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + sdg(2) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + x.adj(3) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + swap.ctrl(3, 4) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + rx.ctrl(1.1, 3, 2) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'control operand 0 is not of quantum type' in repr(e) + + @cudaq.kernel + def test_kernel(nQubits: int): + qubits = cudaq.qvector(nQubits) + rx.adj(2.2, 3) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'target operand 0 is not of quantum type' in repr(e) From be6bf5cd0da81b0b74365fe2de47fa7b6c9ab677 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 14:02:30 +0000 Subject: [PATCH 3/9] Fix 1448, support math.pi Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 2 +- python/tests/kernel/test_kernel_features.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 8e7f8028c5..765edd147d 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -890,7 +890,7 @@ def visit_Attribute(self, node): self.symbolTable[node.value.id]).result) return - if node.value.id in ['np', 'numpy'] and node.attr == 'pi': + if node.value.id in ['np', 'numpy', 'math'] and node.attr == 'pi': self.pushValue(self.getConstantFloat(np.pi)) return diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index c0562d187b..b79207671b 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1227,3 +1227,13 @@ def test_kernel(nQubits: int): with pytest.raises(RuntimeError) as e: test_kernel.compile() assert 'target operand 0 is not of quantum type' in repr(e) + + +def test_math_module_pi(): + import math + @cudaq.kernel + def test_kernel() -> float: + theta = math.pi + return theta + test_kernel.compile() + assert np.isclose(test_kernel(), math.pi, 1e-12) From a026705b188248c4a2ce8930edd3d06f8f51bfff Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 14:07:04 +0000 Subject: [PATCH 4/9] Fix 1449, support len on qvector Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 15 ++++++++++----- python/tests/kernel/test_kernel_features.py | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 765edd147d..658d1326e3 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -1001,11 +1001,16 @@ def visit_Call(self, node): [self.visit(arg) for arg in node.args] if node.func.id == "len": listVal = self.popValue() - # FIXME could this be an array, anyway we need `emitFatalError` here - assert cc.StdvecType.isinstance(listVal.type) - self.pushValue( - cc.StdvecSizeOp(self.getIntegerType(), listVal).result) - return + if cc.StdvecType.isinstance(listVal.type): + self.pushValue( + cc.StdvecSizeOp(self.getIntegerType(), listVal).result) + return + if quake.VeqType.isinstance(listVal.type): + self.pushValue(quake.VeqSizeOp(self.getIntegerType(), + listVal).result) + return + + self.emitFatalError("__len__ not supported on variables of this type.", node) if node.func.id == "range": iTy = self.getIntegerType(64) diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index b79207671b..95ff1da82b 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1229,7 +1229,7 @@ def test_kernel(nQubits: int): assert 'target operand 0 is not of quantum type' in repr(e) -def test_math_module_pi(): +def test_math_module_pi_1448(): import math @cudaq.kernel def test_kernel() -> float: @@ -1237,3 +1237,16 @@ def test_kernel() -> float: return theta test_kernel.compile() assert np.isclose(test_kernel(), math.pi, 1e-12) + +def test_len_qvector_1449(): + + @cudaq.kernel + def test_kernel(nCountingQubits: int) -> int: + qubits = cudaq.qvector(nCountingQubits) + # N = counting_qubits.size() + N = len(qubits) + h(qubits) + return N + + test_kernel.compile() + assert test_kernel(5) == 5 \ No newline at end of file From 26708e0ad90cbbc89eedf9bdf10b6428bd425a2e Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 14:13:43 +0000 Subject: [PATCH 5/9] Fix 1450, catch missing paren and throw error Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 10 ++++++++++ python/tests/kernel/test_kernel_features.py | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 658d1326e3..cd2e8243b9 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -894,6 +894,16 @@ def visit_Attribute(self, node): self.pushValue(self.getConstantFloat(np.pi)) return + def maybeProposeAttributeFix(attrName): + # TODO add more smart suggestions... + if 'qubit' == attrName: + return 'Did you mean to construct a qubit? (missing parenthesis)' + if 'qvector' == attrName: + return 'Did you mean to construct a qvector? (missing parenthesis and size)' + return '' + + self.emitFatalError(f'Invalid attribute detected. {maybeProposeAttributeFix(node.attr)}') + def visit_Call(self, node): """ Map a Python Call operation to equivalent MLIR. This method will first check diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index 95ff1da82b..5dd70b9dbd 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1249,4 +1249,15 @@ def test_kernel(nCountingQubits: int) -> int: return N test_kernel.compile() - assert test_kernel(5) == 5 \ No newline at end of file + assert test_kernel(5) == 5 + +def test_missing_paren_1450(): + + @cudaq.kernel + def test_kernel(): + state_reg = cudaq.qubit + x(state_reg) + + with pytest.raises(RuntimeError) as e: + test_kernel.compile() + assert 'Invalid attribute detected. Did you mean to construct' in repr(e) \ No newline at end of file From 15340c1ca005b45ca964593138eff7fd1ce3af0b Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 17:50:08 +0000 Subject: [PATCH 6/9] Fix 1451, cast float to int for range() Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 46 ++++++++++++++------- python/tests/kernel/test_kernel_features.py | 12 +++++- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index cd2e8243b9..ada3a6f9eb 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -824,6 +824,9 @@ def visit_Assign(self, node): else: self.visit(node.value) + if len(self.valueStack) == 0: + self.emitFatalError("invalid assignement detected.", node) + varNames = [] varValues = [] @@ -894,15 +897,15 @@ def visit_Attribute(self, node): self.pushValue(self.getConstantFloat(np.pi)) return - def maybeProposeAttributeFix(attrName): - # TODO add more smart suggestions... - if 'qubit' == attrName: - return 'Did you mean to construct a qubit? (missing parenthesis)' - if 'qvector' == attrName: - return 'Did you mean to construct a qvector? (missing parenthesis and size)' - return '' - - self.emitFatalError(f'Invalid attribute detected. {maybeProposeAttributeFix(node.attr)}') + # def maybeProposeAttributeFix(attrName): + # # TODO add more smart suggestions... + # if 'qubit' == attrName: + # return 'Did you mean to construct a qubit? (missing parenthesis)' + # if 'qvector' == attrName: + # return 'Did you mean to construct a qvector? (missing parenthesis and size)' + # return '' + + # self.emitFatalError(f'Invalid attribute detected. {maybeProposeAttributeFix(node.attr)}') def visit_Call(self, node): """ @@ -1016,11 +1019,12 @@ def visit_Call(self, node): cc.StdvecSizeOp(self.getIntegerType(), listVal).result) return if quake.VeqType.isinstance(listVal.type): - self.pushValue(quake.VeqSizeOp(self.getIntegerType(), - listVal).result) + self.pushValue( + quake.VeqSizeOp(self.getIntegerType(), listVal).result) return - - self.emitFatalError("__len__ not supported on variables of this type.", node) + + self.emitFatalError( + "__len__ not supported on variables of this type.", node) if node.func.id == "range": iTy = self.getIntegerType(64) @@ -1066,6 +1070,19 @@ def visit_Call(self, node): endVal = self.ifPointerThenLoad(endVal) stepVal = self.ifPointerThenLoad(stepVal) + # Range expects integers + if F64Type.isinstance(startVal.type): + startVal = arith.FPToSIOp(self.getIntegerType(), + startVal).result + + if F64Type.isinstance(endVal.type): + endVal = arith.FPToSIOp(self.getIntegerType(), + endVal).result + + if F64Type.isinstance(stepVal.type): + stepVal = arith.FPToSIOp(self.getIntegerType(), + stepVal).result + # The total number of elements in the iterable # we are generating should be `N == endVal - startVal` totalSize = math.AbsIOp(arith.SubIOp(endVal, @@ -1737,13 +1754,14 @@ def bodyBuilder(iterVal): self.popValue() for i in range(len(self.valueStack)) ] param = controls[-1] + controls = controls[:-1] if IntegerType.isinstance(param.type): param = arith.SIToFPOp(self.getFloatType(), param).result opCtor = getattr(quake, '{}Op'.format(node.func.value.id.title())) self.checkControlAndTargetTypes(controls, [target]) - opCtor([], [param], controls[:-1], [target]) + opCtor([], [param], controls, [target]) return if node.func.attr == 'adj': diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index 5dd70b9dbd..770cf505bd 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1260,4 +1260,14 @@ def test_kernel(): with pytest.raises(RuntimeError) as e: test_kernel.compile() - assert 'Invalid attribute detected. Did you mean to construct' in repr(e) \ No newline at end of file + assert 'Invalid assignment detected.' + +# def test_cast_error_1451(): +# @cudaq.kernel +# def test_kernel(N: int): +# q = cudaq.qvector(N) +# for i in range(0,N/2): +# swap(q[i], q[N-i-1]) + +# # Test is that this compiles +# test_kernel.compile() From 289ce8dbe6d9c6a132241d68ccf4df4106a486ba Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 17:52:23 +0000 Subject: [PATCH 7/9] spell check Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index ada3a6f9eb..12b18e87f8 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -897,16 +897,6 @@ def visit_Attribute(self, node): self.pushValue(self.getConstantFloat(np.pi)) return - # def maybeProposeAttributeFix(attrName): - # # TODO add more smart suggestions... - # if 'qubit' == attrName: - # return 'Did you mean to construct a qubit? (missing parenthesis)' - # if 'qvector' == attrName: - # return 'Did you mean to construct a qvector? (missing parenthesis and size)' - # return '' - - # self.emitFatalError(f'Invalid attribute detected. {maybeProposeAttributeFix(node.attr)}') - def visit_Call(self, node): """ Map a Python Call operation to equivalent MLIR. This method will first check From 4c395bdea509a3b0d3853b962246a9fa559d21a3 Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Thu, 28 Mar 2024 17:56:09 +0000 Subject: [PATCH 8/9] add some docstrings Signed-off-by: Alex McCaskey --- python/cudaq/kernel/ast_bridge.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/cudaq/kernel/ast_bridge.py b/python/cudaq/kernel/ast_bridge.py index 12b18e87f8..d6253b8e17 100644 --- a/python/cudaq/kernel/ast_bridge.py +++ b/python/cudaq/kernel/ast_bridge.py @@ -480,6 +480,10 @@ def argumentsValidForFunction(self, values, functionTy): ] def checkControlAndTargetTypes(self, controls, targets): + """ + Loop through the provided control and target qubit values and + assert that they are of quantum type. Emit a fatal error if not. + """ [ self.emitFatalError(f'control operand {i} is not of quantum type.') if not self.isQuantumType(control.type) else None @@ -1659,6 +1663,12 @@ def bodyBuilder(iterVal): return def maybeProposeOpAttrFix(opName, attrName): + """ + Check the quantum operation attribute name and + propose a smart fix message if possible. For example, + if we have `x.control(...)` then remind the programmer the + correct attribute is `x.ctrl(...)`. + """ # TODO Add more possibilities in the future... if attrName in ['control' ] or 'control' in attrName or 'ctrl' in attrName: From 61acbd9834766220f68902ee67b672cc46a6205c Mon Sep 17 00:00:00 2001 From: Alex McCaskey Date: Mon, 1 Apr 2024 13:27:15 +0000 Subject: [PATCH 9/9] address pr comments Signed-off-by: Alex McCaskey --- python/tests/kernel/test_kernel_features.py | 22 +++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/python/tests/kernel/test_kernel_features.py b/python/tests/kernel/test_kernel_features.py index 770cf505bd..bf19cf2a2b 100644 --- a/python/tests/kernel/test_kernel_features.py +++ b/python/tests/kernel/test_kernel_features.py @@ -1054,7 +1054,6 @@ def test_user_error_op_attr_1446(): def test_kernel(nQubits: int): qubits = cudaq.qvector(nQubits) x(qubits) - # behavior is strange, applies h to 6 qubits now x.control(qubits[0], qubits[1]) h(qubits) @@ -1067,7 +1066,6 @@ def test_kernel(nQubits: int): def test_kernel(nQubits: int): qubits = cudaq.qvector(nQubits) x(qubits) - # behavior is strange, applies h to 6 qubits now x.adjoint(qubits[0], qubits[1]) h(qubits) @@ -1080,7 +1078,6 @@ def test_kernel(nQubits: int): def test_kernel(nQubits: int): qubits = cudaq.qvector(nQubits) x(qubits) - # behavior is strange, applies h to 6 qubits now x.adjointBadAttr(qubits[0], qubits[1]) h(qubits) @@ -1093,7 +1090,6 @@ def test_kernel(nQubits: int): def test_kernel(nQubits: int): qubits = cudaq.qvector(nQubits) x(qubits) - # behavior is strange, applies h to 6 qubits now x.noIdeaWhatThisIs(qubits[0], qubits[1]) h(qubits) @@ -1243,7 +1239,7 @@ def test_len_qvector_1449(): @cudaq.kernel def test_kernel(nCountingQubits: int) -> int: qubits = cudaq.qvector(nCountingQubits) - # N = counting_qubits.size() + # can use N = counting_qubits.size() N = len(qubits) h(qubits) return N @@ -1262,12 +1258,12 @@ def test_kernel(): test_kernel.compile() assert 'Invalid assignment detected.' -# def test_cast_error_1451(): -# @cudaq.kernel -# def test_kernel(N: int): -# q = cudaq.qvector(N) -# for i in range(0,N/2): -# swap(q[i], q[N-i-1]) +def test_cast_error_1451(): + @cudaq.kernel + def test_kernel(N: int): + q = cudaq.qvector(N) + for i in range(0,N/2): + swap(q[i], q[N-i-1]) -# # Test is that this compiles -# test_kernel.compile() + # Test is that this compiles + test_kernel.compile()