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

feat: allow negative controlled global phase gate #216

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
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(
jcjaskula-aws marked this conversation as resolved.
Show resolved Hide resolved
[],
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 @@ -312,14 +312,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 @@ -359,6 +364,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 @@ -432,7 +432,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 @@ -790,7 +795,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 @@ -860,12 +870,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):
jcjaskula-aws marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -338,10 +340,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 @@ -434,7 +438,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 @@ -444,7 +449,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 @@ -681,7 +687,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 @@ -1119,15 +1130,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 @@ -1825,10 +1873,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
Loading