Skip to content

Commit

Permalink
IQP as circuit library function (and in Rust) (#13241)
Browse files Browse the repository at this point in the history
* make IQP a function

* Make IQP fast

* Try fixing type on windows Py 3.9

* rm trailing comment

* review comments v1

* fix test comment

* split into iqp and random_iqp

* fix indentation?

i hope this was the cause of the error, lets see

* fix merge artifact

-- the diagonal entries on the diagonal got lost!

Co-authored-by: Alexander Ivrii <[email protected]>

---------

Co-authored-by: Alexander Ivrii <[email protected]>
  • Loading branch information
Cryoris and alexanderivrii authored Oct 30, 2024
1 parent 0bc6662 commit 91ceee5
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 36 deletions.
165 changes: 165 additions & 0 deletions crates/accelerate/src/circuit_library/iqp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2024
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use std::f64::consts::PI;

use ndarray::{Array2, ArrayView2};
use numpy::PyReadonlyArray2;
use pyo3::prelude::*;
use qiskit_circuit::{
circuit_data::CircuitData,
operations::{Param, StandardGate},
Qubit,
};
use rand::{Rng, SeedableRng};
use rand_pcg::Pcg64Mcg;
use smallvec::{smallvec, SmallVec};

use crate::CircuitError;

const PI2: f64 = PI / 2.0;
const PI8: f64 = PI / 8.0;

fn iqp(
interactions: ArrayView2<i64>,
) -> impl Iterator<Item = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>)> + '_ {
let num_qubits = interactions.ncols();

// The initial and final Hadamard layer.
let h_layer =
(0..num_qubits).map(|i| (StandardGate::HGate, smallvec![], smallvec![Qubit(i as u32)]));

// The circuit interactions are powers of the CSGate, which is implemented by calling
// the CPhaseGate with angles of Pi/2 times the power. The gate powers are given by the
// upper triangular part of the symmetric ``interactions`` matrix.
let connections = (0..num_qubits).flat_map(move |i| {
(i + 1..num_qubits)
.map(move |j| (j, interactions[(i, j)]))
.filter(move |(_, value)| value % 4 != 0)
.map(move |(j, value)| {
(
StandardGate::CPhaseGate,
smallvec![Param::Float(PI2 * value as f64)],
smallvec![Qubit(i as u32), Qubit(j as u32)],
)
})
});

// The layer of T gates. Again we use the PhaseGate, now with powers of Pi/8. The powers
// are given by the diagonal of the ``interactions`` matrix.
let shifts = (0..num_qubits)
.map(move |i| interactions[(i, i)])
.enumerate()
.filter(|(_, value)| value % 8 != 0)
.map(|(i, value)| {
(
StandardGate::PhaseGate,
smallvec![Param::Float(PI8 * value as f64)],
smallvec![Qubit(i as u32)],
)
});

h_layer
.clone()
.chain(connections)
.chain(shifts)
.chain(h_layer)
}

/// This generates a random symmetric integer matrix with values in [0,7].
fn generate_random_interactions(num_qubits: u32, seed: Option<u64>) -> Array2<i64> {
let num_qubits = num_qubits as usize;
let mut rng = match seed {
Some(seed) => Pcg64Mcg::seed_from_u64(seed),
None => Pcg64Mcg::from_entropy(),
};

let mut mat = Array2::zeros((num_qubits, num_qubits));
for i in 0..num_qubits {
mat[[i, i]] = rng.gen_range(0..8) as i64;
for j in 0..i {
mat[[i, j]] = rng.gen_range(0..8) as i64;
mat[[j, i]] = mat[[i, j]];
}
}
mat
}

/// Returns true if the input matrix is symmetric, otherwise false.
fn check_symmetric(matrix: &ArrayView2<i64>) -> bool {
let nrows = matrix.nrows();

if matrix.ncols() != nrows {
return false;
}

for i in 0..nrows {
for j in i + 1..nrows {
if matrix[(i, j)] != matrix[(j, i)] {
return false;
}
}
}

true
}

/// Implement an Instantaneous Quantum Polynomial time (IQP) circuit.
///
/// This class of circuits is conjectured to be classically hard to simulate,
/// forming a generalization of the Boson sampling problem. See Ref. [1] for
/// more details.
///
/// Args:
/// interactions: If provided, this is a symmetric square matrix of width ``num_qubits``,
/// determining the operations in the IQP circuit. The diagonal represents the power
/// of single-qubit T gates and the upper triangular part the power of CS gates
/// in between qubit pairs. If None, a random interactions matrix will be sampled.
///
/// Returns:
/// The IQP circuit.
///
/// References:
///
/// [1] M. J. Bremner et al. Average-case complexity versus approximate simulation of
/// commuting quantum computations, Phys. Rev. Lett. 117, 080501 (2016).
/// `arXiv:1504.07999 <https://arxiv.org/abs/1504.07999>`_
#[pyfunction]
#[pyo3(signature = (interactions))]
pub fn py_iqp(py: Python, interactions: PyReadonlyArray2<i64>) -> PyResult<CircuitData> {
let array = interactions.as_array();
let view = array.view();
if !check_symmetric(&view) {
return Err(CircuitError::new_err("IQP matrix must be symmetric."));
}

let num_qubits = view.ncols() as u32;
let instructions = iqp(view);
CircuitData::from_standard_gates(py, num_qubits, instructions, Param::Float(0.0))
}

/// Generate a random Instantaneous Quantum Polynomial time (IQP) circuit.
///
/// Args:
/// num_qubits: The number of qubits.
/// seed: A random seed for generating the interactions matrix.
///
/// Returns:
/// A random IQP circuit.
#[pyfunction]
#[pyo3(signature = (num_qubits, seed=None))]
pub fn py_random_iqp(py: Python, num_qubits: u32, seed: Option<u64>) -> PyResult<CircuitData> {
let interactions = generate_random_interactions(num_qubits, seed);
let view = interactions.view();
let instructions = iqp(view);
CircuitData::from_standard_gates(py, num_qubits, instructions, Param::Float(0.0))
}
3 changes: 3 additions & 0 deletions crates/accelerate/src/circuit_library/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
use pyo3::prelude::*;

mod entanglement;
mod iqp;
mod pauli_feature_map;
mod quantum_volume;

pub fn circuit_library(m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(pauli_feature_map::pauli_feature_map))?;
m.add_wrapped(wrap_pyfunction!(entanglement::get_entangler_map))?;
m.add_wrapped(wrap_pyfunction!(iqp::py_iqp))?;
m.add_wrapped(wrap_pyfunction!(iqp::py_random_iqp))?;
m.add_wrapped(wrap_pyfunction!(quantum_volume::quantum_volume))?;
Ok(())
}
1 change: 1 addition & 0 deletions crates/accelerate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ pub fn getenv_use_multiple_threads() -> bool {
}

import_exception!(qiskit.exceptions, QiskitError);
import_exception!(qiskit.circuit.exceptions, CircuitError);
5 changes: 4 additions & 1 deletion qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@
HamiltonianGate
UnitaryOverlap
.. autofunction:: iqp
.. autofunction:: random_iqp
N-local circuits
================
Expand Down Expand Up @@ -571,7 +574,7 @@
from .fourier_checking import FourierChecking
from .graph_state import GraphState
from .hidden_linear_function import HiddenLinearFunction
from .iqp import IQP
from .iqp import IQP, iqp, random_iqp
from .phase_estimation import PhaseEstimation
from .grover_operator import GroverOperator
from .phase_oracle import PhaseOracle
Expand Down
119 changes: 99 additions & 20 deletions qiskit/circuit/library/iqp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"""Instantaneous quantum polynomial circuit."""

from __future__ import annotations
from collections.abc import Sequence

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.exceptions import CircuitError
from qiskit.utils.deprecation import deprecate_func
from qiskit._accelerate.circuit_library import py_iqp, py_random_iqp


class IQP(QuantumCircuit):
Expand Down Expand Up @@ -60,6 +62,11 @@ class IQP(QuantumCircuit):
`arXiv:1504.07999 <https://arxiv.org/abs/1504.07999>`_
"""

@deprecate_func(
since="1.3",
additional_msg="Use the qiskit.circuit.library.iqp function instead.",
pending=True,
)
def __init__(self, interactions: list | np.ndarray) -> None:
"""Create IQP circuit.
Expand All @@ -69,28 +76,100 @@ def __init__(self, interactions: list | np.ndarray) -> None:
Raises:
CircuitError: if the inputs is not as symmetric matrix.
"""
num_qubits = len(interactions)
interactions = np.array(interactions)
if not np.allclose(interactions, interactions.transpose()):
raise CircuitError("The interactions matrix is not symmetric")
circuit = iqp(interactions)
super().__init__(*circuit.qregs, name=circuit.name)
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)

a_str = np.array_str(interactions)
a_str.replace("\n", ";")
name = "iqp:" + a_str.replace("\n", ";")

circuit = QuantumCircuit(num_qubits, name=name)
def iqp(
interactions: Sequence[Sequence[int]],
) -> QuantumCircuit:
r"""Instantaneous quantum polynomial time (IQP) circuit.
circuit.h(range(num_qubits))
for i in range(num_qubits):
for j in range(i + 1, num_qubits):
if interactions[i][j] % 4 != 0:
circuit.cp(interactions[i][j] * np.pi / 2, i, j)
The circuit consists of a column of Hadamard gates, a column of powers of T gates,
a sequence of powers of CS gates (up to :math:`\frac{n^2-n}{2}` of them), and a final column of
Hadamard gates, as introduced in [1].
for i in range(num_qubits):
if interactions[i][i] % 8 != 0:
circuit.p(interactions[i][i] * np.pi / 8, i)
The circuit is parameterized by an :math:`n \times n` interactions matrix. The powers of each
T gate are given by the diagonal elements of the interactions matrix. The powers of the CS gates
are given by the upper triangle of the interactions matrix.
circuit.h(range(num_qubits))
**Reference Circuit:**
super().__init__(*circuit.qregs, name=circuit.name)
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)
.. plot::
from qiskit.circuit.library import iqp
A = [[6, 5, 3], [5, 4, 5], [3, 5, 1]]
circuit = iqp(A)
circuit.draw("mpl")
**Expanded Circuit:**
.. plot::
from qiskit.circuit.library import iqp
from qiskit.visualization.library import _generate_circuit_library_visualization
A = [[6, 5, 3], [5, 4, 5], [3, 5, 1]]
circuit = iqp(A)
_generate_circuit_library_visualization(circuit)
**References:**
[1] M. J. Bremner et al. Average-case complexity versus approximate
simulation of commuting quantum computations,
Phys. Rev. Lett. 117, 080501 (2016).
`arXiv:1504.07999 <https://arxiv.org/abs/1504.07999>`_
Args:
interactions: The interactions as symmetric square matrix. If ``None``, then the
``num_qubits`` argument must be set and a random IQP circuit will be generated.
Returns:
An IQP circuit.
"""
# if no interactions are given, generate them
num_qubits = len(interactions)
interactions = np.asarray(interactions).astype(np.int64)

# set the label -- if the number of qubits is too large, do not show the interactions matrix
if num_qubits < 5 and interactions is not None:
label = np.array_str(interactions)
name = "iqp:" + label.replace("\n", ";")
else:
name = "iqp"

circuit = QuantumCircuit._from_circuit_data(py_iqp(interactions), add_regs=True)
circuit.name = name
return circuit


def random_iqp(
num_qubits: int,
seed: int | None = None,
) -> QuantumCircuit:
r"""A random instantaneous quantum polynomial time (IQP) circuit.
See :func:`iqp` for more details on the IQP circuit.
Example:
.. plot::
:include-source:
from qiskit.circuit.library import random_iqp
circuit = random_iqp(3)
circuit.draw("mpl")
Args:
num_qubits: The number of qubits in the circuit.
seed: A seed for the random number generator, in case the interactions matrix is
randomly generated.
Returns:
An IQP circuit.
"""
# set the label -- if the number of qubits is too large, do not show the interactions matrix
circuit = QuantumCircuit._from_circuit_data(py_random_iqp(num_qubits, seed), add_regs=True)
circuit.name = "iqp"
return circuit
12 changes: 12 additions & 0 deletions releasenotes/notes/iqp-function-6594f7cf1521499c.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
features_circuits:
- |
Added the :func:`~qiskit.circuit.library.iqp` function to construct
Instantaneous Quantum Polynomial time (IQP) circuits. In addition to the
existing :class:`.IQP` class, the function also allows construction of random
IQP circuits::
from qiskit.circuit.library import iqp
random_iqp = iqp(num_qubits=4)
print(random_iqp.draw())
Loading

0 comments on commit 91ceee5

Please sign in to comment.