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

[feature] AutoQASM: Free parameters #762

Merged
merged 10 commits into from
Oct 30, 2023
Merged
8 changes: 7 additions & 1 deletion src/braket/devices/local_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,13 @@ def _(
if DeviceActionType.OPENQASM not in simulator.properties.action:
raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs")
program = Program(source=program.to_ir(ir_type=IRType.OPENQASM))

if inputs:
inputs_copy = program.inputs.copy() if program.inputs is not None else {}
inputs_copy.update(inputs)
program = Program(
source=program.source,
inputs=inputs_copy,
)
# Pass mcm=True to the simulator to enable mid-circuit measurement simulation.
# When setting mcm=True, the simulator returns only the measurement results,
# which we then wrap into a GateModelQuantumTaskResult object to return.
Expand Down
24 changes: 19 additions & 5 deletions src/braket/experimental/autoqasm/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import inspect
from collections.abc import Callable
from types import FunctionType
from typing import Any, Optional, Union
from typing import Any, Optional, Union, get_args

import openqasm3.ast as qasm_ast
import oqpy.base
Expand Down Expand Up @@ -206,12 +206,23 @@ def _convert_main(
# Process the program
aq_transpiler.converted_call(f, args, kwargs, options=options)

# Modify program to add qubit declaration if necessary
# Modify program to add global declarations if necessary
_add_qubit_declaration(program_conversion_context)
_add_io_declarations(program_conversion_context)

return program_conversion_context.make_program()


def _add_io_declarations(program_conversion_context: aq_program.ProgramConversionContext) -> None:
root_oqpy_program = program_conversion_context.get_oqpy_program(
scope=aq_program.ProgramScope.MAIN
)
root_oqpy_program.declare(
program_conversion_context.get_free_parameters(),
to_beginning=True,
)


def _add_qubit_declaration(program_conversion_context: aq_program.ProgramConversionContext) -> None:
"""Modify the program to include a global qubit register declaration.

Expand Down Expand Up @@ -296,6 +307,7 @@ def _convert_subroutine(

# Process the program
subroutine_function_call = oqpy_sub(oqpy_program, *args, **kwargs)
program_conversion_context.register_args(args)

# Mark that we are finished processing this function
program_conversion_context.subroutines_processing.remove(f)
Expand Down Expand Up @@ -534,9 +546,11 @@ def _get_gate_args(f: Callable) -> aq_program.GateArgs:
)

if param.annotation == aq_instructions.QubitIdentifierType:
gate_args.append(param.name, True)
elif param.annotation in [float, aq_types.FloatVar]:
gate_args.append(param.name, False)
gate_args.append_qubit(param.name)
elif param.annotation == float or any(
type_ == float for type_ in get_args(param.annotation)
):
gate_args.append_angle(param.name)
else:
raise errors.ParameterTypeError(
f'Parameter "{param.name}" for gate "{f.__name__}" '
Expand Down
79 changes: 41 additions & 38 deletions src/braket/experimental/autoqasm/instructions/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
# language governing permissions and limitations under the License.


"""Quantum gates, unitary instructions, that apply to qubits.
"""
"""Quantum gates, unitary instructions, that apply to qubits."""

from typing import Union

from braket.circuits.free_parameter_expression import FreeParameterExpression

from .instructions import _qubit_instruction
from .qubits import QubitIdentifierType
Expand Down Expand Up @@ -52,14 +55,14 @@ def cnot(
def cphaseshift(
control: QubitIdentifierType,
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if there is a good way to define an alias for this union type. Seems like user-defined gates should also technically be using this as a type hint, and we wouldn't want users to have to copy this. Does it make sense to have an AngleIdentifierType that we expose as aq.Angle (just like aq.Qubit for QubitIdentifierType)? Or is that too specific - are there other types that should be supported with this pattern too? Gates can only take angles, but in general input parameters to a program can be other types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a good question. Aaron pointed out that we'll want to migrate from float to OpenQASM angles, because gates are not supposed to take floats.

It actually makes the code less clean to include the FreeParameterExpression -- there's a type check somewhere that gate parameters should only be floats, so we have to do extra work to ignore the FreeParameterExpression.

I feel like allowing the type hint to just hint at what the concrete value should eventually be is probably best in user code. The ParameterExpressions bit feels noisy and unproductive, even if it's true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I'm noticing now that gate_calibrations produce signatures with angle, so we should prioritize consistency using angles everywhere. I didn't realize they are already used in some places

Copy link
Contributor

Choose a reason for hiding this comment

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

Aaron pointed out that we'll want to migrate from float to OpenQASM angles, because gates are not supposed to take floats.

This is just a consideration for the IR we generate. Users will still certainly provide floats

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, gate definitions in OQ3 must use angle params, so we do use that for @aq.gate and @aq.gate_calibration. OQ3 allows implicitly casting float to angle, so technically everything else could use float and be fine, I think. But I also agree that if possible we should try to avoid exposing that to users, who just want to use float everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah okay, wasn't aware of the implicit cast. That's good news

) -> None:
"""Controlled phase shift gate.

Args:
control (QubitIdentifierType): Control qubit.
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("cphaseshift", [control, target], angle)
Expand All @@ -68,14 +71,14 @@ def cphaseshift(
def cphaseshift00(
control: QubitIdentifierType,
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Controlled phase shift gate for phasing the \\|00> state.

Args:
control (QubitIdentifierType): Control qubit.
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("cphaseshift00", [control, target], angle)
Expand All @@ -84,14 +87,14 @@ def cphaseshift00(
def cphaseshift01(
control: QubitIdentifierType,
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Controlled phase shift gate for phasing the \\|01> state.

Args:
control (QubitIdentifierType): Control qubit.
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("cphaseshift01", [control, target], angle)
Expand All @@ -100,14 +103,14 @@ def cphaseshift01(
def cphaseshift10(
control: QubitIdentifierType,
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Controlled phase shift gate for phasing the \\|10> state.

Args:
control (QubitIdentifierType): Control qubit.
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("cphaseshift10", [control, target], angle)
Expand Down Expand Up @@ -187,27 +190,27 @@ def ecr(

def gpi(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""IonQ GPi gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("gpi", [target], angle)


def gpi2(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""IonQ GPi2 gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("gpi2", [target], angle)
Expand Down Expand Up @@ -254,32 +257,32 @@ def iswap(
def ms(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle_0: float,
angle_1: float,
angle_2: float,
angle_0: Union[float, FreeParameterExpression],
angle_1: Union[float, FreeParameterExpression],
angle_2: Union[float, FreeParameterExpression],
) -> None:
"""IonQ Mølmer-Sørenson gate.

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle_0 (float): Rotation angle 0 in radians.
angle_1 (float): Rotation angle 1 in radians.
angle_2 (float): Rotation angle 2 in radians.
angle_0 (Union[float, FreeParameterExpression]): Rotation angle 0 in radians.
angle_1 (Union[float, FreeParameterExpression]): Rotation angle 1 in radians.
angle_2 (Union[float, FreeParameterExpression]): Rotation angle 2 in radians.

"""
_qubit_instruction("ms", [target_0, target_1], angle_0, angle_1, angle_2)


def phaseshift(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Phase shift gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("phaseshift", [target], angle)
Expand All @@ -288,56 +291,56 @@ def phaseshift(
def pswap(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""PSwap gate.

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("pswap", [target_0, target_1], angle)


def rx(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""X-axis rotation gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("rx", [target], angle)


def ry(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Y-axis rotation gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("ry", [target], angle)


def rz(
target: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Z-axis rotation gate.

Args:
target (QubitIdentifierType): Target qubit.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("rz", [target], angle)
Expand Down Expand Up @@ -444,14 +447,14 @@ def x(
def xx(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Ising XX coupling gate.

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("xx", [target_0, target_1], angle)
Expand All @@ -460,14 +463,14 @@ def xx(
def xy(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""XY gates

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("xy", [target_0, target_1], angle)
Expand All @@ -488,14 +491,14 @@ def y(
def yy(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Ising YY coupling gate.

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("yy", [target_0, target_1], angle)
Expand All @@ -516,14 +519,14 @@ def z(
def zz(
target_0: QubitIdentifierType,
target_1: QubitIdentifierType,
angle: float,
angle: Union[float, FreeParameterExpression],
) -> None:
"""Ising ZZ coupling gate.

Args:
target_0 (QubitIdentifierType): Target qubit 0.
target_1 (QubitIdentifierType): Target qubit 1.
angle (float): Rotation angle in radians.
angle (Union[float, FreeParameterExpression]): Rotation angle in radians.

"""
_qubit_instruction("zz", [target_0, target_1], angle)
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
# language governing permissions and limitations under the License.


"""Non-unitary instructions that apply to qubits.
"""
"""Non-unitary instructions that apply to qubits."""

from typing import Any

Expand All @@ -30,6 +29,7 @@ def _qubit_instruction(

# Add the instruction to the program.
program_conversion_context.register_gate(name)
program_conversion_context.register_args(args)
program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE
oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode)
oqpy_program.gate([_qubit(q) for q in qubits], name, *args)
Expand Down
Loading