# Copyright 2021 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Mindspore quantum simulator operator."""
from mindspore.ops.primitive import PrimitiveWithInfer
from mindspore.ops.primitive import prim_attr_register
from mindspore._checkparam import Validator as validator
from mindspore.common import dtype as mstype
from mindspore.ops._grad.grad_base import bprop_getters
import mindspore.ops as P
from mindquantum.circuit import Circuit
from mindquantum.gate import Hamiltonian, IGate
from mindquantum.gate import Projector
from mindquantum.ops import QubitOperator
from mindquantum.utils import count_qubits
from ._check_qnn_input import _check_type_or_iterable_type
from ._check_qnn_input import _check_circuit
from ._check_qnn_input import _check_parameters_of_circuit
[docs]class PQC(PrimitiveWithInfer):
r"""
Evaluate a parameterized quantum circuit and calculate the gradient of each parameters.
Inputs of this operation is generated by MindQuantum framework.
Inputs:
- **n_qubits** (int) - The qubit number of quantum simulator.
- **encoder_params_names** (list[str]) - The parameters names of encoder circuit.
- **ansatz_params_names** (list[str]) - The parameters names of ansatz circuit.
- **gate_names** (list[str]) - The name of each gate.
- **gate_matrix** (list[list[list[list[float]]]]) - Real part and image part of the matrix of quantum gate.
- **gate_obj_qubits** (list[list[int]]) - Object qubits of each gate.
- **gate_ctrl_qubits** (list[list[int]]) - Control qubits of each gate.
- **gate_params_names** (list[list[str]]) - Parameter names of each gate.
- **gate_coeff** (list[list[float]]) - Coefficient of eqch parameter of each gate.
- **gate_requires_grad** (list[list[bool]]) - Whether to calculate gradient of parameters of gates.
- **hams_pauli_coeff** (list[list[float]]) - Coefficient of pauli words.
- **hams_pauli_word** (list[list[list[str]]]) - Pauli words.
- **hams_pauli_qubit** (list[list[list[int]]]) - The qubit that pauli matrix act on.
- **is_projector** (bool) - Whether the measurement operator is a subspace bitstring measurement.
- **projectors** (Union[str, list[str]]) - The projector bitstrings.
- **n_threads** (int) - Thread to evaluate input data.
Outputs:
- **expected_value** (Tensor) - The expected value of hamiltonian.
- **g1** (Tensor) - Gradient of encode circuit parameters.
- **g2** (Tensor) - Gradient of ansatz circuit parameters.
Supported Platforms:
``CPU``
"""
@prim_attr_register
def __init__(self, n_qubits, encoder_params_names, ansatz_params_names,
gate_names, gate_matrix, gate_obj_qubits, gate_ctrl_qubits,
gate_params_names, gate_coeff, gate_requires_grad,
hams_pauli_coeff, hams_pauli_word, hams_pauli_qubit,
is_projector, projectors, n_threads):
"""Initialize PQC"""
self.init_prim_io_names(
inputs=['encoder_data', 'ansatz_data'],
outputs=['results', 'encoder_gradient', 'ansatz_gradient'])
if is_projector:
self.n_meas = len(projectors)
else:
self.n_meas = len(hams_pauli_coeff)
[docs] def check_shape_size(self, encoder_data, ansatz_data):
"""check shape size"""
if len(encoder_data) != 2:
raise ValueError(
"PQC input encoder_data should have dimension size \
equal to 2, but got {}.".format(len(encoder_data)))
if len(ansatz_data) != 1:
raise ValueError(
"PQC input ansatz_data should have dimension size \
equal to 1, but got {}.".format(len(ansatz_data)))
if encoder_data[1] != len(self.encoder_params_names):
raise ValueError(
f'Input encoder_data length {encoder_data[1]} \
mismatch with circuit encoder parameter number {len(self.encoder_params_names)}')
if ansatz_data[0] != len(self.ansatz_params_names):
raise ValueError(
f'Input ansatz_data length {ansatz_data[1]} \
mismatch with circuit ansatz parameter number {len(self.ansatz_params_names)}')
def infer_shape(self, encoder_data, ansatz_data):
self.check_shape_size(encoder_data, ansatz_data)
return [encoder_data[0], self.n_meas], [
encoder_data[0], self.n_meas,
len(self.encoder_params_names)
], [encoder_data[0], self.n_meas,
len(self.ansatz_params_names)]
def infer_dtype(self, encoder_data, ansatz_data):
args = {'encoder_data': encoder_data, 'ansatz_data': ansatz_data}
validator.check_tensors_dtypes_same_and_valid(args, mstype.float_type,
self.name)
return encoder_data, encoder_data, encoder_data
@bprop_getters.register(PQC)
def bprop_pqc(self):
"""Grad definition for `PQC` operation."""
t = P.Transpose()
mul = P.Mul()
sum_ = P.ReduceSum()
def bprop(encoder_data, ansatz_data, out, dout):
dx = t(out[1], (2, 0, 1))
dx = mul(dout[0], dx)
dx = sum_(dx, 2)
dx = t(dx, (1, 0))
dy = P.tensor_dot(dout[0], out[2], ((0, 1), (0, 1)))
return dx, dy
return bprop
[docs]def generate_pqc_operator(encoder_params_names,
ansatz_params_names,
circuit: Circuit,
measurements,
n_threads=1):
"""
A method to generate a parameterized quantum circuit simulation operator.
Args:
encoder_params_names (list[str]): The list of parameter names for
encoder circuit.
ansatz_params_names (list[str]): The list of parameter names for ansatz
circuit.
circuit (Circuit): The whole circuit combined with
encoder circuit and ansatz circuit.
measurements (Union[Hamiltonian, list[Hamiltonian], Projector, list[Projector]]): The measurement
operators, would be hamiltonians or projectors.
n_threads (int): Number of threads for data parallelize. Default: 1.
Returns:
PQC, A parameterized quantum circuit simulator operator supported by mindspore framework.
Examples:
>>> from mindquantum.ops import QubitOperator
>>> from mindquantum import Circuit
>>> import mindquantum.gate as G
>>> from mindquantum.nn import generate_pqc_operator
>>> encoder_circ = Circuit([G.RX('a').on(0)])
>>> ansatz_circ = Circuit([G.RY('b').on(0)])
>>> circ = encoder_circ + ansatz_circ
>>> ham = G.Hamiltonian(QubitOperator('Z0'))
>>> pqc = generate_pqc_operator(['a'], ['b'], circ, ham)
"""
_check_circuit(circuit, 'Circuit')
_check_type_or_iterable_type(measurements, (Hamiltonian, Projector),
'Hamiltonian or Projector')
_check_parameters_of_circuit(encoder_params_names, ansatz_params_names,
circuit)
if not isinstance(n_threads, int) or n_threads <= 0:
raise TypeError(
"n_threads requires a positive int, but get {}".format(n_threads))
is_projector = False
if isinstance(measurements, (Projector, Hamiltonian)):
measurements = [measurements]
hams = [Hamiltonian(QubitOperator())]
pros = [Projector('')]
if isinstance(measurements[0], Hamiltonian):
hams = measurements
n_qubits_ham = count_qubits(hams[0].hamiltonian)
if n_qubits_ham > circuit.n_qubits:
circuit += IGate().on(n_qubits_ham - 1)
if isinstance(measurements[0], Projector):
for pro in measurements:
if pro.n_qubits != circuit.n_qubits:
raise ValueError(
f"The qubit of projector {pro} not match with quantum circuit."
)
is_projector = True
pros = measurements
ham_ms_data = {}
for ham in hams:
for k, v in ham.mindspore_data().items():
if k not in ham_ms_data:
ham_ms_data[k] = [v]
else:
ham_ms_data[k].append(v)
proj_ms_data = {}
for pro in pros:
for k, v in pro.mindspore_data().items():
if k not in proj_ms_data:
proj_ms_data[k] = [v]
else:
proj_ms_data[k].append(v)
return PQC(circuit.n_qubits,
encoder_params_names=encoder_params_names,
ansatz_params_names=ansatz_params_names,
**circuit.mindspore_data(),
**ham_ms_data,
is_projector=is_projector,
**proj_ms_data,
n_threads=n_threads)