# 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.
# ============================================================================
"""Simulator."""
import warnings
import numpy as np
import mindquantum.mqbackend as mb
from ..core.circuit import Circuit
from ..core.gates import BarrierGate, BasicGate, Measure, MeasureResult
from ..core.operators import Hamiltonian
from ..core.operators.hamiltonian import HowTo
from ..core.parameterresolver import ParameterResolver
from ..utils.string_utils import ket_string
from ..utils.type_value_check import (
_check_and_generate_pr_type,
_check_input_type,
_check_int_type,
_check_seed,
_check_value_should_not_less,
)
SUPPORTED_SIMULATOR = ['projectq']
[文档]def get_supported_simulator():
"""
Get simulator name that supported by MindQuantum.
Returns:
list, The supported simulator list.
"""
return SUPPORTED_SIMULATOR
[文档]class Simulator:
"""
Quantum simulator that simulate quantum circuit.
Args:
backend (str): which backend you want. The supported backend can be found
in SUPPORTED_SIMULATOR
n_qubits (int): number of quantum simulator.
seed (int): the random seed for this simulator, if None, seed will generate
by `numpy.random.randint`. Default: None.
Raises:
TypeError: if `backend` is not str.
TypeError: if `n_qubits` is not int.
TypeError: if `seed` is not int.
ValueError: if `backend` is not supported.
ValueError: if `n_qubits` is negative.
ValueError: if `seed` is less than 0 or great than 2**23 - 1.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 2)
>>> sim.apply_circuit(qft(range(2)))
>>> sim.get_qs()
array([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j])
"""
def __init__(self, backend, n_qubits, seed=None):
"""Initialize a Simulator object."""
_check_input_type('backend', str, backend)
_check_int_type('n_qubits', n_qubits)
_check_value_should_not_less('n_qubits', 0, n_qubits)
if seed is None:
seed = np.random.randint(1, 2**23)
_check_seed(seed)
if backend not in SUPPORTED_SIMULATOR:
raise ValueError(f"backend {backend} not supported, now we support {SUPPORTED_SIMULATOR}!")
self.backend = backend
self.seed = seed
self.n_qubits = n_qubits
if backend == 'projectq':
self.sim = mb.projectq(seed, n_qubits)
[文档] def copy(self):
"""
Copy this simulator.
Returns:
Simulator, a copy version of this simulator.
Examples:
>>> from mindquantum.core.gates import RX
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 1)
>>> sim.apply_gate(RX(1).on(0))
>>> sim.flush()
>>> sim2 = sim.copy()
>>> sim2.apply_gate(RX(-1).on(0))
>>> sim2
projectq simulator with 1 qubit (little endian).
Current quantum state:
1¦0⟩
"""
sim = Simulator(self.backend, self.n_qubits, self.seed)
sim.sim = self.sim.copy()
return sim
def __str__(self):
"""Return a string representation of the object."""
state = self.get_qs()
ret = f"{self.backend} simulator with {self.n_qubits} qubit{'s' if self.n_qubits > 1 else ''} (little endian)."
ret += "\nCurrent quantum state:\n"
if self.n_qubits < 4:
ret += '\n'.join(ket_string(state))
else:
ret += state.__str__()
return ret
def __repr__(self):
"""Return a string representation of the object."""
return self.__str__()
[文档] def reset(self):
"""
Reset simulator to zero state.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 2)
>>> sim.apply_circuit(qft(range(2)))
>>> sim.reset()
>>> sim.get_qs()
array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j])
"""
self.sim.reset()
[文档] def flush(self):
"""
Flush gate that works for projectq simulator.
The projectq simulator will cache several gate and fushion these gate into a bigger gate, and than act on the
quantum state. The flush command will ask the simulator to fushion currently stored gate and act on the quantum
state.
Examples:
>>> from mindquantum.core.gates import H
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 1)
>>> sim.apply_gate(H.on(0))
>>> sim.flush()
"""
if self.backend == 'projectq':
self.sim.run()
[文档] def apply_gate(self, gate, pr=None, diff=False):
"""
Apply a gate on this simulator, can be a quantum gate or a measurement operator.
Args:
gate (BasicGate): The gate you want to apply.
pr (Union[numbers.Number, numpy.ndarray, ParameterResolver, list]): The
parameter for parameterized gate. Default: None.
diff (bool): Whether to apply the derivative gate on this simulator. Default: False.
Returns:
int or None, if the gate if a measure gate, then return a collapsed state, Otherwise
return None.
Raises:
TypeError: if `gate` is not a BasicGate.
ValueError: if any qubit of `gate` is higher than simulator qubits.
ValueError: if `gate` is parameterized, but no parameter supplied.
TypeError: the `pr` is not a ParameterResolver if `gate` is parameterized.
Examples:
>>> import numpy as np
>>> from mindquantum.core.gates import RY, Measure
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 1)
>>> sim.apply_gate(RY('a').on(0), np.pi/2)
>>> sim.get_qs()
array([0.70710678+0.j, 0.70710678+0.j])
>>> sim.apply_gate(Measure().on(0))
1
>>> sim.get_qs()
array([0.+0.j, 1.+0.j])
"""
_check_input_type('gate', BasicGate, gate)
if not isinstance(gate, BarrierGate):
gate_max = max(max(gate.obj_qubits, gate.ctrl_qubits))
if self.n_qubits < gate_max:
raise ValueError(f"qubits of gate {gate} is higher than simulator qubits.")
if isinstance(gate, Measure):
return self.sim.apply_measure(gate.get_cpp_obj())
if pr is None:
if gate.parameterized:
raise ValueError("apply a parameterized gate needs a parameter_resolver")
self.sim.apply_gate(gate.get_cpp_obj())
else:
pr = _check_and_generate_pr_type(pr, gate.coeff.params_name)
self.sim.apply_gate(gate.get_cpp_obj(), pr.get_cpp_obj(), diff)
return None
[文档] def apply_circuit(self, circuit, pr=None):
"""
Apply a circuit on this simulator.
Args:
circuit (Circuit): The quantum circuit you want to apply on this simulator.
pr (Union[ParameterResolver, dict, numpy.ndarray, list, numbers.Number]): The
parameter resolver for this circuit. If the circuit is not parameterized,
this arg should be None. Default: None.
Returns:
MeasureResult or None, if the circuit has measure gate, then return a MeasureResult,
otherwise return None.
Examples:
>>> import numpy as np
>>> from mindquantum.core.circuit import Circuit
>>> from mindquantum.core.gates import H
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 2)
>>> sim.apply_circuit(Circuit().un(H, 2))
>>> sim.apply_circuit(Circuit().ry('a', 0).ry('b', 1), np.array([1.1, 2.2]))
>>> sim
projectq simulator with 2 qubits (little endian).
Current quantum state:
-0.0721702531972066¦00⟩
-0.30090405886869676¦01⟩
0.22178317006196263¦10⟩
0.9246947752567126¦11⟩
>>> sim.apply_circuit(Circuit().measure(0).measure(1))
shots: 1
Keys: q1 q0│0.00 0.2 0.4 0.6 0.8 1.0
───────────┼───────────┴───────────┴───────────┴───────────┴───────────┴
11│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
│
{'11': 1}
"""
_check_input_type('circuit', Circuit, circuit)
if self.n_qubits < circuit.n_qubits:
raise ValueError(f"Circuit has {circuit.n_qubits} qubits, which is more than simulator qubits.")
if circuit.has_measure_gate:
res = MeasureResult()
res.add_measure(circuit.all_measures.keys())
if circuit.params_name:
if pr is None:
raise ValueError("Applying a parameterized circuit needs a parameter_resolver")
pr = _check_and_generate_pr_type(pr, circuit.params_name)
else:
pr = ParameterResolver()
if circuit.has_measure_gate:
samples = np.array(
self.sim.apply_circuit_with_measure(circuit.get_cpp_obj(), pr.get_cpp_obj(), res.keys_map)
)
samples = samples.reshape((1, -1))
res.collect_data(samples)
return res
if circuit.params_name:
self.sim.apply_circuit(circuit.get_cpp_obj(), pr.get_cpp_obj())
else:
self.sim.apply_circuit(circuit.get_cpp_obj())
return None
[文档] def sampling(self, circuit, pr=None, shots=1, seed=None):
"""
Samping the measure qubit in circuit. Sampling do not change the origin quantum state of this simulator.
Args:
circuit (Circuit): The circuit that you want to evolution and do sampling.
pr (Union[None, dict, ParameterResolver]): The parameter
resolver for this circuit, if this circuit is a parameterized circuit.
Default: None.
shots (int): How many shots you want to sampling this circuit. Default: 1
seed (int): Random seed for random sampling. If None, seed will be a random
int number. Default: None.
Returns:
MeasureResult, the measure result of sampling.
Examples:
>>> from mindquantum.core.circuit import Circuit
>>> from mindquantum.core.gates import Measure
>>> from mindquantum.simulator import Simulator
>>> circ = Circuit().ry('a', 0).ry('b', 1)
>>> circ += Measure('q0_0').on(0)
>>> circ += Measure('q0_1').on(0)
>>> circ += Measure('q1').on(1)
>>> sim = Simulator('projectq', circ.n_qubits)
>>> res = sim.sampling(circ, {'a': 1.1, 'b': 2.2}, shots=100, seed=42)
>>> res
shots: 100
Keys: q1 q0_1 q0_0│0.00 0.122 0.245 0.367 0.49 0.612
──────────────────┼───────────┴───────────┴───────────┴───────────┴───────────┴
000│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│
011│▒▒▒▒▒▒▒▒▒
│
100│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
│
111│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
│
{'000': 18, '011': 9, '100': 49, '111': 24}
"""
if not circuit.all_measures.map:
raise ValueError("circuit must have at least one measurement gate.")
_check_input_type("circuit", Circuit, circuit)
if self.n_qubits < circuit.n_qubits:
raise ValueError(f"Circuit has {circuit.n_qubits} qubits, which is more than simulator qubits.")
_check_int_type("sampling shots", shots)
_check_value_should_not_less("sampling shots", 1, shots)
if circuit.parameterized:
if pr is None:
raise ValueError("Sampling a parameterized circuit need a ParameterResolver")
if not isinstance(pr, (dict, ParameterResolver)):
raise TypeError(f"pr requires a dict or a ParameterResolver, but get {type(pr)}!")
pr = ParameterResolver(pr)
else:
pr = ParameterResolver()
if seed is None:
seed = int(np.random.randint(1, 2 << 20))
else:
_check_seed(seed)
res = MeasureResult()
res.add_measure(circuit.all_measures.keys())
sim = self
if circuit.is_measure_end and not circuit.is_noise_circuit:
sim = Simulator(self.backend, self.n_qubits, self.seed)
sim.set_qs(self.get_qs())
sim.apply_circuit(circuit.remove_measure(), pr)
circuit = Circuit(circuit.all_measures.keys())
samples = np.array(
sim.sim.sampling(circuit.get_cpp_obj(), pr.get_cpp_obj(), shots, res.keys_map, seed)
).reshape((shots, -1))
res.collect_data(samples)
return res
[文档] def apply_hamiltonian(self, hamiltonian: Hamiltonian):
"""
Apply hamiltonian to a simulator, this hamiltonian can be hermitian or non hermitian.
Note:
The quantum state may be not a normalized quantum state after apply hamiltonian.
Args:
hamiltonian (Hamiltonian): the hamiltonian you want to apply.
Examples:
>>> from mindquantum.core.circuit import Circuit
>>> from mindquantum.core.operators import QubitOperator, Hamiltonian
>>> from mindquantum.simulator import Simulator
>>> import scipy.sparse as sp
>>> sim = Simulator('projectq', 1)
>>> sim.apply_circuit(Circuit().h(0))
>>> sim.get_qs()
array([0.70710678+0.j, 0.70710678+0.j])
>>> ham1 = Hamiltonian(QubitOperator('Z0'))
>>> sim.apply_hamiltonian(ham1)
>>> sim.get_qs()
array([ 0.70710678+0.j, -0.70710678+0.j])
>>> sim.reset()
>>> ham2 = Hamiltonian(sp.csr_matrix([[1, 2], [3, 4]]))
>>> sim.apply_hamiltonian(ham2)
>>> sim.get_qs()
array([1.+0.j, 3.+0.j])
"""
_check_input_type('hamiltonian', Hamiltonian, hamiltonian)
_check_hamiltonian_qubits_number(hamiltonian, self.n_qubits)
self.sim.apply_hamiltonian(hamiltonian.get_cpp_obj())
[文档] def get_expectation(self, hamiltonian):
r"""
Get expectation of the given hamiltonian. The hamiltonian could be non hermitian.
.. math::
E = \left<\psi\right|H\left|\psi\right>
Args:
hamiltonian (Hamiltonian): The hamiltonian you want to get expectation.
Returns:
numbers.Number, the expectation value.
Examples:
>>> from mindquantum.core.circuit import Circuit
>>> from mindquantum.core.operators import QubitOperator, Hamiltonian
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 1)
>>> sim.apply_circuit(Circuit().ry(1.2, 0))
>>> ham = Hamiltonian(QubitOperator('Z0'))
>>> sim.get_expectation(ham)
(0.36235775447667357+0j)
"""
if not isinstance(hamiltonian, Hamiltonian):
raise TypeError(f"hamiltonian requires a Hamiltonian, but got {type(hamiltonian)}")
_check_hamiltonian_qubits_number(hamiltonian, self.n_qubits)
return self.sim.get_expectation(hamiltonian.get_cpp_obj())
[文档] def get_qs(self, ket=False):
"""
Get current quantum state of this simulator.
Args:
ket (bool): Whether to return the quantum state in ket format or not.
Default: False.
Returns:
numpy.ndarray, the current quantum state.
Examples:
>>> from mindquantum.algorithm.library import qft
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 2)
>>> sim.apply_circuit(qft(range(2)))
>>> sim.get_qs()
array([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j])
"""
if not isinstance(ket, bool):
raise TypeError(f"ket requires a bool, but get {type(ket)}")
state = np.array(self.sim.get_qs())
if ket:
return '\n'.join(ket_string(state))
return state
[文档] def set_qs(self, quantum_state):
"""
Set quantum state for this simulation.
Args:
quantum_state (numpy.ndarray): the quantum state that you want.
Examples:
>>> import numpy as np
>>> from mindquantum.simulator import Simulator
>>> sim = Simulator('projectq', 1)
>>> sim.get_qs()
array([1.+0.j, 0.+0.j])
>>> sim.set_qs(np.array([1, 1]))
>>> sim.get_qs()
array([0.70710678+0.j, 0.70710678+0.j])
"""
if not isinstance(quantum_state, np.ndarray):
raise TypeError(f"quantum state must be a ndarray, but get {type(quantum_state)}")
if len(quantum_state.shape) != 1:
raise ValueError(f"vec requires a 1-dimensional array, but get {quantum_state.shape}")
n_qubits = np.log2(quantum_state.shape[0])
if n_qubits % 1 != 0:
raise ValueError(f"vec size {quantum_state.shape[0]} is not power of 2")
n_qubits = int(n_qubits)
if self.n_qubits != n_qubits:
raise ValueError(f"{n_qubits} qubits vec does not match with simulation qubits ({self.n_qubits})")
self.sim.set_qs(quantum_state / np.sqrt(np.sum(np.abs(quantum_state) ** 2)))
# pylint: disable=too-many-arguments
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
[文档] def get_expectation_with_grad(
self,
hams,
circ_right,
circ_left=None,
simulator_left=None,
encoder_params_name=None,
ansatz_params_name=None,
parallel_worker=None,
):
r"""
Get a function that return the forward value and gradient w.r.t circuit parameters.
This method is designed to calculate the expectation and its gradient shown as below.
.. math::
E = \left<\varphi\right|U_l^\dagger H U_r \left|\psi\right>
where :math:`U_l` is circ_left, :math:`U_r` is circ_right, :math:`H` is hams
and :math:`\left|\psi\right>` is the current quantum state of this simulator,
and :math:`\left|\varphi\right>` is the quantum state of `simulator_left`.
Args:
hams (Hamiltonian): The hamiltonian that need to get expectation.
circ_right (Circuit): The :math:`U_r` circuit described above.
circ_left (Circuit): The :math:`U_l` circuit described above. By default, this circuit
will be none, and in this situation, :math:`U_l` will be equals to
:math:`U_r`. Default: None.
simulator_left (Simulator): The simulator that contains :math:`\left|\varphi\right>`. If
None, then :math:`\left|\varphi\right>` is assumed to be equals to :math:`\left|\psi\right>`.
Default: None.
encoder_params_name (list[str]): To specific which parameters belongs to encoder,
that will encoder the input data into quantum state. The encoder data
can be a batch. Default: None.
ansatz_params_name (list[str]): To specific which parameters belongs to ansatz,
that will be trained during training. Default: None.
parallel_worker (int): The parallel worker numbers. The parallel workers can handle
batch in parallel threads. Default: None.
Returns:
GradOpsWrapper, a grad ops wrapper than contains information to generate this grad ops.
Examples:
>>> import numpy as np
>>> from mindquantum.core.circuit import Circuit
>>> from mindquantum.core.operators import QubitOperator, Hamiltonian
>>> from mindquantum.simulator import Simulator
>>> circ = Circuit().ry('a', 0)
>>> ham = Hamiltonian(QubitOperator('Z0'))
>>> sim = Simulator('projectq', 1)
>>> grad_ops = sim.get_expectation_with_grad(ham, circ)
>>> grad_ops(np.array([1.0]))
(array([[0.54030231+0.j]]), array([[[-0.84147098+0.j]]]))
>>> sim1 = Simulator('projectq', 1)
>>> prep_circ = Circuit().h(0)
>>> ansatz = Circuit().ry('a', 0).rz('b', 0).ry('c', 0)
>>> sim1.apply_circuit(prep_circ)
>>> sim2 = Simulator('projectq', 1)
>>> ham = Hamiltonian(QubitOperator(""))
>>> grad_ops = sim2.get_expectation_with_grad(ham, ansatz, Circuit(), simulator_left=sim1)
>>> f, g = grad_ops(np.array([7.902762e-01, 2.139225e-04, 7.795934e-01]))
>>> f
array([[0.99999989-7.52279618e-05j]])
"""
if isinstance(hams, Hamiltonian):
hams = [hams]
elif not isinstance(hams, list):
raise TypeError(f"hams requires a Hamiltonian or a list of Hamiltonian, but get {type(hams)}")
for h_tmp in hams:
_check_input_type("hams's element", Hamiltonian, h_tmp)
_check_hamiltonian_qubits_number(h_tmp, self.n_qubits)
_check_input_type("circ_right", Circuit, circ_right)
if circ_right.is_noise_circuit:
raise ValueError("noise circuit not support yet.")
non_hermitian = False
if circ_left is not None:
_check_input_type("circ_left", Circuit, circ_left)
if circ_left.is_noise_circuit:
raise ValueError("noise circuit not support yet.")
non_hermitian = True
if simulator_left is not None:
_check_input_type("simulator_left", Simulator, simulator_left)
if self.backend != simulator_left.backend:
raise ValueError(
"simulator_left should have the same backend as this simulator, ",
f"which is {self.backend}, but get {simulator_left.backend}",
)
if self.n_qubits != simulator_left.n_qubits:
raise ValueError(
"simulator_left should have the same n_qubits as this simulator, ",
f"which is {self.n_qubits}, but get {simulator_left.n_qubits}",
)
non_hermitian = True
if non_hermitian and simulator_left is None:
simulator_left = self
if circ_left is None:
circ_left = circ_right
if circ_left.has_measure_gate or circ_right.has_measure_gate:
raise ValueError("circuit for variational algorithm cannot have measure gate")
if parallel_worker is not None:
_check_int_type("parallel_worker", parallel_worker)
url = "https://mindspore.cn/mindquantum/docs/zh-CN/r0.7/get_gradient_of_PQC_with_mindquantum.html"
if encoder_params_name is not None:
warnings.warn(
(
"Setting encoder_params_name is perecated from version 0.7.0, please call '.as_encoder()'"
" of the circuit you want to work as encoder, and do not set in this API. "
f"Please refer to tutorial {url}"
),
DeprecationWarning,
stacklevel=2,
)
encoder_params_name_old_api = encoder_params_name
else:
encoder_params_name_old_api = []
if ansatz_params_name is not None:
warnings.warn(
(
"Setting ansatz_params_name is perecated from version 0.7.0, please call '.as_ansatz()' "
"of the circuit you want to work as ansatz, and do not set in this API. "
f"Please refer to tutorial {url}"
),
DeprecationWarning,
stacklevel=2,
)
ansatz_params_name_old_api = ansatz_params_name
else:
ansatz_params_name_old_api = []
ansatz_params_name = circ_right.all_ansatz.keys()
encoder_params_name = circ_right.all_encoder.keys()
if not encoder_params_name_old_api:
encoder_params_name_old_api = encoder_params_name
if not ansatz_params_name_old_api:
ansatz_params_name_old_api = ansatz_params_name
if non_hermitian:
for i in circ_left.all_ansatz.keys():
if i not in ansatz_params_name:
ansatz_params_name.append(i)
for i in circ_left.all_encoder.keys():
if i not in encoder_params_name:
encoder_params_name.append(i)
if set(ansatz_params_name) & set(encoder_params_name):
raise RuntimeError("Parameter cannot be both encoder and ansatz parameter.")
if set(encoder_params_name_old_api) != set(encoder_params_name):
raise RuntimeError(
"You set wrong encoder parameters. Please do not set encoder_params_name anymore, "
"but call '.as_encoder()' of circuit that you want to work as encoder."
)
if set(ansatz_params_name_old_api) != set(ansatz_params_name):
raise RuntimeError(
"You set wrong ansatz parameters. Please do not set ansatz_params_name anymore, "
"but call '.as_ansatz()' of circuit that you want to work as ansatz."
)
version = "both"
if not ansatz_params_name:
version = "encoder"
if not encoder_params_name:
version = "ansatz"
circ_n_qubits = max(circ_left.n_qubits, circ_right.n_qubits)
if self.n_qubits < circ_n_qubits:
raise ValueError(f"Simulator has {self.n_qubits} qubits, but circuit has {circ_n_qubits} qubits.")
def grad_ops(*inputs):
if version == "both" and len(inputs) != 2:
raise ValueError("Need two inputs!")
if version in ("encoder", "ansatz") and len(inputs) != 1:
raise ValueError("Need one input!")
if version == "both":
_check_encoder(inputs[0], len(encoder_params_name))
_check_ansatz(inputs[1], len(ansatz_params_name))
batch_threads, mea_threads = _thread_balance(inputs[0].shape[0], len(hams), parallel_worker)
inputs0 = inputs[0]
inputs1 = inputs[1]
if version == "encoder":
_check_encoder(inputs[0], len(encoder_params_name))
batch_threads, mea_threads = _thread_balance(inputs[0].shape[0], len(hams), parallel_worker)
inputs0 = inputs[0]
inputs1 = np.array([])
if version == "ansatz":
_check_ansatz(inputs[0], len(ansatz_params_name))
batch_threads, mea_threads = _thread_balance(1, len(hams), parallel_worker)
inputs0 = np.array([[]])
inputs1 = inputs[0]
if non_hermitian:
f_g1_g2 = self.sim.non_hermitian_measure_with_grad(
[i.get_cpp_obj() for i in hams],
[i.get_cpp_obj(hermitian=True) for i in hams],
circ_left.get_cpp_obj(),
circ_left.get_cpp_obj(hermitian=True),
circ_right.get_cpp_obj(),
circ_right.get_cpp_obj(hermitian=True),
inputs0,
inputs1,
encoder_params_name,
ansatz_params_name,
batch_threads,
mea_threads,
simulator_left.sim,
)
else:
f_g1_g2 = self.sim.hermitian_measure_with_grad(
[i.get_cpp_obj() for i in hams],
circ_right.get_cpp_obj(),
circ_right.get_cpp_obj(hermitian=True),
inputs0,
inputs1,
encoder_params_name,
ansatz_params_name,
batch_threads,
mea_threads,
)
res = np.array(f_g1_g2)
if version == 'both':
return (
res[:, :, 0],
res[:, :, 1 : 1 + len(encoder_params_name)], # noqa:E203
res[:, :, 1 + len(encoder_params_name) :], # noqa:E203
) # f, g1, g2
return res[:, :, 0], res[:, :, 1:] # f, g
grad_wrapper = GradOpsWrapper(
grad_ops, hams, circ_right, circ_left, encoder_params_name, ansatz_params_name, parallel_worker
)
grad_str = f'{self.n_qubits} qubit' + ('' if self.n_qubits == 1 else 's')
grad_str += f' {self.backend} VQA Operator'
grad_wrapper.set_str(grad_str)
return grad_wrapper
def _check_encoder(data, encoder_params_size):
if not isinstance(data, np.ndarray):
raise ValueError(f"encoder parameters need numpy array, but get {type(data)}")
data_shape = data.shape
if len(data_shape) != 2:
raise ValueError("encoder data requires a two dimension numpy array")
if data_shape[1] != encoder_params_size:
raise ValueError(
"encoder parameters size do not match with encoder parameters name, ",
f"need {encoder_params_size} but get {data_shape[1]}.",
)
def _check_ansatz(data, ansatz_params_size):
"""Check ansatz."""
if not isinstance(data, np.ndarray):
raise ValueError(f"ansatz parameters need numpy array, but get {type(data)}")
data_shape = data.shape
if len(data_shape) != 1:
raise ValueError("ansatz data requires a one dimension numpy array")
if data_shape[0] != ansatz_params_size:
raise ValueError(
"ansatz parameters size do not match with ansatz parameters name, "
f"need {ansatz_params_size} but get {data_shape[0]}"
)
def _thread_balance(n_prs, n_meas, parallel_worker):
"""Thread balance."""
if parallel_worker is None:
parallel_worker = n_meas * n_prs
if n_meas * n_prs <= parallel_worker:
batch_threads = n_prs
mea_threads = n_meas
else:
if n_meas < n_prs:
batch_threads = min(n_prs, parallel_worker)
mea_threads = min(n_meas, max(1, parallel_worker // batch_threads))
else:
mea_threads = min(n_meas, parallel_worker)
batch_threads = min(n_prs, max(1, parallel_worker // mea_threads))
return batch_threads, mea_threads
def _check_hamiltonian_qubits_number(hamiltonian, sim_qubits):
"""Check hamiltonian qubits number."""
if hamiltonian.how_to != HowTo.ORIGIN:
if hamiltonian.n_qubits != sim_qubits:
raise ValueError(
f"Hamiltonian qubits is {hamiltonian.n_qubits}, not match with simulator qubits number {sim_qubits}"
)
else:
if hamiltonian.n_qubits > sim_qubits:
raise ValueError(f"Hamiltonian qubits is {hamiltonian.n_qubits}, which is bigger than simulator qubits.")
[文档]class GradOpsWrapper: # pylint: disable=too-many-instance-attributes
"""
Wrapper the gradient operator that with the information that generate this gradient operator.
Args:
grad_ops (Union[FunctionType, MethodType])): A function or a method
that return forward value and gradient w.r.t parameters.
hams (Hamiltonian): The hamiltonian that generate this grad ops.
circ_right (Circuit): The right circuit that generate this grad ops.
circ_left (Circuit): The left circuit that generate this grad ops.
encoder_params_name (list[str]): The encoder parameters name.
ansatz_params_name (list[str]): The ansatz parameters name.
parallel_worker (int): The number of parallel worker to run the batch.
"""
def __init__(
self, grad_ops, hams, circ_right, circ_left, encoder_params_name, ansatz_params_name, parallel_worker
): # pylint: disable=too-many-arguments
"""Initialize a GradOpsWrapper object."""
self.grad_ops = grad_ops
self.hams = hams
self.circ_right = circ_right
self.circ_left = circ_left
self.encoder_params_name = encoder_params_name
self.ansatz_params_name = ansatz_params_name
self.parallel_worker = parallel_worker
self.str = ''
def __call__(self, *args):
"""Definition of a function call operator."""
return self.grad_ops(*args)
[文档] def set_str(self, grad_str):
"""
Set expression for gradient operator.
Args:
grad_str (str): The string of QNN operator.
"""
self.str = grad_str
[文档]def inner_product(bra_simulator: Simulator, ket_simulator: Simulator):
"""
Calculate the inner product of two state that in the given simulator.
Args:
bra_simulator (Simulator): The simulator that serve as bra state.
ket_simulator (Simulator): The simulator that serve as ket state.
Returns:
numbers.Number, the inner product of two quantum state.
Examples:
>>> from mindquantum.core.gates import RX, RY
>>> from mindquantum.simulator import inner_product, Simulator
>>> bra_simulator = Simulator('projectq', 1)
>>> bra_simulator.apply_gate(RY(1.2).on(0))
>>> ket_simulator = Simulator('projectq', 1)
>>> ket_simulator.apply_gate(RX(2.3).on(0))
>>> inner_product(bra_simulator, ket_simulator)
(0.33713923320500694-0.5153852888544989j)
"""
_check_input_type('bra_simulator', Simulator, bra_simulator)
_check_input_type('ket_simulator', Simulator, ket_simulator)
if bra_simulator.n_qubits != ket_simulator.n_qubits:
raise ValueError(
"Two simulator should have same quantum state, "
f"but get {bra_simulator.n_qubits} and {ket_simulator.n_qubits}."
)
if bra_simulator.backend != ket_simulator.backend:
raise ValueError("The backend of two simulator should be same.")
if bra_simulator.backend == 'projectq' and ket_simulator.backend == 'projectq':
bra_simulator.flush()
ket_simulator.flush()
return mb.cpu_projectq_inner_product(bra_simulator.sim, ket_simulator.sim)
raise ValueError(f"backend for {bra_simulator.backend} not implement.")
__all__ = ['Simulator', 'get_supported_simulator', 'GradOpsWrapper', 'inner_product']