Skip to content

Commit

Permalink
Merge remote-tracking branch 'ibm/main' into sabre/physical
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelishman committed Sep 20, 2023
2 parents 0475b3b + 01bb111 commit 03089f9
Show file tree
Hide file tree
Showing 114 changed files with 1,765 additions and 1,658 deletions.
46 changes: 39 additions & 7 deletions crates/accelerate/src/sabre_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// that they have been altered from the originals.
#![allow(clippy::too_many_arguments)]

use hashbrown::HashSet;
use ndarray::prelude::*;
use numpy::{IntoPyArray, PyArray, PyReadonlyArray2};
use pyo3::prelude::*;
Expand All @@ -28,6 +29,7 @@ use crate::sabre_swap::swap_map::SwapMap;
use crate::sabre_swap::{build_swap_map_inner, Heuristic, NodeBlockResults, SabreResult};

#[pyfunction]
#[pyo3(signature = (dag, neighbor_table, distance_matrix, heuristic, max_iterations, num_swap_trials, num_random_trials, seed=None, partial_layouts=vec![]))]
pub fn sabre_layout_and_routing(
py: Python,
dag: &SabreDAG,
Expand All @@ -36,20 +38,24 @@ pub fn sabre_layout_and_routing(
heuristic: &Heuristic,
max_iterations: usize,
num_swap_trials: usize,
num_layout_trials: usize,
num_random_trials: usize,
seed: Option<u64>,
mut partial_layouts: Vec<Vec<Option<u32>>>,
) -> (NLayout, PyObject, (SwapMap, PyObject, NodeBlockResults)) {
let run_in_parallel = getenv_use_multiple_threads();
let mut starting_layouts: Vec<Vec<Option<u32>>> =
(0..num_random_trials).map(|_| vec![]).collect();
starting_layouts.append(&mut partial_layouts);
let outer_rng = match seed {
Some(seed) => Pcg64Mcg::seed_from_u64(seed),
None => Pcg64Mcg::from_entropy(),
};
let seed_vec: Vec<u64> = outer_rng
.sample_iter(&rand::distributions::Standard)
.take(num_layout_trials)
.take(starting_layouts.len())
.collect();
let dist = distance_matrix.as_array();
let res = if run_in_parallel && num_layout_trials > 1 {
let res = if run_in_parallel && starting_layouts.len() > 1 {
seed_vec
.into_par_iter()
.enumerate()
Expand All @@ -65,6 +71,7 @@ pub fn sabre_layout_and_routing(
max_iterations,
num_swap_trials,
run_in_parallel,
&starting_layouts[index],
),
)
})
Expand All @@ -79,7 +86,8 @@ pub fn sabre_layout_and_routing(
} else {
seed_vec
.into_iter()
.map(|seed_trial| {
.enumerate()
.map(|(index, seed_trial)| {
layout_trial(
dag,
neighbor_table,
Expand All @@ -89,6 +97,7 @@ pub fn sabre_layout_and_routing(
max_iterations,
num_swap_trials,
run_in_parallel,
&starting_layouts[index],
)
})
.min_by_key(|(_, _, result)| result.map.map.values().map(|x| x.len()).sum::<usize>())
Expand All @@ -114,15 +123,38 @@ fn layout_trial(
max_iterations: usize,
num_swap_trials: usize,
run_swap_in_parallel: bool,
starting_layout: &[Option<u32>],
) -> (NLayout, Vec<PhysicalQubit>, SabreResult) {
let num_physical_qubits: u32 = distance_matrix.shape()[0].try_into().unwrap();
let mut rng = Pcg64Mcg::seed_from_u64(seed);

// Pick a random initial layout including a full ancilla allocation.
let mut initial_layout = {
let mut physical_qubits: Vec<PhysicalQubit> =
(0..num_physical_qubits).map(PhysicalQubit::new).collect();
physical_qubits.shuffle(&mut rng);
let physical_qubits: Vec<PhysicalQubit> = if !starting_layout.is_empty() {
let used_bits: HashSet<u32> = starting_layout
.iter()
.filter_map(|x| x.as_ref())
.copied()
.collect();
let mut free_bits: Vec<u32> = (0..num_physical_qubits)
.filter(|x| !used_bits.contains(x))
.collect();
free_bits.shuffle(&mut rng);
(0..num_physical_qubits)
.map(|x| {
let bit_index = match starting_layout.get(x as usize) {
Some(phys) => phys.unwrap_or_else(|| free_bits.pop().unwrap()),
None => free_bits.pop().unwrap(),
};
PhysicalQubit::new(bit_index)
})
.collect()
} else {
let mut physical_qubits: Vec<PhysicalQubit> =
(0..num_physical_qubits).map(PhysicalQubit::new).collect();
physical_qubits.shuffle(&mut rng);
physical_qubits
};
NLayout::from_virtual_to_physical(physical_qubits).unwrap()
};

Expand Down
14 changes: 9 additions & 5 deletions qiskit/algorithms/eigen_solvers/vqd.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,9 @@ def _eval_aux_ops(
list_op = ListOp(aux_operators)

aux_op_meas = expectation.convert(StateFn(list_op, is_measurement=True))
aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.ansatz.bind_parameters(parameters)))
aux_op_expect = aux_op_meas.compose(
CircuitStateFn(self.ansatz.assign_parameters(parameters))
)
aux_op_expect_sampled = sampler.convert(aux_op_expect)

# compute means
Expand Down Expand Up @@ -611,7 +613,9 @@ def compute_eigenvalues(

eigenvalue = (
StateFn(operator, is_measurement=True)
.compose(CircuitStateFn(self.ansatz.bind_parameters(result.optimal_parameters[-1])))
.compose(
CircuitStateFn(self.ansatz.assign_parameters(result.optimal_parameters[-1]))
)
.reduce()
.eval()
)
Expand All @@ -620,7 +624,7 @@ def compute_eigenvalues(
result.eigenstates.append(self._get_eigenstate(result.optimal_parameters[-1]))

if aux_operators is not None:
bound_ansatz = self.ansatz.bind_parameters(result.optimal_point[-1])
bound_ansatz = self.ansatz.assign_parameters(result.optimal_point[-1])
aux_value = eval_observables(
self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation
)
Expand Down Expand Up @@ -715,7 +719,7 @@ def get_energy_evaluation(

for state in range(step - 1):

prev_circ = self.ansatz.bind_parameters(prev_states[state])
prev_circ = self.ansatz.assign_parameters(prev_states[state])
overlap_op.append(~CircuitStateFn(prev_circ) @ CircuitStateFn(self.ansatz))

def energy_evaluation(parameters):
Expand Down Expand Up @@ -751,7 +755,7 @@ def energy_evaluation(parameters):

def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]:
"""Get the simulation outcome of the ansatz, provided with parameters."""
optimal_circuit = self.ansatz.bind_parameters(optimal_parameters)
optimal_circuit = self.ansatz.assign_parameters(optimal_parameters)
state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval()
if self.quantum_instance.is_statevector:
state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array
Expand Down
2 changes: 1 addition & 1 deletion qiskit/algorithms/eigensolvers/vqd.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ def compute_eigenvalues(
initial_point = validate_initial_point(initial_points[step - 1], self.ansatz)

if step > 1:
prev_states.append(self.ansatz.bind_parameters(result.optimal_points[-1]))
prev_states.append(self.ansatz.assign_parameters(result.optimal_points[-1]))

self._eval_count = 0
energy_evaluation = self._get_evaluate_energy(
Expand Down
2 changes: 1 addition & 1 deletion qiskit/algorithms/evolvers/trotterization/trotter_qrte.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def evolve(self, evolution_problem: EvolutionProblem) -> EvolutionResult:
f"PauliSumOp | SummedOp, {type(hamiltonian)} provided."
)
if isinstance(hamiltonian, OperatorBase):
hamiltonian = hamiltonian.bind_parameters(evolution_problem.param_value_dict)
hamiltonian = hamiltonian.assign_parameters(evolution_problem.param_value_dict)
if isinstance(hamiltonian, SummedOp):
hamiltonian = self._summed_op_to_pauli_sum_op(hamiltonian)
# the evolution gate
Expand Down
2 changes: 1 addition & 1 deletion qiskit/algorithms/gradients/reverse/bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def bind(
"""Bind parameters in a circuit (or list of circuits).
This method also allows passing parameter binds to parameters that are not in the circuit,
and thereby differs to :meth:`.QuantumCircuit.bind_parameters`.
and thereby differs to :meth:`.QuantumCircuit.assign_parameters`.
Args:
circuits: Input circuit(s).
Expand Down
4 changes: 2 additions & 2 deletions qiskit/algorithms/minimum_eigen_solvers/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ def compute_minimum_eigenvalue(
self._ret = result

if aux_operators is not None:
bound_ansatz = self.ansatz.bind_parameters(result.optimal_point)
bound_ansatz = self.ansatz.assign_parameters(result.optimal_point)

aux_values = eval_observables(
self.quantum_instance, bound_ansatz, aux_operators, expectation=expectation
Expand Down Expand Up @@ -648,7 +648,7 @@ def energy_evaluation(parameters):

def _get_eigenstate(self, optimal_parameters) -> list[float] | dict[str, int]:
"""Get the simulation outcome of the ansatz, provided with parameters."""
optimal_circuit = self.ansatz.bind_parameters(optimal_parameters)
optimal_circuit = self.ansatz.assign_parameters(optimal_parameters)
state_fn = self._circuit_sampler.convert(StateFn(optimal_circuit)).eval()
if self.quantum_instance.is_statevector:
state = state_fn.primitive.data # VectorStateFn -> Statevector -> np.array
Expand Down
4 changes: 2 additions & 2 deletions qiskit/algorithms/optimizers/qnspsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def loss(x):
initial_point = np.random.random(ansatz.num_parameters)
def loss(x):
bound = ansatz.bind_parameters(x)
bound = ansatz.assign_parameters(x)
return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval())
fidelity = QNSPSA.get_fidelity(ansatz)
Expand Down Expand Up @@ -387,7 +387,7 @@ def fidelity(values_x, values_y):
value_dict = dict(
zip(params_x[:] + params_y[:], values_x.tolist() + values_y.tolist())
)
return np.abs(expression.bind_parameters(value_dict).eval()) ** 2
return np.abs(expression.assign_parameters(value_dict).eval()) ** 2

else:
sampler = CircuitSampler(backend)
Expand Down
2 changes: 1 addition & 1 deletion qiskit/algorithms/optimizers/spsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class SPSA(Optimizer):
initial_point = np.random.random(ansatz.num_parameters)
def loss(x):
bound = ansatz.bind_parameters(x)
bound = ansatz.assign_parameters(x)
return np.real((StateFn(observable, is_measurement=True) @ StateFn(bound)).eval())
spsa = SPSA(maxiter=300)
Expand Down
4 changes: 2 additions & 2 deletions qiskit/algorithms/time_evolvers/pvqd/pvqd.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def get_loss(
self._validate_setup(skip={"optimizer"})

# use Trotterization to evolve the current state
trotterized = ansatz.bind_parameters(current_parameters)
trotterized = ansatz.assign_parameters(current_parameters)

evolution_gate = PauliEvolutionGate(hamiltonian, time=dt, synthesis=self.evolution)

Expand Down Expand Up @@ -388,7 +388,7 @@ def evolve(self, evolution_problem: TimeEvolutionProblem) -> TimeEvolutionResult
if observables is not None:
observable_values.append(evaluate_observables(next_parameters))

evolved_state = self.ansatz.bind_parameters(parameters[-1])
evolved_state = self.ansatz.assign_parameters(parameters[-1])

result = PVQDResult(
evolved_state=evolved_state,
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@
InstructionSet
Operation
EquivalenceLibrary
SingletonGate
Control Flow Operations
-----------------------
Expand Down Expand Up @@ -366,6 +367,7 @@

# pylint: disable=cyclic-import
from .controlledgate import ControlledGate
from .singleton_gate import SingletonGate
from .instruction import Instruction
from .instructionset import InstructionSet
from .operation import Operation
Expand Down
4 changes: 3 additions & 1 deletion qiskit/circuit/add_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def add_control(
# attempt decomposition
operation._define()
cgate = control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label, ctrl_state=ctrl_state)
cgate.base_gate.label = operation.label
if operation.label is not None:
cgate.base_gate = cgate.base_gate.to_mutable()
cgate.base_gate.label = operation.label
return cgate


Expand Down
1 change: 1 addition & 0 deletions qiskit/circuit/classical/expr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
.. autofunction:: bit_and
.. autofunction:: bit_or
.. autofunction:: bit_xor
.. autofunction:: logic_and
.. autofunction:: logic_or
.. autofunction:: equal
Expand Down
12 changes: 10 additions & 2 deletions qiskit/circuit/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@
class Gate(Instruction):
"""Unitary gate."""

def __init__(self, name: str, num_qubits: int, params: list, label: str | None = None) -> None:
def __init__(
self,
name: str,
num_qubits: int,
params: list,
label: str | None = None,
duration=None,
unit="dt",
) -> None:
"""Create a new gate.
Args:
Expand All @@ -34,7 +42,7 @@ def __init__(self, name: str, num_qubits: int, params: list, label: str | None =
label: An optional label for the gate.
"""
self.definition = None
super().__init__(name, num_qubits, 0, params, label=label)
super().__init__(name, num_qubits, 0, params, label=label, duration=duration, unit=unit)

# Set higher priority than Numpy array and matrix classes
__array_priority__ = 20
Expand Down
33 changes: 30 additions & 3 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,43 @@ def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt
self._label = label
# tuple (ClassicalRegister, int), tuple (Clbit, bool) or tuple (Clbit, int)
# when the instruction has a conditional ("if")
self.condition = None
self._condition = None
# list of instructions (and their contexts) that this instruction is composed of
# empty definition means opaque or fundamental instruction
self._definition = None

self._duration = duration
self._unit = unit

self.params = params # must be at last (other properties may be required for validation)

@property
def mutable(self) -> bool:
"""Is this instance is a mutable unique instance or not.
If this attribute is ``False`` the gate instance is a shared singleton
and is not mutable.
"""
return True

def to_mutable(self):
"""Return a mutable copy of this gate.
This method will return a new mutable copy of this gate instance.
If a singleton instance is being used this will be a new unique
instance that can be mutated. If the instance is already mutable it
will be a deepcopy of that instance.
"""
return self.copy()

@property
def condition(self):
"""The classical condition on the instruction."""
return self._condition

@condition.setter
def condition(self, condition):
self._condition = condition

def __eq__(self, other):
"""Two instructions are the same if they have the same name,
same dimensions, and same params.
Expand Down Expand Up @@ -409,7 +436,7 @@ def c_if(self, classical, val):
# Casting the conditional value as Boolean when
# the classical condition is on a classical bit.
val = bool(val)
self.condition = (classical, val)
self._condition = (classical, val)
return self

def copy(self, name=None):
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/instructionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def c_if(self, classical: Clbit | ClassicalRegister | int, val: int) -> "Instruc
if self._requester is not None:
classical = self._requester(classical)
for instruction in self._instructions:
instruction.operation.c_if(classical, val)
instruction.operation = instruction.operation.c_if(classical, val)
return self

# Legacy support for properties. Added in Terra 0.21 to support the internal switch in
Expand Down
Loading

0 comments on commit 03089f9

Please sign in to comment.