Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved error handling and bug fixes for Python #1458

Merged
merged 9 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 167 additions & 89 deletions python/cudaq/kernel/ast_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,22 @@ def argumentsValidForFunction(self, values, functionTy):
for i, ty in enumerate(FunctionType(functionTy).inputs)
]

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
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,
Expand Down Expand Up @@ -812,6 +828,9 @@ def visit_Assign(self, node):
else:
self.visit(node.value)

if len(self.valueStack) == 0:
self.emitFatalError("invalid assignement detected.", node)

varNames = []
varValues = []

Expand Down Expand Up @@ -878,7 +897,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

Expand Down Expand Up @@ -989,11 +1008,17 @@ 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)
Expand Down Expand Up @@ -1039,6 +1064,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,
Expand Down Expand Up @@ -1207,6 +1245,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()))
Expand Down Expand Up @@ -1235,6 +1274,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
Expand All @@ -1246,6 +1286,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):
Expand Down Expand Up @@ -1291,6 +1332,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(
Expand All @@ -1311,6 +1353,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
Expand Down Expand Up @@ -1349,6 +1392,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
Expand Down Expand Up @@ -1533,6 +1577,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 +
Expand Down Expand Up @@ -1617,26 +1662,77 @@ def bodyBuilder(iterVal):
index=zero).result)
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:
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()))
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):

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':
Expand All @@ -1647,78 +1743,60 @@ def bodyBuilder(iterVal):
]
opCtor = getattr(quake,
'{}Op'.format(node.func.value.id.title()))
self.checkControlAndTargetTypes(controls, [targetA, targetB])
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]
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, [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()))
self.checkControlAndTargetTypes([], [target])
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):
"""
Expand Down
Loading
Loading