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

Approximate Quantum Compiler #6727

Merged
merged 130 commits into from
Nov 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
8698317
initial version
adekusar-drl Jun 4, 2021
f07fd5c
linting
adekusar-drl Jun 11, 2021
71115ac
linting
adekusar-drl Jun 14, 2021
f2f57f0
linting and black
adekusar-drl Jun 14, 2021
2a80c61
linting
adekusar-drl Jun 14, 2021
576e0fe
linting
adekusar-drl Jun 14, 2021
5899b84
linting
adekusar-drl Jun 16, 2021
7ffaa3b
linting
adekusar-drl Jun 16, 2021
5cd18dc
linting
adekusar-drl Jun 17, 2021
6b17eee
linting
adekusar-drl Jun 18, 2021
8ef89c9
linting
adekusar-drl Jun 22, 2021
3c0e979
linting
adekusar-drl Jun 23, 2021
a8af3e2
linting
adekusar-drl Jun 24, 2021
eca564b
linting
adekusar-drl Jun 24, 2021
c386858
linting
adekusar-drl Jun 24, 2021
c8d975c
linting
adekusar-drl Jun 25, 2021
913810f
linting
adekusar-drl Jun 28, 2021
635c5cd
linting
adekusar-drl Jun 28, 2021
f13228f
linting
adekusar-drl Jun 28, 2021
ad2efca
linting
adekusar-drl Jun 28, 2021
29f7331
hello
liammadden Jun 28, 2021
01fcc5b
linting
adekusar-drl Jun 28, 2021
46d1470
remove debugging
adekusar-drl Jun 28, 2021
2d4f2b2
changed variable names, defined d, and moved -1j/2 multiply to the end
liammadden Jun 28, 2021
158e3b9
Merge remote-tracking branch 'origin/aqc' into aqc
liammadden Jun 28, 2021
ae1597e
linting
adekusar-drl Jun 28, 2021
e4e5492
linting
adekusar-drl Jun 28, 2021
bc4652b
Merge remote-tracking branch 'origin/aqc' into aqc
liammadden Jun 29, 2021
27f9495
checking
liammadden Jun 29, 2021
034586f
changed variable names
liammadden Jun 29, 2021
8d8d443
corrected typo
liammadden Jun 29, 2021
e78c110
corrected typo
liammadden Jun 29, 2021
ce408da
commented
liammadden Jun 29, 2021
6a3c014
typo
liammadden Jun 29, 2021
a3f1e89
typo
liammadden Jun 29, 2021
5f2149e
check
liammadden Jun 29, 2021
38daa81
typo
liammadden Jun 29, 2021
bb74b19
linting
adekusar-drl Jun 29, 2021
fba74f4
changed variable names and commented
liammadden Jun 29, 2021
c7ccf59
Merge remote-tracking branch 'origin/aqc' into aqc
liammadden Jun 29, 2021
8a3b6a4
linting
adekusar-drl Jun 30, 2021
f03b72c
changed variable names and commented
liammadden Jul 2, 2021
6677115
cleaning mvp
adekusar-drl Jul 9, 2021
481a381
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Jul 9, 2021
ca6e0d5
cleaning mvp
adekusar-drl Jul 9, 2021
2f29d14
cleaning mvp
adekusar-drl Jul 12, 2021
a379af1
added plugin
adekusar-drl Jul 12, 2021
9965269
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Jul 12, 2021
f0cb216
Merge branch 'main' into mvp
adekusar-drl Jul 12, 2021
bfe1105
Merge branch 'main' into mvp
adekusar-drl Jul 13, 2021
0fc591b
Merge branch 'main' into mvp
adekusar-drl Jul 13, 2021
f8f083f
Merge branch 'main' into mvp
adekusar-drl Jul 13, 2021
5f3d15a
Merge branch 'main' into mvp
adekusar-drl Jul 16, 2021
885d8be
Merge branch 'main' into mvp
adekusar-drl Jul 19, 2021
fa91883
Merge branch 'main' into mvp
adekusar-drl Jul 19, 2021
76a40f5
Merge branch 'main' into mvp
adekusar-drl Jul 20, 2021
fd13158
Merge branch 'main' into mvp
adekusar-drl Jul 21, 2021
59084ff
Merge branch 'main' into mvp
adekusar-drl Jul 22, 2021
466afcb
Merge branch 'main' into mvp
adekusar-drl Jul 22, 2021
f5bc4f8
Merge branch 'main' into mvp
adekusar-drl Jul 23, 2021
599cd3c
Merge branch 'main' into mvp
adekusar-drl Jul 23, 2021
8a171ab
Merge branch 'main' into mvp
adekusar-drl Jul 26, 2021
058dde4
Merge branch 'main' into mvp
adekusar-drl Jul 26, 2021
0896573
Merge branch 'main' into mvp
adekusar-drl Jul 27, 2021
441b305
Merge branch 'main' into mvp
adekusar-drl Jul 27, 2021
eea8cc1
Merge branch 'main' into mvp
adekusar-drl Jul 28, 2021
80f7663
Merge branch 'main' into mvp
adekusar-drl Jul 29, 2021
6b81031
Merge branch 'main' into mvp
adekusar-drl Jul 30, 2021
73c8b98
Merge branch 'main' into mvp
adekusar-drl Jul 30, 2021
b9e41db
Merge branch 'main' into mvp
adekusar-drl Aug 1, 2021
9fa1714
Merge branch 'main' into mvp
adekusar-drl Aug 4, 2021
5394217
Merge branch 'main' into mvp
adekusar-drl Aug 9, 2021
a0fba07
Merge branch 'main' into mvp
adekusar-drl Aug 10, 2021
79852ea
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Aug 19, 2021
ee6028a
Merge branch 'main' into mvp
adekusar-drl Aug 19, 2021
e154e77
Merge branch 'main' into mvp
1ucian0 Aug 23, 2021
12584f9
Merge branch 'mvp' of https://github.com/adekusar-drl/qiskit-terra in…
adekusar-drl Aug 23, 2021
008a4b2
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Sep 12, 2021
ba6e9b4
code review
adekusar-drl Sep 12, 2021
1c5a22f
fix types in cnot_structures.py
adekusar-drl Sep 12, 2021
55ef2d7
Merge branch 'main' into mvp
adekusar-drl Sep 15, 2021
9f372fc
more on documentation
adekusar-drl Sep 16, 2021
76be97a
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Sep 16, 2021
ce4952e
Merge branch 'main' into mvp
adekusar-drl Sep 16, 2021
a07eea6
Update qiskit/transpiler/synthesis/aqc/approximate.py
1ucian0 Sep 23, 2021
6eed3ad
Update qiskit/transpiler/synthesis/aqc/cnot_unit_objective.py
adekusar-drl Oct 1, 2021
ee71e94
Update test/python/transpiler/aqc/sample_data.py
adekusar-drl Oct 1, 2021
f71f8c6
code review
adekusar-drl Oct 1, 2021
3168a92
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Oct 1, 2021
2a06bf6
Merge branch 'main' into mvp
adekusar-drl Oct 2, 2021
b79245f
fix cache
adekusar-drl Oct 11, 2021
37857b6
updated plugin, more on documentation
adekusar-drl Oct 13, 2021
45302f8
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Oct 13, 2021
7e7aab1
Merge branch 'main' into mvp
adekusar-drl Oct 13, 2021
99a687f
restructured documentation
adekusar-drl Oct 13, 2021
1f8300a
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Oct 13, 2021
7570021
Merge branch 'main' into mvp
adekusar-drl Oct 13, 2021
f970bd4
default configuration, aqc plugin setup
adekusar-drl Oct 14, 2021
60eb8b1
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Oct 14, 2021
1e5ffc7
added reno
adekusar-drl Oct 14, 2021
f7fcc23
Merge branch 'main' into mvp
adekusar-drl Oct 14, 2021
86451e6
Merge branch 'main' into mvp
adekusar-drl Oct 14, 2021
5ed7045
Update setup.py
adekusar-drl Oct 18, 2021
1fc9923
added a test where AQC is invoked via the plugin discovery interface
adekusar-drl Oct 18, 2021
472ae2f
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Oct 18, 2021
fab57ff
Merge branch 'main' into mvp
adekusar-drl Oct 18, 2021
f897673
Merge branch 'main' into mvp
adekusar-drl Oct 19, 2021
41cbad9
Merge branch 'main' into mvp
adekusar-drl Oct 20, 2021
cb7587f
Merge branch 'main' into mvp
adekusar-drl Oct 21, 2021
dd5fd05
Merge branch 'main' into mvp
adekusar-drl Oct 21, 2021
da3bc91
Merge branch 'main' into mvp
adekusar-drl Oct 26, 2021
12c7d13
Merge branch 'main' into mvp
adekusar-drl Oct 26, 2021
edf039c
added a test where AQC is invoked via pass manager
adekusar-drl Oct 26, 2021
6ea3a23
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Oct 26, 2021
72c4209
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Oct 26, 2021
c9f6aa3
Merge branch 'main' into mvp
adekusar-drl Oct 27, 2021
263b7ec
Merge branch 'main' into mvp
adekusar-drl Oct 28, 2021
e7916c6
Merge branch 'main' into mvp
adekusar-drl Nov 1, 2021
e45fb19
update scipy in requirements.txt
adekusar-drl Nov 2, 2021
d8ffcc4
Merge remote-tracking branch 'origin/mvp' into mvp
adekusar-drl Nov 2, 2021
a866ef8
Merge branch 'main' of https://github.com/Qiskit/qiskit-terra into mvp
adekusar-drl Nov 2, 2021
ab60db6
Merge branch 'main' into mvp
adekusar-drl Nov 6, 2021
7e184b3
Merge branch 'main' into mvp
adekusar-drl Nov 9, 2021
8d92cf7
Merge branch 'main' into mvp
adekusar-drl Nov 11, 2021
9e8528e
Update qiskit/transpiler/synthesis/aqc/aqc.py
adekusar-drl Nov 16, 2021
554fb59
Merge branch 'main' into mvp
adekusar-drl Nov 16, 2021
ac5aaee
added tests for cnot networks
adekusar-drl Nov 16, 2021
dd100dd
added support for plugin configuration
adekusar-drl Nov 16, 2021
f1bbad9
Merge branch 'main' into mvp
adekusar-drl Nov 16, 2021
408168c
Merge branch 'main' into mvp
adekusar-drl Nov 23, 2021
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
133 changes: 133 additions & 0 deletions qiskit/transpiler/synthesis/aqc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.
r"""
Implementation of Approximate Quantum Compiler as described in the paper [1].

We are interested in compiling a quantum circuit, which we formalize as finding the best
circuit representation in terms of an ordered gate sequence of a target unitary matrix
:math:`U\in U(d)`, with some additional hardware constraints. In particular, we look at
representations that could be constrained in terms of hardware connectivity, as well
as gate depth, and we choose a gate basis in terms of CNOT and rotation gates.
We recall that the combination of CNOT and rotation gates is universal in :math:`SU(d)` and
therefore it does not limit compilation.

To properly define what we mean by best circuit representation, we define the metric
as the Frobenius norm between the unitary matrix of the compiled circuit :math:`V` and
the target unitary matrix :math:`U`, i.e., :math:`\|V - U\|_{\mathrm{F}}`. This choice
is motivated by mathematical programming considerations, and it is related to other
formulations that appear in the literature. Let's take a look at the problem in more details.

Let :math:`n` be the number of qubits and :math:`d=2^n`. Given a CNOT structure :math:`ct`
and a vector of rotation angles :math:`\theta`, the parametric circuit forms a matrix
:math:`Vct(\theta)\in SU(d)`. If we are given a target circuit forming a matrix
:math:`U\in SU(d)`, then we would like to compute

.. math::

argmax_{\theta}\frac{1}{d}|\langle Vct(\theta),U\rangle|

where the inner product is the Frobenius inner product. Note that
:math:`|\langle V,U\rangle|\leq d` for all unitaries :math:`U` and :math:`V`, so the objective
has range in :math:`[0,1]`.

Our strategy is to maximize

.. math::

\frac{1}{d}\Re \langle Vct(\theta),U\rangle

using its gradient. We will now discuss the specifics by going through an example.

While the range of :math:`Vct` is a subset of :math:`SU(d)` by construction, the target
circuit may form a general unitary matrix. However, for any :math:`U\in U(d)`,

.. math::

\frac{\exp(2\pi i k/d)}{\det(U)^{1/d}}U\in SU(d)\text{ for all }k\in\{0,\ldots,d-1\}.

Thus, we should normalize the target circuit by its global phase and then approximately
compile the normalized circuit. We can add the global phase back in afterwards.

In the algorithm let :math:`U'` denote the un-normalized target matrix and :math:`U`
the normalized target matrix. Now that we have :math:`U`, we give the gradient function
to the Nesterov's method optimizer and compute :math:`\theta`.

To add the global phase back in, we can form the control circuit as

.. math::

\frac{\langle Vct(\theta),U'\rangle}{|\langle Vct(\theta),U'\rangle|}Vct(\theta).

Note that while we optimized using Nesterov's method in the paper, this was for its convergence
guarantees, not its speed in practice. It is much faster to use L-BFGS which is used as a
default optimizer in this implementation.

A basic usage of the AQC algorithm should consist of the following steps::

# Define a target circuit as a unitary matrix
unitary = ...

# Define a number of qubits for the algorithm, at least 3 qubits
num_qubits = int(round(np.log2(unitary.shape[0])))

# Choose a layout of the CNOT structure for the approximate circuit, e.g. ``spin`` for
# a linear layout.
layout = options.get("layout") or "spin"

# Choose a connectivity type, e.g. ``full`` for full connectivity between qubits.
connectivity = options.get("connectivity") or "full"

# Define a targeted depth of the approximate circuit in the number of CNOT units.
depth = int(options.get("depth") or 0)

# Generate a network made of CNOT units
cnots = make_cnot_network(
num_qubits=num_qubits,
network_layout=layout,
connectivity_type=connectivity,
depth=depth,
)

# Create an optimizer to be used by AQC
optimizer = L_BFGS_B()

# Create an instance
aqc = AQC(optimizer)

# Create a template circuit that will approximate our target circuit
approximate_circuit = CNOTUnitCircuit(num_qubits=num_qubits, cnots=cnots)

# Create an objective that defines our optimization problem
approximating_objective = DefaultCNOTUnitObjective(num_qubits=num_qubits, cnots=cnots)

# Run optimization process to compile the unitary
aqc.compile_unitary(
target_matrix=unitary,
approximate_circuit=approximate_circuit,
approximating_objective=approximating_objective,
)

Now ``approximate_circuit`` is a circuit that approximates the target unitary to a certain
degree and can be used instead of the original matrix.

References:

[1]: Liam Madden, Andrea Simonetto, Best Approximate Quantum Compiling Problems.
`arXiv:2106.05649 <https://arxiv.org/abs/2106.05649>`_
"""

from .approximate import ApproximateCircuit, ApproximatingObjective
from .aqc import AQC
from .aqc_plugin import AQCSynthesisPlugin
from .cnot_structures import make_cnot_network
from .cnot_unit_circuit import CNOTUnitCircuit
from .cnot_unit_objective import CNOTUnitObjective, DefaultCNOTUnitObjective
1ucian0 marked this conversation as resolved.
Show resolved Hide resolved
116 changes: 116 additions & 0 deletions qiskit/transpiler/synthesis/aqc/approximate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.
"""Base classes for an approximate circuit definition."""

from abc import ABC, abstractmethod
from typing import Optional
import numpy as np

from qiskit import QuantumCircuit


class ApproximateCircuit(QuantumCircuit, ABC):
"""A base class that represents an approximate circuit."""

def __init__(self, num_qubits: int, name: Optional[str] = None) -> None:
"""
Args:
num_qubits: number of qubit this circuit will span.
name: a name of the circuit.
"""
super().__init__(num_qubits, name=name)

@property
@abstractmethod
def thetas(self) -> np.ndarray:
"""
The property is not implemented and raises a ``NotImplementedException`` exception.

Returns:
a vector of parameters of this circuit.
"""
raise NotImplementedError

@abstractmethod
def build(self, thetas: np.ndarray) -> None:
"""
Constructs this circuit out of the parameters(thetas). Parameter values must be set before
constructing the circuit.

Args:
thetas: a vector of parameters to be set in this circuit.
"""
raise NotImplementedError


class ApproximatingObjective(ABC):
"""
A base class for an optimization problem definition. An implementing class must provide at least
an implementation of the ``objective`` method. In such case only gradient free optimizers can
be used. Both method, ``objective`` and ``gradient``, preferable to have in an implementation.
"""

def __init__(self) -> None:
# must be set before optimization
self._target_matrix = None
Comment on lines +62 to +64
Copy link
Member

@1ucian0 1ucian0 Sep 23, 2021

Choose a reason for hiding this comment

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

Im not fully sure why target_matrix cannot be set at construction time. compile_unitary is called like this

        approximating_objective = DefaultCNOTUnitObjective(num_qubits, cnots)

        aqc.compile_unitary(
            target_matrix=unitary,
            approximate_circuit=approximate_circuit,
            approximating_objective=approximating_objective,
        )

Why wouldnt be possible to do something like this?

        approximating_objective = DefaultCNOTUnitObjective(num_qubits, cnots, unitary)

       aqc.compile_unitary(
           target_matrix=unitary,
           approximate_circuit=approximate_circuit,
           approximating_objective=approximating_objective,
       )

In this way, you avoid the dependency set target_matrix before calling objective/gradient.

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 also a little puzzled. I appreciate that it's often cheaper to re-use instances of numerical optimizers across calls with different objective parameters, and so I understand the impulse to defer setting the objective / allowing it to be mutable. Continuing that thought, I'd want to defer setting the target all the way up until I actually ask for optimization to happen. What's gained by saving it as part of the optimizer's state, available before and after optimization completes? Why not supply it only through the .compile_unitary call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@1ucian0 It won't work this way. At least in the current design. Now, you pass an objective, a template circuit and a unitary to be optimized in the aqc.compile_unitary() call. The problem is that the unitary passed is may be updated in AQC, now we normalize it before starting off optimization. So, there should be a way to update the matrix in the objective. OK, another option here is to create an objective, a template inside of aqc.compile_unitary(). But then you loose flexibility of passing a custom objective/template.

@ecpeterson Sorry, I don't fully understand your comment. The target matrix is set just before optimization starts. Everything is as stateless as possible and deferred till the latest stages.


@abstractmethod
def objective(self, param_values: np.ndarray) -> float:
"""
Computes a value of the objective function given a vector of parameter values.

Args:
param_values: a vector of parameter values for the optimization problem.

Returns:
a float value of the objective function.
"""
raise NotImplementedError

@abstractmethod
def gradient(self, param_values: np.ndarray) -> np.ndarray:
"""
Computes a gradient with respect to parameters given a vector of parameter values.

Args:
param_values: a vector of parameter values for the optimization problem.

Returns:
an array of gradient values.
"""
raise NotImplementedError

@property
def target_matrix(self) -> np.ndarray:
"""
Returns:
a matrix being approximated
"""
return self._target_matrix

@target_matrix.setter
def target_matrix(self, target_matrix: np.ndarray) -> None:
"""
Args:
target_matrix: a matrix to approximate in the optimization procedure.
"""
self._target_matrix = target_matrix

@property
@abstractmethod
def num_thetas(self) -> int:
"""

Returns:
the number of parameters in this optimization problem.
"""
raise NotImplementedError
108 changes: 108 additions & 0 deletions qiskit/transpiler/synthesis/aqc/aqc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.
"""A generic implementation of Approximate Quantum Compiler."""
from typing import Optional

import numpy as np

from qiskit.algorithms.optimizers import L_BFGS_B, Optimizer
from qiskit.quantum_info import Operator
from .approximate import ApproximateCircuit, ApproximatingObjective


class AQC:
"""
A generic implementation of Approximate Quantum Compiler. This implementation is agnostic of
the underlying implementation of the approximate circuit, objective, and optimizer. Users may
pass corresponding implementations of the abstract classes:

* Optimizer is an instance of :class:`~qiskit.algorithms.optimizer.Optimizer` and
used to run the optimization process. A choice of optimizer may affect overall
convergence, required time for the optimization process and achieved objective value.
* Approximate circuit represents a template which parameters we want to optimize.
Currently, there's only one implementation based on 4-rotations CNOT unit blocks:
:class:`~qiskit.transpiler.aqc.CNOTUnitCircuit`. See the paper for more details.
* Approximate objective is tightly coupled with the approximate circuit implementation
and provides two methods for computing objective function and gradient with respect to
approximate circuit parameters. This objective is passed to the optimizer. Currently,
there's only one implementation based on 4-rotations CNOT unit blocks:
:class:`~qiskit.transpiler.aqc.DefaultCNOTUnitObjective`. This is a naive implementation
of the objective function and gradient and may suffer from performance issues.
"""

def __init__(
self,
optimizer: Optional[Optimizer] = None,
seed: Optional[int] = None,
):
"""
Args:
optimizer: an optimizer to be used in the optimization procedure of the search for
the best approximate circuit. By default ``L_BFGS_B`` is used with max iterations
is set to 1000.
seed: a seed value to be user by a random number generator.
"""
super().__init__()
self._optimizer = optimizer
self._seed = seed

def compile_unitary(
self,
target_matrix: np.ndarray,
approximate_circuit: ApproximateCircuit,
approximating_objective: ApproximatingObjective,
initial_point: Optional[np.ndarray] = None,
) -> None:
"""
Approximately compiles a circuit represented as a unitary matrix by solving an optimization
problem defined by ``approximating_objective`` and using ``approximate_circuit`` as a
template for the approximate circuit.

Args:
target_matrix: a unitary matrix to approximate.
approximate_circuit: a template circuit that will be filled with the parameter values
obtained in the optimization procedure.
approximating_objective: a definition of the optimization problem.
initial_point: initial values of angles/parameters to start optimization from.
"""
matrix_dim = target_matrix.shape[0]
# check if it is actually a special unitary matrix
target_det = np.linalg.det(target_matrix)
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 not sure that this is correctly handling global phase: even among operators U whose determinant is normalized to 1, each still has four representatives up to phase: {U, 1j U, -U, -1j U}. Maybe it would be better to add global_phase as another variable over which to optimize? Since it commutes with any supplied optimization template, it should be possible to extend a user's supplied definition of the template gradient with a gradient which also accounts for this extra factor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right, there are many mapping SU(d) -> U(d) in this case and in overall we may end up with a sub-optimal solution instead of a optimal one. Currently, it is a limitation of the algorithm as initially we started from SU matrices. We are studying the ways how to get rid of global phase dependency and it may include consideration of other cost functions, e.g. a function that does not depend on global phase. This question definitely requires thoughtful exploration of various possibilities and we will address it in the next release.

if not np.isclose(target_det, 1):
su_matrix = target_matrix / np.power(target_det, (1 / matrix_dim))
global_phase_required = True
else:
su_matrix = target_matrix
global_phase_required = False

# set the matrix to approximate in the algorithm
approximating_objective.target_matrix = su_matrix

optimizer = self._optimizer or L_BFGS_B(maxiter=1000)

if initial_point is None:
np.random.seed(self._seed)
initial_point = np.random.uniform(0, 2 * np.pi, approximating_objective.num_thetas)

opt_result = optimizer.minimize(
fun=approximating_objective.objective,
x0=initial_point,
jac=approximating_objective.gradient,
)

approximate_circuit.build(opt_result.x)

approx_matrix = Operator(approximate_circuit).data

if global_phase_required:
alpha = np.angle(np.trace(np.dot(approx_matrix.conj().T, target_matrix)))
approximate_circuit.global_phase = alpha
Loading