def test_joined_generator(self): generator1 = TwoRotsGenerator() generator2 = OneRotGenerator() grad = op_tree_generator_grad( OpTreeGenerator.join(generator1, generator2), rad ) grad_lco = LinearCombinationOfOperations({ _generator((q0, q1)): _coeff for _generator, _coeff in grad.items() }) for _generator, _coeff in grad.items(): print(_generator.diagram(), _coeff) exact_grad_lco = LinearCombinationOfOperations({ (X(q0), rx(rad).on(q0), rz(rad).on(q1), ry(c * rad).on(q0)): -0.5j, (rx(rad).on(q0), Z(q1), rz(rad).on(q1), ry(c * rad).on(q0)): -0.5j, (rx(rad).on(q0), rz(rad).on(q1), Y(q0), ry(c * rad).on(q0)): -0.5j * c, }) param_resolver = { rad: np.random.rand() * 4 * np.pi, c: np.random.rand() } grad_lco = cirq.resolve_parameters(grad_lco, param_resolver) exact_grad_lco = cirq.resolve_parameters(exact_grad_lco, param_resolver) test_lco_identical_with_simulator( grad_lco, exact_grad_lco, self )
def op_series_grad( op_series: typing.Sequence[cirq.Operation], parameter: sympy.Symbol ) -> typing.Union[LinearCombinationOfOperations, GradNotImplemented]: grad_dict = LinearCombinationOfOperations({}) for i, _op in enumerate(op_series): _grad = op_grad(typing.cast(cirq.GateOperation, _op), parameter) if isinstance(_grad, GradNotImplemented): return _grad elif _grad == ZERO_OP: continue else: for _grad_op, coeff in _grad.items(): _grad_op_series = copy.deepcopy(op_series) _grad_op_series = (_grad_op_series[:i] + list(_grad_op) + _grad_op_series[i + 1:]) grad_dict += LinearCombinationOfOperations( {tuple(_grad_op_series): coeff}) return grad_dict
def test_simple_generator(self): generator = TwoRotsGenerator() grad_lcg = op_tree_generator_grad(generator, rad) grad_lco = LinearCombinationOfOperations({ _generator((q0, q1)): _coeff for _generator, _coeff in grad_lcg.items() }) exact_grad_lco = LinearCombinationOfOperations({ (X(q0), rx(rad).on(q0), rz(rad).on(q1)): -0.5j, (rx(rad).on(q0), Z(q1), rz(rad).on(q1)): -0.5j }) param_resolver = {rad: np.random.rand() * 4 * np.pi} grad_lco = cirq.resolve_parameters(grad_lco, param_resolver) exact_grad_lco = cirq.resolve_parameters(exact_grad_lco, param_resolver) test_lco_identical_with_simulator( grad_lco, exact_grad_lco, self )
def op_grad( operation: cirq.GateOperation, parameter: sympy.Symbol ) -> typing.Union[LinearCombinationOfOperations, GradNotImplemented]: if not cirq.is_parameterized(operation): return ZERO_OP.copy() gate = operation.gate qubits = operation.qubits if isinstance(gate, cirq.XPowGate): # dXPow(e=f(t), s) / dt = i π (s + 1 / 2 - X / 2) * (df(t)/dt) XPow(e=f(t), s) # Note that: Rx(θ) = XPowGate(exponent=θ/pi, global_shift=-0.5) partial = sympy.diff(gate.exponent, parameter) coeff_i = 1.0j * sympy.pi * (gate._global_shift + 0.5) coeff_x = -1.0j / 2 * sympy.pi if partial == 0: return ZERO_OP.copy() return LinearCombinationOfOperations({ (cirq.X.on(*qubits), operation): coeff_x * partial, (cirq.I.on(*qubits), operation): coeff_i * partial }) elif isinstance(gate, cirq.YPowGate): # dYPow(e=f(t), s) / dt = i π (s + 1 / 2 - Y / 2) * (df(t)/dt) YPow(e=f(t), s) # Note that: Ry(θ) = YPowGate(exponent=θ/pi, global_shift=-0.5) partial = sympy.diff(gate.exponent, parameter) coeff_i = 1.0j * sympy.pi * (gate._global_shift + 0.5) coeff_y = -1.0j / 2 * sympy.pi if partial == 0: return ZERO_OP.copy() return LinearCombinationOfOperations({ (cirq.Y.on(*qubits), operation): coeff_y * partial, (cirq.I.on(*qubits), operation): coeff_i * partial }) elif isinstance(gate, cirq.ZPowGate): # dZPow(e=f(t), s) / dt = i π (s + 1 / 2 - Z / 2) * (df(t)/dt) ZPow(e=f(t), s) # Note that: Ry(θ) = ZPowGate(exponent=θ/pi, global_shift=-0.5) partial = sympy.diff(gate.exponent, parameter) coeff_i = 1.0j * sympy.pi * (gate._global_shift + 0.5) coeff_z = -1.0j / 2 * sympy.pi if partial == 0: return ZERO_OP.copy() return LinearCombinationOfOperations({ (cirq.Z.on(*qubits), operation): coeff_z * partial, (cirq.I.on(*qubits), operation): coeff_i * partial }) elif isinstance(gate, GlobalPhaseGate): gate = typing.cast(GlobalPhaseGate, gate) # Ph(θ) = exp(i pi θ) # dPh(f(θ)) / dθ = [i pi df(θ) / dθ] exp(i pi f(θ)) # = [i pi df(θ) / dθ] Ph(f(θ)) coeff = 1.0j * sympy.diff(gate.rad * sympy.pi, parameter) if coeff == 0: return ZERO_OP.copy() return LinearCombinationOfOperations({(operation, ): coeff}) elif isinstance(gate, cirq.EigenGate): gate = typing.cast(cirq.EigenGate, gate) eigenvalues = {v for v, p in gate._eigen_components()} if eigenvalues == {0, 1}: e = gate.exponent s = gate._global_shift partial = sympy.diff(e, parameter) if partial == 0: return ZERO_OP.copy() num_qubits = gate.num_qubits() gate_e1_s0 = copy.deepcopy(gate) gate_e1_s0._exponent = 1.0 gate_e1_s0._global_shift = 0.0 # Any better solutions? coeff = 0.5 * sympy.exp(1.0j * sympy.pi * (1 + s) * e) return 1.0j * sympy.pi * LinearCombinationOfOperations( { (operation, ): s * partial, (gate_e1_s0.on(*qubits), ): -coeff * partial, (cirq.IdentityGate(num_qubits).on(*qubits), ): coeff * partial }) else: return GradNotImplemented(operation) elif isinstance(gate, GateBlock): gate = typing.cast(GateBlock, gate) generator_grad = op_tree_generator_grad(gate._op_generator, parameter) if isinstance(generator_grad, GradNotImplemented): return generator_grad if len(generator_grad) == 0: return ZERO_OP.copy() _grad = LinearCombinationOfOperations({}) for generator, coeff in generator_grad.items(): grad_gate = GateBlock(generator) _grad += LinearCombinationOfOperations({ (grad_gate.on(*qubits), ): coeff }) # print(grad_gate.diagram()) return _grad elif isinstance(gate, cirq.ControlledGate): gate = typing.cast(cirq.ControlledGate, gate) sub_gate_qubits = qubits[gate.num_controls():] sub_op_grad = op_grad(gate.sub_gate.on(*sub_gate_qubits), parameter) if is_zero_op_or_grad_not_implemented(sub_op_grad): return sub_op_grad _gate_grad = LinearCombinationOfOperations({}) op: cirq.GateOperation for op_series, coeff in sub_op_grad.items(): _controlled_op_series = [ cirq.ControlledGate( op.gate, control_qubits=qubits[:gate.num_controls()]).on( *sub_gate_qubits) for op in op_series ] _controlled_negative_op_series = copy.deepcopy( _controlled_op_series) _controlled_negative_op_series.insert( 0, cirq.ControlledGate( GlobalPhaseGate(rad=1), control_qubits=qubits[:gate.num_controls()]).on( *sub_gate_qubits)) _gate_grad += LinearCombinationOfOperations( { tuple(_controlled_op_series): coeff, tuple(_controlled_negative_op_series): -coeff, }) / 2.0 return _gate_grad # if `operation` is a basic and indecomposable operation whose grad is # not implemented elif is_an_indecomposable_operation(operation): return GradNotImplemented(operation) else: op_series = cirq.decompose( operation, keep=(lambda op: is_a_basic_operation(op) or is_an_indecomposable_operation(op)), on_stuck_raise=None) # type: typing.List[cirq.Operation] _grad = op_series_grad(op_series, parameter) return _grad
import cirq import numpy as np import sympy from cirq import flatten_op_tree from paulicirq.gates import GlobalPhaseGate from paulicirq.gates.gate_block import GateBlock from paulicirq.gates.universal_gate_set import (is_a_basic_operation, is_an_indecomposable_operation) from paulicirq.linear_combinations import (LinearCombinationOfOperations, LinearSymbolicDict) from paulicirq.op_tree import OpTreeGenerator from paulicirq.utils import ToBeTested ZERO_OP = LinearCombinationOfOperations({}) # constant class GradNotImplemented: __slots__ = ("operation", ) def __init__(self, operation): self.operation = operation def is_zero_op_or_grad_not_implemented(grad): return grad == ZERO_OP or isinstance(grad, GradNotImplemented) @ToBeTested def op_grad(