Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ test/unit_tests/.DS_Store
test/unit_tests/braket/.DS_Store
test/.benchmarks
.DS_Store
.vscode/*
18 changes: 14 additions & 4 deletions src/braket/default_simulator/openqasm/_helpers/quantum.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,16 @@ def is_controlled(phase: QuantumPhase) -> bool:
return False


def convert_phase_to_gate(controlled_phase: QuantumPhase) -> QuantumGate:
def convert_phase_to_gate(controlled_phase: QuantumPhase) -> Union[QuantumGate, list[QuantumGate]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach increases complexity, particularly in inline_gate_def_body which has a lot of logic embedded already. I wonder if we shouldn't be handling "controlled phase instructions" logic during interpretation, but rather in ProgramContext.add_phase_instruction. I guess it comes down to whether controlled phases are considered part of the spec, or up to implementation (which now we do a mix of both)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a nice point you are raising. It is problably ok to defer later to the context construction. The BDK should anyway have the logic to handle it if necessary.

"""Convert a controlled quantum phase into a quantum gate"""
ctrl_modifiers = get_ctrl_modifiers(controlled_phase.modifiers)
first_ctrl_modifier = ctrl_modifiers[-1]
if first_ctrl_modifier.modifier == GateModifierName.negctrl:
raise ValueError("negctrl modifier undefined for gphase operation")
if first_ctrl_modifier.argument.value == 1:
ctrl_modifiers.pop()
else:
ctrl_modifiers[-1].argument.value -= 1
return QuantumGate(

ctrl_phaseshift = QuantumGate(
ctrl_modifiers,
Identifier("U"),
[
Expand All @@ -75,6 +74,17 @@ def convert_phase_to_gate(controlled_phase: QuantumPhase) -> QuantumGate:
controlled_phase.qubits,
)

if first_ctrl_modifier.modifier == GateModifierName.negctrl:
X = QuantumGate(
[],
Identifier("x"),
[],
[controlled_phase.qubits[-1]],
)
return [X, ctrl_phaseshift, X]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused how this is being handled without any changes to inline_gate_def_body

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

me too. Reworked the functions that needed a change.

else:
return ctrl_phaseshift


def get_ctrl_modifiers(modifiers: list[QuantumGateModifier]) -> list[QuantumGateModifier]:
"""Get the control modifiers from a list of quantum gate modifiers"""
Expand Down
8 changes: 7 additions & 1 deletion src/braket/default_simulator/openqasm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,19 @@ def _(self, node: QuantumGateDefinition) -> None:

def inline_gate_def_body(self, body: list[QuantumStatement]) -> list[QuantumStatement]:
inlined_body = []
for statement in body:
statement = body.pop(0) if body else None
while statement is not None:
if isinstance(statement, QuantumPhase):
statement.argument = self.visit(statement.argument)
statement.modifiers = self.visit(statement.modifiers)
if is_inverted(statement):
statement = invert_phase(statement)
if is_controlled(statement):
statement = convert_phase_to_gate(statement)
if isinstance(statement, list):
for gate in reversed(statement):
body.insert(0, gate)
continue
# statement is a quantum phase instruction
else:
inlined_body.append(statement)
Expand Down Expand Up @@ -358,6 +363,7 @@ def inline_gate_def_body(self, body: list[QuantumStatement]) -> list[QuantumStat
ctrl_qubits,
pow_modifiers,
)
statement = body.pop(0) if body else None
return inlined_body

@visit.register
Expand Down
23 changes: 19 additions & 4 deletions src/braket/default_simulator/openqasm/program_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,12 @@ def circuit(self):
def __repr__(self):
return "\n\n".join(
repr(x)
for x in (self.symbol_table, self.variable_table, self.gate_table, self.qubit_mapping)
for x in (
self.symbol_table,
self.variable_table,
self.gate_table,
self.qubit_mapping,
)
)

def load_inputs(self, inputs: dict[str, Any]) -> None:
Expand Down Expand Up @@ -782,7 +787,12 @@ def handle_parameter_value(self, value: Union[float, Expr]) -> Any:

@abstractmethod
def add_gate_instruction(
self, gate_name: str, target: tuple[int, ...], params, ctrl_modifiers: list[int], power: int
self,
gate_name: str,
target: tuple[int, ...],
params,
ctrl_modifiers: list[int],
power: int,
):
"""Abstract method to add Braket gate to the circuit.
Args:
Expand Down Expand Up @@ -849,12 +859,17 @@ def is_builtin_gate(self, name: str) -> bool:
user_defined_gate = self.is_user_defined_gate(name)
return name in BRAKET_GATES and not user_defined_gate

def add_phase_instruction(self, target: tuple[int], phase_value: int):
def add_phase_instruction(self, target: tuple[int], phase_value: float):
phase_instruction = GPhase(target, phase_value)
self._circuit.add_instruction(phase_instruction)

def add_gate_instruction(
self, gate_name: str, target: tuple[int, ...], params, ctrl_modifiers: list[int], power: int
self,
gate_name: str,
target: tuple[int, ...],
params,
ctrl_modifiers: list[int],
power: int,
):
instruction = BRAKET_GATES[gate_name](
target, *params, ctrl_modifiers=ctrl_modifiers, power=power
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@
BoolType,
FloatLiteral,
FloatType,
GateModifierName,
Identifier,
IndexedIdentifier,
IntegerLiteral,
IntType,
QuantumGate,
QuantumGateDefinition,
QuantumGateModifier,
SymbolLiteral,
UintType,
)
Expand Down Expand Up @@ -306,10 +308,12 @@ def test_array_declaration():
base_type=IntType(), dimensions=[IntegerLiteral(2)]
)
assert context.get_type("multi_dim") == ArrayType(
base_type=UintType(IntegerLiteral(8)), dimensions=[IntegerLiteral(2), IntegerLiteral(2)]
base_type=UintType(IntegerLiteral(8)),
dimensions=[IntegerLiteral(2), IntegerLiteral(2)],
)
assert context.get_type("by_ref") == ArrayType(
base_type=UintType(IntegerLiteral(8)), dimensions=[IntegerLiteral(2), IntegerLiteral(2)]
base_type=UintType(IntegerLiteral(8)),
dimensions=[IntegerLiteral(2), IntegerLiteral(2)],
)
assert context.get_type("with_expressions") == ArrayType(
base_type=UintType(IntegerLiteral(8)),
Expand Down Expand Up @@ -402,7 +406,8 @@ def test_indexed_expression():
context = Interpreter().run(qasm)

assert context.get_type("multi_dim") == ArrayType(
base_type=UintType(IntegerLiteral(8)), dimensions=[IntegerLiteral(2), IntegerLiteral(2)]
base_type=UintType(IntegerLiteral(8)),
dimensions=[IntegerLiteral(2), IntegerLiteral(2)],
)
assert context.get_type("int_from_array") == IntType(IntegerLiteral(8))
assert context.get_type("array_from_array") == ArrayType(
Expand All @@ -412,7 +417,8 @@ def test_indexed_expression():
base_type=UintType(IntegerLiteral(8)), dimensions=[IntegerLiteral(3)]
)
assert context.get_type("using_set_multi_dim") == ArrayType(
base_type=UintType(IntegerLiteral(8)), dimensions=[IntegerLiteral(3), IntegerLiteral(2)]
base_type=UintType(IntegerLiteral(8)),
dimensions=[IntegerLiteral(3), IntegerLiteral(2)],
)

assert context.get_value("multi_dim") == ArrayLiteral(
Expand Down Expand Up @@ -649,7 +655,12 @@ def test_indexed_identifier():
]
)
assert context.get_value("two") == ArrayLiteral(
[BooleanLiteral(False), BooleanLiteral(False), BooleanLiteral(True), BooleanLiteral(False)]
[
BooleanLiteral(False),
BooleanLiteral(False),
BooleanLiteral(True),
BooleanLiteral(False),
]
)


Expand Down Expand Up @@ -1073,15 +1084,52 @@ def test_gphase():
assert np.allclose(simulation.state_vector, [-1 / np.sqrt(2), 0, 0, 1 / np.sqrt(2)])


def test_no_neg_ctrl_phase():
def test_neg_ctrl_phase():
qasm = """
gate bad_phase a {
negctrl @ gphase(π/2);
}
gate some_ctrl_gphase(λ) a, b { ctrl @ negctrl @ gphase(λ) a, b; }
qubit[2] q;
x q[0];
h q[1];
some_ctrl_gphase(π) q[0], q[1];
h q[1];
"""
no_negctrl = "negctrl modifier undefined for gphase operation"
with pytest.raises(ValueError, match=no_negctrl):
Interpreter().run(qasm)
context = Interpreter().run(qasm)
circuit = context.circuit
simulation = StateVectorSimulation(2, 1, 1)
simulation.evolve(circuit.instructions)
assert np.allclose(simulation.state_vector, [0, 0, 0, -1])

assert context.get_gate_definition("some_ctrl_gphase") == QuantumGateDefinition(
name=Identifier("some_ctrl_gphase"),
arguments=[Identifier("λ")],
qubits=[Identifier("a"), Identifier("b")],
body=[
QuantumGate(
modifiers=[],
name=Identifier("x"),
arguments=[],
qubits=[Identifier("b")],
),
QuantumGate(
modifiers=[
QuantumGateModifier(modifier=GateModifierName.ctrl, argument=IntegerLiteral(1))
],
name=Identifier("U"),
arguments=[
IntegerLiteral(0),
IntegerLiteral(0),
Identifier("λ"),
],
qubits=[Identifier("a"), Identifier("b")],
),
QuantumGate(
modifiers=[],
name=Identifier("x"),
arguments=[],
qubits=[Identifier("b")],
),
],
)


def test_if():
Expand Down Expand Up @@ -1779,10 +1827,20 @@ def classical(bit[4] bits) {
"""
context = Interpreter().run(qasm)
assert context.get_value("before") == ArrayLiteral(
[BooleanLiteral(False), BooleanLiteral(False), BooleanLiteral(False), BooleanLiteral(False)]
[
BooleanLiteral(False),
BooleanLiteral(False),
BooleanLiteral(False),
BooleanLiteral(False),
]
)
assert context.get_value("after") == ArrayLiteral(
[BooleanLiteral(True), BooleanLiteral(False), BooleanLiteral(False), BooleanLiteral(False)]
[
BooleanLiteral(True),
BooleanLiteral(False),
BooleanLiteral(False),
BooleanLiteral(False),
]
)


Expand Down