diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index a39a3e3b391..814dd659322 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -260,6 +260,7 @@ PauliString, PauliStringGateOperation, PauliStringPhasor, + PauliStringPhasorGate, PauliSum, PauliSumExponential, PauliSumLike, diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index a600115ac94..7c523399f7d 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -118,6 +118,8 @@ def _parallel_gate_op(gate, qubits): 'ParallelGateFamily': cirq.ParallelGateFamily, 'PauliMeasurementGate': cirq.PauliMeasurementGate, 'PauliString': cirq.PauliString, + 'PauliStringPhasor': cirq.PauliStringPhasor, + 'PauliStringPhasorGate': cirq.PauliStringPhasorGate, '_PauliX': cirq.ops.pauli_gates._PauliX, '_PauliY': cirq.ops.pauli_gates._PauliY, '_PauliZ': cirq.ops.pauli_gates._PauliZ, diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 056013e5ad4..849354a14df 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -237,6 +237,7 @@ from cirq.ops.pauli_string_phasor import ( PauliStringPhasor, + PauliStringPhasorGate, ) from cirq.ops.pauli_string_raw_types import ( diff --git a/cirq-core/cirq/ops/pauli_string_phasor.py b/cirq-core/cirq/ops/pauli_string_phasor.py index 7b27a83942e..aa761f5d503 100644 --- a/cirq-core/cirq/ops/pauli_string_phasor.py +++ b/cirq-core/cirq/ops/pauli_string_phasor.py @@ -12,19 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import AbstractSet, Dict, Iterable, Union, TYPE_CHECKING +from typing import AbstractSet, cast, Dict, Iterable, Union, TYPE_CHECKING, Sequence, Iterator import sympy from cirq import value, protocols -from cirq._compat import proper_repr +from cirq._compat import proper_repr, deprecated from cirq.ops import ( raw_types, common_gates, + gate_operation, + dense_pauli_string as dps, pauli_string as ps, pauli_gates, op_tree, - pauli_string_raw_types, ) if TYPE_CHECKING: @@ -32,7 +33,7 @@ @value.value_equality(approximate=True) -class PauliStringPhasor(pauli_string_raw_types.PauliStringGateOperation): +class PauliStringPhasor(gate_operation.GateOperation): """An operation that phases the eigenstates of a Pauli string. The -1 eigenstates of the Pauli string will have their amplitude multiplied @@ -58,26 +59,72 @@ def __init__( in the form of the t in (-1)**t = exp(i pi t). Raises: - ValueError: If the given pauli string does not have eignevalues +1 - or -1. + ValueError: If coefficient is not 1 or -1. """ - if pauli_string.coefficient == -1: - pauli_string = -pauli_string - exponent_pos, exponent_neg = exponent_neg, exponent_pos + gate = PauliStringPhasorGate( + pauli_string.dense(pauli_string.qubits), + exponent_neg=exponent_neg, + exponent_pos=exponent_pos, + ) + super().__init__(gate, pauli_string.qubits) + self._pauli_string = gate.dense_pauli_string.on(*self.qubits) - if pauli_string.coefficient != 1: - raise ValueError( - "Given PauliString doesn't have +1 and -1 eigenvalues. " - "pauli_string.coefficient must be 1 or -1." - ) + @property + def gate(self) -> 'cirq.PauliStringPhasorGate': + """The gate applied by the operation.""" + return cast(PauliStringPhasorGate, self._gate) - super().__init__(pauli_string) - self.exponent_neg = value.canonicalize_half_turns(exponent_neg) - self.exponent_pos = value.canonicalize_half_turns(exponent_pos) + @property + def exponent_neg(self): + """The negative exponent.""" + return self.gate.exponent_neg + + @exponent_neg.setter # type: ignore + @deprecated( + deadline="v0.15", + fix="The mutators of this class are deprecated, instantiate a new object instead.", + ) + def exponent_neg(self, exponent_neg): + """Sets the negative exponent.""" + # coverage: ignore + self.gate._exponent_neg = value.canonicalize_half_turns(exponent_neg) + + @property + def exponent_pos(self): + """The positive exponent.""" + return self.gate.exponent_pos + + @exponent_pos.setter # type: ignore + @deprecated( + deadline="v0.15", + fix="The mutators of this class are deprecated, instantiate a new object instead.", + ) + def exponent_pos(self, exponent_pos): + """Sets the positive exponent.""" + # coverage: ignore + self.gate._exponent_pos = value.canonicalize_half_turns(exponent_pos) + + @property + def pauli_string(self): + """The underlying pauli string.""" + return self._pauli_string + + @pauli_string.setter # type: ignore + @deprecated( + deadline="v0.15", + fix="The mutators of this class are deprecated, instantiate a new object instead.", + ) + def pauli_string(self, pauli_string): + """Sets the underlying pauli string.""" + # coverage: ignore + self._pauli_string = pauli_string + self.gate._dense_pauli_string = pauli_string.dense(pauli_string.qubits) + super()._qubits = pauli_string.qubits @property def exponent_relative(self) -> Union[int, float, sympy.Basic]: - return value.canonicalize_half_turns(self.exponent_neg - self.exponent_pos) + """The relative exponent between negative and positive exponents.""" + return self.gate.exponent_relative def _value_equality_values_(self): return ( @@ -87,6 +134,7 @@ def _value_equality_values_(self): ) def equal_up_to_global_phase(self, other): + """Checks equality of two PauliStringPhasors, up to global phase.""" if isinstance(other, PauliStringPhasor): rel1 = self.exponent_relative rel2 = other.exponent_relative @@ -94,38 +142,181 @@ def equal_up_to_global_phase(self, other): return False def map_qubits(self, qubit_map: Dict[raw_types.Qid, raw_types.Qid]): + """Maps the qubits inside the PauliString.""" return PauliStringPhasor( self.pauli_string.map_qubits(qubit_map), exponent_neg=self.exponent_neg, exponent_pos=self.exponent_pos, ) - def __pow__(self, exponent: Union[float, sympy.Symbol]) -> 'PauliStringPhasor': - pn = protocols.mul(self.exponent_neg, exponent, None) - pp = protocols.mul(self.exponent_pos, exponent, None) - if pn is None or pp is None: - return NotImplemented - return PauliStringPhasor(self.pauli_string, exponent_neg=pn, exponent_pos=pp) - def can_merge_with(self, op: 'PauliStringPhasor') -> bool: + """Checks whether the underlying PauliStrings can be merged.""" return self.pauli_string.equal_up_to_coefficient(op.pauli_string) def merged_with(self, op: 'PauliStringPhasor') -> 'PauliStringPhasor': + """Merges two PauliStringPhasors.""" if not self.can_merge_with(op): raise ValueError(f'Cannot merge operations: {self}, {op}') pp = self.exponent_pos + op.exponent_pos pn = self.exponent_neg + op.exponent_neg return PauliStringPhasor(self.pauli_string, exponent_pos=pp, exponent_neg=pn) + def _circuit_diagram_info_( + self, args: 'cirq.CircuitDiagramInfoArgs' + ) -> 'cirq.CircuitDiagramInfo': + qubits = self.qubits if args.known_qubits is None else args.known_qubits + syms = tuple(f'[{self.pauli_string[qubit]}]' for qubit in qubits) + return protocols.CircuitDiagramInfo(wire_symbols=syms, exponent=self.exponent_relative) + + def pass_operations_over( + self, ops: Iterable[raw_types.Operation], after_to_before: bool = False + ) -> 'PauliStringPhasor': + """Determines how the Pauli phasor changes when conjugated by Cliffords. + + The output and input pauli phasors are related by a circuit equivalence. + In particular, this circuit: + + ───ops───INPUT_PAULI_PHASOR─── + + will be equivalent to this circuit: + + ───OUTPUT_PAULI_PHASOR───ops─── + + up to global phase (assuming `after_to_before` is not set). + + If ops together have matrix C, the Pauli string has matrix P, and the + output Pauli string has matrix P', then P' == C^-1 P C up to + global phase. + + Setting `after_to_before` inverts the relationship, so that the output + is the input and the input is the output. Equivalently, it inverts C. + + Args: + ops: The operations to move over the string. + after_to_before: Determines whether the operations start after the + pauli string, instead of before (and so are moving in the + opposite direction). + """ + new_pauli_string = self.pauli_string.pass_operations_over(ops, after_to_before) + pp = self.exponent_pos + pn = self.exponent_neg + return PauliStringPhasor(new_pauli_string, exponent_pos=pp, exponent_neg=pn) + + def __repr__(self) -> str: + return ( + f'cirq.PauliStringPhasor({self.pauli_string!r}, ' + f'exponent_neg={proper_repr(self.exponent_neg)}, ' + f'exponent_pos={proper_repr(self.exponent_pos)})' + ) + + def __str__(self) -> str: + if self.exponent_pos == -self.exponent_neg: + sign = '-' if self.exponent_pos < 0 else '' + exponent = str(abs(self.exponent_pos)) + return f'exp({sign}iπ{exponent}*{self.pauli_string})' + return f'({self.pauli_string})**{self.exponent_relative}' + + def _json_dict_(self): + return protocols.obj_to_dict_helper(self, ['pauli_string', 'exponent_neg', 'exponent_pos']) + + +@value.value_equality(approximate=True) +class PauliStringPhasorGate(raw_types.Gate): + """A gate that phases the eigenstates of a Pauli string. + + The -1 eigenstates of the Pauli string will have their amplitude multiplied + by e^(i pi exponent_neg) while +1 eigenstates of the Pauli string will have + their amplitude multiplied by e^(i pi exponent_pos). + """ + + def __init__( + self, + dense_pauli_string: dps.DensePauliString, + *, + exponent_neg: Union[int, float, sympy.Basic] = 1, + exponent_pos: Union[int, float, sympy.Basic] = 0, + ) -> None: + """Initializes the PauliStringPhasorGate. + + Args: + dense_pauli_string: The DensePauliString defining the positive and + negative eigenspaces that will be independently phased. + exponent_neg: How much to phase vectors in the negative eigenspace, + in the form of the t in (-1)**t = exp(i pi t). + exponent_pos: How much to phase vectors in the positive eigenspace, + in the form of the t in (-1)**t = exp(i pi t). + + Raises: + ValueError: If coefficient is not 1 or -1. + """ + if dense_pauli_string.coefficient == -1: + dense_pauli_string = -dense_pauli_string + exponent_pos, exponent_neg = exponent_neg, exponent_pos + + if dense_pauli_string.coefficient != 1: + raise ValueError( + "Given DensePauliString doesn't have +1 and -1 eigenvalues. " + "dense_pauli_string.coefficient must be 1 or -1." + ) + + self._dense_pauli_string = dense_pauli_string + self._exponent_neg = value.canonicalize_half_turns(exponent_neg) + self._exponent_pos = value.canonicalize_half_turns(exponent_pos) + + @property + def exponent_relative(self) -> Union[int, float, sympy.Basic]: + """The relative exponent between negative and positive exponents.""" + return value.canonicalize_half_turns(self.exponent_neg - self.exponent_pos) + + @property + def exponent_neg(self): + """The negative exponent.""" + return self._exponent_neg + + @property + def exponent_pos(self): + """The positive exponent.""" + return self._exponent_pos + + @property + def dense_pauli_string(self): + """The underlying DensePauliString.""" + return self._dense_pauli_string + + def _value_equality_values_(self): + return ( + self.dense_pauli_string, + self.exponent_neg, + self.exponent_pos, + ) + + def equal_up_to_global_phase(self, other): + """Checks equality of two PauliStringPhasors, up to global phase.""" + if isinstance(other, PauliStringPhasorGate): + rel1 = self.exponent_relative + rel2 = other.exponent_relative + return rel1 == rel2 and self.dense_pauli_string == other.dense_pauli_string + return False + + def __pow__(self, exponent: Union[float, sympy.Symbol]) -> 'PauliStringPhasorGate': + pn = protocols.mul(self.exponent_neg, exponent, None) + pp = protocols.mul(self.exponent_pos, exponent, None) + if pn is None or pp is None: + return NotImplemented + return PauliStringPhasorGate(self.dense_pauli_string, exponent_neg=pn, exponent_pos=pp) + def _has_unitary_(self): return not self._is_parameterized_() - def _decompose_(self) -> 'cirq.OP_TREE': - if len(self.pauli_string) <= 0: + def _to_z_basis_ops(self, qubits: Sequence['cirq.Qid']) -> Iterator[raw_types.Operation]: + """Returns operations to convert the qubits to the computational basis.""" + return self.dense_pauli_string.on(*qubits).to_z_basis_ops() + + def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': + if len(self.dense_pauli_string) <= 0: return - qubits = self.qubits any_qubit = qubits[0] - to_z_ops = op_tree.freeze_op_tree(self.pauli_string.to_z_basis_ops()) + to_z_ops = op_tree.freeze_op_tree(self._to_z_basis_ops(qubits)) xor_decomp = tuple(xor_nonlocal_decompose(qubits, any_qubit)) yield to_z_ops yield xor_decomp @@ -140,13 +331,8 @@ def _decompose_(self) -> 'cirq.OP_TREE': yield protocols.inverse(xor_decomp) yield protocols.inverse(to_z_ops) - def _circuit_diagram_info_( - self, args: 'cirq.CircuitDiagramInfoArgs' - ) -> 'cirq.CircuitDiagramInfo': - return self._pauli_string_diagram_info(args, exponent=self.exponent_relative) - def _trace_distance_bound_(self) -> float: - if len(self.qubits) == 0: + if len(self.dense_pauli_string) == 0: return 0.0 return protocols.trace_distance_bound(pauli_gates.Z ** self.exponent_relative) @@ -162,24 +348,16 @@ def _parameter_names_(self) -> AbstractSet[str]: def _resolve_parameters_( self, resolver: 'cirq.ParamResolver', recursive: bool - ) -> 'PauliStringPhasor': - return PauliStringPhasor( - self.pauli_string, + ) -> 'PauliStringPhasorGate': + return PauliStringPhasorGate( + self.dense_pauli_string, exponent_neg=resolver.value_of(self.exponent_neg, recursive), exponent_pos=resolver.value_of(self.exponent_pos, recursive), ) - def pass_operations_over( - self, ops: Iterable[raw_types.Operation], after_to_before: bool = False - ) -> 'PauliStringPhasor': - new_pauli_string = self.pauli_string.pass_operations_over(ops, after_to_before) - pp = self.exponent_pos - pn = self.exponent_neg - return PauliStringPhasor(new_pauli_string, exponent_pos=pp, exponent_neg=pn) - def __repr__(self) -> str: return ( - f'cirq.PauliStringPhasor({self.pauli_string!r}, ' + f'cirq.PauliStringPhasorGate({self.dense_pauli_string!r}, ' f'exponent_neg={proper_repr(self.exponent_neg)}, ' f'exponent_pos={proper_repr(self.exponent_pos)})' ) @@ -188,8 +366,25 @@ def __str__(self) -> str: if self.exponent_pos == -self.exponent_neg: sign = '-' if self.exponent_pos < 0 else '' exponent = str(abs(self.exponent_pos)) - return f'exp({sign}iπ{exponent}*{self.pauli_string})' - return f'({self.pauli_string})**{self.exponent_relative}' + return f'exp({sign}iπ{exponent}*{self.dense_pauli_string})' + return f'({self.dense_pauli_string})**{self.exponent_relative}' + + def num_qubits(self) -> int: + """The number of qubits for the gate.""" + return len(self.dense_pauli_string) + + def on(self, *qubits: 'cirq.Qid') -> 'cirq.PauliStringPhasor': + """Creates a PauliStringPhasor on the qubits.""" + return PauliStringPhasor( + self.dense_pauli_string.on(*qubits), + exponent_pos=self.exponent_pos, + exponent_neg=self.exponent_neg, + ) + + def _json_dict_(self): + return protocols.obj_to_dict_helper( + self, ['dense_pauli_string', 'exponent_neg', 'exponent_pos'] + ) def xor_nonlocal_decompose( diff --git a/cirq-core/cirq/ops/pauli_string_phasor_test.py b/cirq-core/cirq/ops/pauli_string_phasor_test.py index 2fc731e08cd..3eb75f2e119 100644 --- a/cirq-core/cirq/ops/pauli_string_phasor_test.py +++ b/cirq-core/cirq/ops/pauli_string_phasor_test.py @@ -19,6 +19,14 @@ import cirq +dps_empty = cirq.DensePauliString('') +dps_x = cirq.DensePauliString('X') +dps_y = cirq.DensePauliString('Y') +dps_xy = cirq.DensePauliString('XY') +dps_yx = cirq.DensePauliString('YX') +dps_xyz = cirq.DensePauliString('XYZ') +dps_zyx = cirq.DensePauliString('ZYX') + def _make_qubits(n): return [cirq.NamedQubit(f'q{i}') for i in range(n)] @@ -401,3 +409,190 @@ def test_str(): assert str(np.exp(0.5j * np.pi * cirq.X(q0) * cirq.Y(q1))) == 'exp(iπ0.5*X(q0)*Y(q1))' assert str(np.exp(-0.25j * np.pi * cirq.X(q0) * cirq.Y(q1))) == 'exp(-iπ0.25*X(q0)*Y(q1))' assert str(np.exp(0.5j * np.pi * cirq.PauliString())) == 'exp(iπ0.5*I)' + + +def test_gate_init(): + a = cirq.LineQubit(0) + with pytest.raises(ValueError, match='eigenvalues'): + _ = cirq.PauliStringPhasorGate(1j * cirq.X(a)) + + v1 = cirq.PauliStringPhasorGate( + cirq.DensePauliString('X', coefficient=-1), exponent_neg=0.25, exponent_pos=-0.5 + ) + assert v1.dense_pauli_string == dps_x + assert v1.exponent_neg == -0.5 + assert v1.exponent_pos == 0.25 + + v2 = cirq.PauliStringPhasorGate(dps_x, exponent_neg=0.75, exponent_pos=-0.125) + assert v2.dense_pauli_string == dps_x + assert v2.exponent_neg == 0.75 + assert v2.exponent_pos == -0.125 + + +def test_gate_on(): + q = cirq.LineQubit(0) + g1 = cirq.PauliStringPhasorGate( + cirq.DensePauliString('X', coefficient=-1), exponent_neg=0.25, exponent_pos=-0.5 + ) + + op1 = g1.on(q) + assert isinstance(op1, cirq.PauliStringPhasor) + assert op1.qubits == (q,) + assert op1.gate == g1 + assert op1.pauli_string == dps_x.on(q) + assert op1.exponent_neg == -0.5 + assert op1.exponent_pos == 0.25 + + g2 = cirq.PauliStringPhasorGate(dps_x, exponent_neg=0.75, exponent_pos=-0.125) + op2 = g2.on(q) + assert isinstance(op2, cirq.PauliStringPhasor) + assert op2.qubits == (q,) + assert op2.gate == g2 + assert op2.pauli_string == dps_x.on(q) + assert op2.exponent_neg == 0.75 + assert op2.exponent_pos == -0.125 + + +def test_gate_eq_ne_hash(): + eq = cirq.testing.EqualsTester() + dps_xyx = cirq.DensePauliString('XYX') + eq.make_equality_group( + lambda: cirq.PauliStringPhasorGate(dps_empty, exponent_neg=0.5), + lambda: cirq.PauliStringPhasorGate(dps_empty, exponent_neg=-1.5), + lambda: cirq.PauliStringPhasorGate(dps_empty, exponent_neg=2.5), + ) + eq.make_equality_group(lambda: cirq.PauliStringPhasorGate(-dps_empty, exponent_neg=-0.5)) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_xyz), cirq.PauliStringPhasorGate(dps_xyz, exponent_neg=1) + ) + eq.add_equality_group(cirq.PauliStringPhasorGate(-dps_xyz, exponent_neg=1)) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_xyx), cirq.PauliStringPhasorGate(dps_xyx, exponent_neg=1) + ) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_xy), cirq.PauliStringPhasorGate(dps_xy, exponent_neg=1) + ) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_yx), cirq.PauliStringPhasorGate(dps_yx, exponent_neg=1) + ) + eq.add_equality_group(cirq.PauliStringPhasorGate(-dps_xyx, exponent_neg=1)) + eq.add_equality_group(cirq.PauliStringPhasorGate(dps_xyx, exponent_neg=0.5)) + eq.add_equality_group(cirq.PauliStringPhasorGate(-dps_xyx, exponent_neg=-0.5)) + eq.add_equality_group(cirq.PauliStringPhasorGate(dps_xyz, exponent_neg=sympy.Symbol('a'))) + + +def test_gate_equal_up_to_global_phase(): + groups = [ + [ + cirq.PauliStringPhasorGate(dps_x, exponent_neg=0.25), + cirq.PauliStringPhasorGate(dps_x, exponent_neg=0, exponent_pos=-0.25), + cirq.PauliStringPhasorGate(dps_x, exponent_pos=-0.125, exponent_neg=0.125), + ], + [cirq.PauliStringPhasorGate(dps_x)], + [cirq.PauliStringPhasorGate(dps_y, exponent_neg=0.25)], + [cirq.PauliStringPhasorGate(dps_xy, exponent_neg=0.25)], + ] + for g1 in groups: + for e1 in g1: + assert not e1.equal_up_to_global_phase("not even close") + for g2 in groups: + for e2 in g2: + assert e1.equal_up_to_global_phase(e2) == (g1 is g2) + + +def test_gate_pow(): + s = dps_x + p = cirq.PauliStringPhasorGate(s, exponent_neg=0.25, exponent_pos=0.5) + assert p ** 0.5 == cirq.PauliStringPhasorGate(s, exponent_neg=0.125, exponent_pos=0.25) + with pytest.raises(TypeError, match='unsupported operand'): + _ = p ** object() + assert p ** 1 == p + + +def test_gate_extrapolate_effect(): + gate1 = cirq.PauliStringPhasorGate(dps_empty, exponent_neg=0.5) + gate2 = cirq.PauliStringPhasorGate(dps_empty, exponent_neg=1.5) + gate3 = cirq.PauliStringPhasorGate(dps_empty, exponent_neg=0.125) + assert gate1 ** 3 == gate2 + assert gate1 ** 0.25 == gate3 + + +def test_gate_extrapolate_effect_with_symbol(): + eq = cirq.testing.EqualsTester() + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_empty, exponent_neg=sympy.Symbol('a')), + cirq.PauliStringPhasorGate(dps_empty) ** sympy.Symbol('a'), + ) + eq.add_equality_group(cirq.PauliStringPhasorGate(dps_empty) ** sympy.Symbol('b')) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_empty, exponent_neg=0.5) ** sympy.Symbol('b') + ) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_empty, exponent_neg=sympy.Symbol('a')) ** 0.5 + ) + eq.add_equality_group( + cirq.PauliStringPhasorGate(dps_empty, exponent_neg=sympy.Symbol('a')) ** sympy.Symbol('b') + ) + + +def test_gate_inverse(): + i = dps_empty + gate1 = cirq.PauliStringPhasorGate(i, exponent_neg=0.25) + gate2 = cirq.PauliStringPhasorGate(i, exponent_neg=-0.25) + gate3 = cirq.PauliStringPhasorGate(i, exponent_neg=sympy.Symbol('s')) + gate4 = cirq.PauliStringPhasorGate(i, exponent_neg=-sympy.Symbol('s')) + assert cirq.inverse(gate1) == gate2 + assert cirq.inverse(gate3, None) == gate4 + + +def test_gate_is_parameterized(): + gate = cirq.PauliStringPhasorGate(dps_empty) + assert not cirq.is_parameterized(gate) + assert not cirq.is_parameterized(gate ** 0.1) + assert cirq.is_parameterized(gate ** sympy.Symbol('a')) + + +@pytest.mark.parametrize('resolve_fn', [cirq.resolve_parameters, cirq.resolve_parameters_once]) +def test_gate_with_parameters_resolved_by(resolve_fn): + gate = cirq.PauliStringPhasorGate(dps_empty, exponent_neg=sympy.Symbol('a')) + resolver = cirq.ParamResolver({'a': 0.1}) + actual = resolve_fn(gate, resolver) + expected = cirq.PauliStringPhasorGate(dps_empty, exponent_neg=0.1) + assert actual == expected + + +def test_gate_repr(): + cirq.testing.assert_equivalent_repr( + cirq.PauliStringPhasorGate( + dps_zyx, + exponent_neg=0.5, + exponent_pos=0.25, + ) + ) + cirq.testing.assert_equivalent_repr( + cirq.PauliStringPhasorGate(-dps_yx, exponent_neg=-0.5, exponent_pos=0.25) + ) + + +def test_gate_str(): + gate = cirq.PauliStringPhasorGate(cirq.DensePauliString('ZYX', coefficient=+1)) ** 0.5 + assert str(gate) == '(+ZYX)**0.5' + + gate = cirq.PauliStringPhasorGate(cirq.DensePauliString('ZYX', coefficient=+1)) ** -0.5 + assert str(gate) == '(+ZYX)**-0.5' + + gate = cirq.PauliStringPhasorGate(cirq.DensePauliString('ZYX', coefficient=-1)) ** -0.5 + assert str(gate) == '(+ZYX)**0.5' + + gate = cirq.PauliStringPhasorGate( + cirq.DensePauliString('ZYX'), exponent_pos=0.5, exponent_neg=-0.5 + ) + assert str(gate) == 'exp(iπ0.5*+ZYX)' + + gate = ( + cirq.PauliStringPhasorGate( + cirq.DensePauliString('ZYX'), exponent_pos=0.5, exponent_neg=-0.5 + ) + ** -0.5 + ) + assert str(gate) == 'exp(-iπ0.25*+ZYX)' diff --git a/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.json b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.json new file mode 100644 index 00000000000..2a1a81cb194 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.json @@ -0,0 +1,38 @@ +{ + "cirq_type": "PauliStringPhasor", + "exponent_neg": 0.2, + "exponent_pos": 0.1, + "pauli_string": + { + "cirq_type": "PauliString", + "qubit_pauli_map": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "_PauliX", + "exponent": 1, + "global_shift": 0.0 + } + ], + [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "_PauliY", + "exponent": 1, + "global_shift": 0.0 + } + ] + ], + "coefficient": { + "cirq_type": "complex", + "real": 1.0, + "imag": 0.0 + } + } +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.repr b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.repr new file mode 100644 index 00000000000..19c005a22c0 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasor.repr @@ -0,0 +1 @@ +cirq.PauliStringPhasor(cirq.X.on(cirq.LineQubit(0))*cirq.Y.on(cirq.LineQubit(1)), exponent_neg=0.2, exponent_pos=0.1) \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.json b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.json new file mode 100644 index 00000000000..8a1a3e3f76f --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.json @@ -0,0 +1,20 @@ +{ + "cirq_type": "PauliStringPhasorGate", + "exponent_neg": 0.2, + "exponent_pos": 0.1, + "dense_pauli_string": + { + "cirq_type": "DensePauliString", + "pauli_mask": [ + 1, + 2, + 3, + 0 + ], + "coefficient": { + "cirq_type": "complex", + "real": 1.0, + "imag": 0.0 + } + } +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.repr b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.repr new file mode 100644 index 00000000000..b3965ffe6f4 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/PauliStringPhasorGate.repr @@ -0,0 +1 @@ +cirq.PauliStringPhasorGate(cirq.DensePauliString('XYZI', coefficient=1), exponent_neg=0.2, exponent_pos=0.1) \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 81e1fa4e45d..eb951540e6d 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -47,7 +47,6 @@ 'DiagonalGate', 'NeutralAtomDevice', 'PauliInteractionGate', - 'PauliStringPhasor', 'PauliSum', 'PauliSumCollector', 'PauliSumExponential',