Skip to content

Commit

Permalink
Merge branch 'main' into multi2
Browse files Browse the repository at this point in the history
  • Loading branch information
speller26 authored Jun 27, 2024
2 parents 5f06a06 + 3378eaa commit 9d86d58
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 118 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## v1.25.0 (2024-06-26)

### Features

* Track classical register indices for measurements

### Bug Fixes and Other Changes

* Include measured in noncontiguous qubit map

## v1.24.1 (2024-06-26)

### Bug Fixes and Other Changes

* Use csr_matrix.getH() instead of H

## v1.24.0 (2024-06-24)

### Features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ def _apply_hamiltonian(
rabi_ops, rabi_coefs, detuning_ops, detuning_coefs
):
output_register += (rabi_coef[index_time] / 2) * rabi_op.dot(input_register)
output_register += (np.conj(rabi_coef[index_time]) / 2) * rabi_op.H.dot(input_register)
output_register += (np.conj(rabi_coef[index_time]) / 2) * rabi_op.getH().dot(input_register)
output_register -= detuning_coef[index_time] * detuning_op.dot(input_register)

# Add local detuning
Expand Down
2 changes: 1 addition & 1 deletion src/braket/default_simulator/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "1.24.1.dev0"
__version__ = "1.25.1.dev0"
8 changes: 7 additions & 1 deletion src/braket/default_simulator/openqasm/_helpers/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ def convert_index(index: Union[RangeDefinition, IntegerLiteral]) -> Union[int, s

def flatten_indices(indices: list[IndexElement]) -> list:
"""Convert a[i][j][k] to the equivalent a[i, j, k]"""
return sum((index for index in indices), [])
result = []
for index in indices:
if isinstance(index, DiscreteSet):
result.append(index)
else:
result += index
return result


def unwrap_var_type(var_type: ClassicalType) -> ClassicalType:
Expand Down
12 changes: 10 additions & 2 deletions src/braket/default_simulator/openqasm/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from __future__ import annotations

from collections.abc import Iterable
from typing import Optional

import numpy as np
Expand Down Expand Up @@ -42,6 +43,7 @@ def __init__(
self.results = []
self.qubit_set = set()
self.measured_qubits = []
self.target_classical_indices = []

if instructions:
for instruction in instructions:
Expand All @@ -61,11 +63,17 @@ def add_instruction(self, instruction: [GateOperation, KrausOperation]) -> None:
self.instructions.append(instruction)
self.qubit_set |= set(instruction.targets)

def add_measure(self, target: tuple[int]):
for qubit in target:
def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None):
for index, qubit in enumerate(target):
if qubit in self.measured_qubits:
raise ValueError(f"Qubit {qubit} is already measured or captured.")
self.measured_qubits.append(qubit)
self.qubit_set.add(qubit)
self.target_classical_indices.append(
classical_targets[index]
if classical_targets
else max(index, len(self.target_classical_indices))
)

def add_result(self, result: Results) -> None:
"""
Expand Down
34 changes: 32 additions & 2 deletions src/braket/default_simulator/openqasm/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ._helpers.arrays import (
convert_range_def_to_range,
create_empty_array,
flatten_indices,
get_elements,
get_type_width,
)
Expand Down Expand Up @@ -471,12 +472,41 @@ def _(self, node: QuantumGateModifier) -> QuantumGateModifier:
@visit.register
def _(self, node: QuantumMeasurement) -> None:
qubits = self.context.get_qubits(self.visit(node.qubit))
self.context.add_measure(qubits)
return qubits

@visit.register
def _(self, node: QuantumMeasurementStatement) -> None:
"""The measure is performed but the assignment is ignored"""
self.visit(node.measure)
qubits = self.visit(node.measure)
targets = []
if node.target:
if isinstance(node.target, IndexedIdentifier):
indices = flatten_indices(node.target.indices)
if len(node.target.indices) != 1:
raise ValueError(
"Multi-Dimensional indexing not supported for classical registers."
)
elem = indices[0]
if isinstance(elem, DiscreteSet):
self._uses_advanced_language_features = True
target_indices = [self.visit(val).value for val in elem.values]
targets.extend(target_indices)
elif isinstance(elem, RangeDefinition):
self._uses_advanced_language_features = True
target_indices = convert_range_def_to_range(self.visit(elem))
targets.extend(target_indices)
else:
target_idx = elem.value
targets.append(target_idx)

if not len(targets):
targets = None

if targets and len(targets) != len(qubits):
raise ValueError(
f"Number of qubits ({len(qubits)}) does not match number of provided classical targets ({len(targets)})"
)
self.context.add_measure(qubits, targets)

@visit.register
def _(self, node: ClassicalAssignment) -> None:
Expand Down
6 changes: 3 additions & 3 deletions src/braket/default_simulator/openqasm/program_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,7 @@ def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]):
"""
raise NotImplementedError

def add_measure(self, target: tuple[int]):
def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None):
"""Add qubit targets to be measured"""


Expand Down Expand Up @@ -902,5 +902,5 @@ def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]):
def add_result(self, result: Results) -> None:
self._circuit.add_result(result)

def add_measure(self, target: tuple[int]):
self._circuit.add_measure(target)
def add_measure(self, target: tuple[int], classical_targets: Iterable[int] = None):
self._circuit.add_measure(target, classical_targets)
71 changes: 31 additions & 40 deletions src/braket/default_simulator/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def _create_results_obj(
openqasm_ir: OpenQASMProgram,
simulation: Simulation,
measured_qubits: list[int] = None,
mapped_measured_qubits: list[int] = None,
) -> GateModelTaskResult:
return GateModelTaskResult.construct(
taskMetadata=TaskMetadata(
Expand All @@ -267,10 +268,8 @@ def _create_results_obj(
action=openqasm_ir,
),
resultTypes=results,
measurements=self._formatted_measurements(simulation, measured_qubits),
measuredQubits=(
measured_qubits if measured_qubits else self._get_all_qubits(simulation.qubit_count)
),
measurements=self._formatted_measurements(simulation, mapped_measured_qubits),
measuredQubits=(measured_qubits or list(range(simulation.qubit_count))),
)

@staticmethod
Expand Down Expand Up @@ -348,10 +347,6 @@ def _validate_input_provided(self, circuit: Circuit) -> None:
missing_input = param.free_symbols.pop()
raise NameError(f"Missing input variable '{missing_input}'.")

@staticmethod
def _get_all_qubits(qubit_count: int) -> list[int]:
return list(range(qubit_count))

@staticmethod
def _tensor_product_index_dict(
observable: TensorProduct, func: Callable[[Observable], Any]
Expand Down Expand Up @@ -383,7 +378,7 @@ def _observable_hash(observable: Observable) -> Union[str, dict[int, str]]:
return str(observable.__class__.__name__)

@staticmethod
def _map_circuit_to_contiguous_qubits(circuit: Union[Circuit, JaqcdProgram]) -> Circuit:
def _map_circuit_to_contiguous_qubits(circuit: Union[Circuit, JaqcdProgram]) -> dict[int, int]:
"""
Maps the qubits in operations and result types to contiguous qubits.
Expand All @@ -392,24 +387,23 @@ def _map_circuit_to_contiguous_qubits(circuit: Union[Circuit, JaqcdProgram]) ->
result types.
Returns:
Circuit: The circuit with qubits in operations and result types mapped
to contiguous qubits.
dict[int, int]: Map of qubit index to corresponding contiguous index
"""
circuit_qubit_set = BaseLocalSimulator._get_circuit_qubit_set(circuit)
qubit_map = BaseLocalSimulator._contiguous_qubit_mapping(circuit_qubit_set)
BaseLocalSimulator._map_instructions_to_qubits(circuit, qubit_map)
return circuit
BaseLocalSimulator._map_circuit_qubits(circuit, qubit_map)
return qubit_map

@staticmethod
def _get_circuit_qubit_set(circuit: Union[Circuit, JaqcdProgram]) -> set:
def _get_circuit_qubit_set(circuit: Union[Circuit, JaqcdProgram]) -> set[int]:
"""
Returns the set of qubits used in the given circuit.
Args:
circuit (Union[Circuit, JaqcdProgram]): The circuit from which to extract the qubit set.
Returns:
set: The set of qubits used in the circuit.
set[int]: The set of qubits used in the circuit.
"""
if isinstance(circuit, Circuit):
return circuit.qubit_set
Expand All @@ -425,12 +419,13 @@ def _get_circuit_qubit_set(circuit: Union[Circuit, JaqcdProgram]) -> set:
return BaseLocalSimulator._get_qubits_referenced(operations)

@staticmethod
def _map_instructions_to_qubits(circuit: Union[Circuit, JaqcdProgram], qubit_map: dict):
def _map_circuit_qubits(circuit: Union[Circuit, JaqcdProgram], qubit_map: dict[int, int]):
"""
Maps the qubits in operations and result types to contiguous qubits.
Args:
circuit (Circuit): The circuit containing the operations and result types.
qubit_map (dict[int, int]): The mapping from qubits to their contiguous indices.
Returns:
Circuit: The circuit with qubits in operations and result types mapped
Expand All @@ -441,7 +436,6 @@ def _map_instructions_to_qubits(circuit: Union[Circuit, JaqcdProgram], qubit_map
BaseLocalSimulator._map_circuit_results(circuit, qubit_map)
else:
BaseLocalSimulator._map_jaqcd_instructions(circuit, qubit_map)

return circuit

@staticmethod
Expand Down Expand Up @@ -514,13 +508,13 @@ def _map_instruction_attributes(instruction, qubit_map: dict):
instruction.targets = [qubit_map.get(q, q) for q in instruction.targets]

@staticmethod
def _contiguous_qubit_mapping(qubit_set: list[int]) -> dict[int, int]:
def _contiguous_qubit_mapping(qubit_set: set[int]) -> dict[int, int]:
"""
Maping of qubits to contiguous integers. The qubit mapping may be discontiguous or
contiguous.
Args:
qubit_set (list[int]): List of qubits to be mapped.
qubit_set (set[int]): List of qubits to be mapped.
Returns:
dict[int, int]: Dictionary where keys are qubits and values are contiguous integers.
Expand Down Expand Up @@ -548,22 +542,16 @@ def _formatted_measurements(
]
# Gets the subset of measurements from the full measurements
if measured_qubits is not None and measured_qubits != []:
if any(qubit in range(simulation.qubit_count) for qubit in measured_qubits):
measured_qubits = np.array(measured_qubits)
in_circuit_mask = measured_qubits < simulation.qubit_count
measured_qubits_in_circuit = measured_qubits[in_circuit_mask]
measured_qubits_not_in_circuit = measured_qubits[~in_circuit_mask]

measurements_array = np.array(measurements)
selected_measurements = measurements_array[:, measured_qubits_in_circuit]
measurements = np.pad(
selected_measurements, ((0, 0), (0, len(measured_qubits_not_in_circuit)))
).tolist()

else:
measurements = np.zeros(
(simulation.shots, len(measured_qubits)), dtype=int
).tolist()
measured_qubits = np.array(measured_qubits)
in_circuit_mask = measured_qubits < simulation.qubit_count
measured_qubits_in_circuit = measured_qubits[in_circuit_mask]
measured_qubits_not_in_circuit = measured_qubits[~in_circuit_mask]

measurements_array = np.array(measurements)
selected_measurements = measurements_array[:, measured_qubits_in_circuit]
measurements = np.pad(
selected_measurements, ((0, 0), (0, len(measured_qubits_not_in_circuit)))
).tolist()
return measurements

def run_openqasm(
Expand Down Expand Up @@ -593,8 +581,12 @@ def run_openqasm(
are requested when shots>0.
"""
circuit = self.parse_program(openqasm_ir).circuit
qubit_map = BaseLocalSimulator._map_circuit_to_contiguous_qubits(circuit)
qubit_count = circuit.num_qubits
measured_qubits = circuit.measured_qubits
mapped_measured_qubits = (
[qubit_map[q] for q in measured_qubits] if measured_qubits else None
)

self._validate_ir_results_compatibility(
circuit.results,
Expand All @@ -607,8 +599,6 @@ def run_openqasm(
self._validate_input_provided(circuit)
BaseLocalSimulator._validate_shots_and_ir_results(shots, circuit.results, qubit_count)

circuit = BaseLocalSimulator._map_circuit_to_contiguous_qubits(circuit)

results = circuit.results

simulation = self.initialize_simulation(
Expand All @@ -635,7 +625,9 @@ def run_openqasm(
else:
simulation.evolve(circuit.basis_rotation_instructions)

return self._create_results_obj(results, openqasm_ir, simulation, measured_qubits)
return self._create_results_obj(
results, openqasm_ir, simulation, measured_qubits, mapped_measured_qubits
)

def run_jaqcd(
self,
Expand Down Expand Up @@ -674,8 +666,7 @@ def run_jaqcd(
device_action_type=DeviceActionType.JAQCD,
)
BaseLocalSimulator._validate_shots_and_ir_results(shots, circuit_ir.results, qubit_count)

circuit_ir = BaseLocalSimulator._map_circuit_to_contiguous_qubits(circuit_ir)
BaseLocalSimulator._map_circuit_to_contiguous_qubits(circuit_ir)

operations = [
from_braket_instruction(instruction) for instruction in circuit_ir.instructions
Expand Down
6 changes: 0 additions & 6 deletions test/resources/discontiguous.qasm

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"},
"instructions": [
{"target": 2, "type": "x"},
{"target": 2, "type": "h"},
{"control": 2, "target": 9, "type": "cnot"}
],
"results": [],
Expand Down
6 changes: 6 additions & 0 deletions test/resources/noncontiguous_physical.qasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
OPENQASM 3.0;
bit[2] b;
h $2;
cnot $2, $8;
b[0] = measure $2;
b[1] = measure $8;
7 changes: 7 additions & 0 deletions test/resources/noncontiguous_virtual.qasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
OPENQASM 3.0;
bit[2] b;
qubit[10] q;
h q[2];
cnot q[2], q[8];
b[0] = measure q[2];
b[1] = measure q[8];
Loading

0 comments on commit 9d86d58

Please sign in to comment.