Skip to content

Commit

Permalink
GlobalPhase: use exponent, and implement controlled (ZPowGate) (#1117)
Browse files Browse the repository at this point in the history
* Global Phase

* use `exponent` instead of `coefficient` for `GlobalPhase`

* regenerate notebook

* fix neg-ctrl gphase, add unitary test

* docstring + types

* approx equal for gphase

* fix generalizer

---------

Co-authored-by: Matthew Harrigan <[email protected]>
Co-authored-by: Tanuj Khattar <[email protected]>
  • Loading branch information
3 people authored Jul 11, 2024
1 parent 1357456 commit 37d523c
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 59 deletions.
2 changes: 1 addition & 1 deletion qualtran/_infra/controlled_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def test_controlled_tensor_without_decompose():


def test_controlled_global_phase_tensor():
bloq = GlobalPhase(1.0j).controlled()
bloq = GlobalPhase.from_coefficient(1.0j).controlled()
should_be = np.diag([1, 1.0j])
np.testing.assert_allclose(bloq.tensor_contract(), should_be)

Expand Down
55 changes: 51 additions & 4 deletions qualtran/bloqs/basic_gates/global_phase.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,17 @@
},
"source": [
"## `GlobalPhase`\n",
"Global phase operation of $z$ (where $|z| = 1$).\n",
"Applies a global phase to the circuit as a whole.\n",
"\n",
"The unitary effect is to multiply the state vector by the complex scalar\n",
"$e^{i pi t}$ for `exponent` $t$.\n",
"\n",
"The global phase of a state or circuit does not affect any observable quantity, but\n",
"keeping track of it can be a useful bookkeeping mechanism for testing circuit identities.\n",
"The global phase becomes important if the gate becomes controlled.\n",
"\n",
"#### Parameters\n",
" - `coefficient`: a unit complex number $z$ representing the global phase\n",
" - `exponent`: the exponent $t$ of the global phase $e^{i pi t}$ to apply.\n",
" - `eps`: precision\n"
]
},
Expand Down Expand Up @@ -74,7 +81,7 @@
},
"outputs": [],
"source": [
"global_phase = GlobalPhase(1j)"
"global_phase = GlobalPhase(exponent=0.5)"
]
},
{
Expand Down Expand Up @@ -125,6 +132,46 @@
"show_call_graph(global_phase_g)\n",
"show_counts_sigma(global_phase_sigma)"
]
},
{
"cell_type": "markdown",
"id": "c183275c-cc9c-477d-888c-d8b850f67a2e",
"metadata": {},
"source": [
"### Tensors and Controlled\n",
"\n",
"The \"tensor\" of the global phase gate is just a number."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d053b20b-3d61-487a-b962-e2368d834c40",
"metadata": {},
"outputs": [],
"source": [
"global_phase.tensor_contract()"
]
},
{
"cell_type": "markdown",
"id": "b737b871-9c61-4d54-860a-d9928f18808b",
"metadata": {},
"source": [
"When a global phase is controlled, it is equivalent to a ZPowGate"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d873856e-a687-4b73-acdc-9188dd13a60e",
"metadata": {},
"outputs": [],
"source": [
"cgp = global_phase.controlled()\n",
"print(repr(cgp))\n",
"print(cgp.tensor_contract())"
]
}
],
"metadata": {
Expand All @@ -143,7 +190,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
"version": "3.11.8"
}
},
"nbformat": 4,
Expand Down
87 changes: 67 additions & 20 deletions qualtran/bloqs/basic_gates/global_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,73 @@
# 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.

from typing import Dict, List, TYPE_CHECKING
from functools import cached_property
from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING

import attrs
import cirq
from attrs import frozen

from qualtran import bloq_example, BloqDocSpec, ConnectionT, DecomposeTypeError
from attrs import field, frozen

from qualtran import (
AddControlledT,
Bloq,
bloq_example,
BloqBuilder,
BloqDocSpec,
CompositeBloq,
ConnectionT,
CtrlSpec,
DecomposeTypeError,
SoquetT,
)
from qualtran.bloqs.basic_gates.rotation import ZPowGate
from qualtran.cirq_interop import CirqGateAsBloqBase
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.symbolics import sconj, SymbolicComplex
from qualtran.symbolics import pi, sarg, sexp, SymbolicComplex, SymbolicFloat

if TYPE_CHECKING:
import quimb.tensor as qtn

from qualtran import CompositeBloq


@frozen
class GlobalPhase(CirqGateAsBloqBase):
"""Global phase operation of $z$ (where $|z| = 1$).
r"""Applies a global phase to the circuit as a whole.
The unitary effect is to multiply the state vector by the complex scalar
$e^{i pi t}$ for `exponent` $t$.
The global phase of a state or circuit does not affect any observable quantity, but
keeping track of it can be a useful bookkeeping mechanism for testing circuit identities.
The global phase becomes important if the gate becomes controlled.
Args:
coefficient: a unit complex number $z$ representing the global phase
exponent: the exponent $t$ of the global phase $e^{i pi t}$ to apply.
eps: precision
"""

coefficient: 'SymbolicComplex'
eps: float = 1e-11
exponent: SymbolicFloat = field(kw_only=True)
eps: SymbolicFloat = 1e-11

@cached_property
def coefficient(self) -> SymbolicComplex:
return sexp(self.exponent * pi(self.exponent) * 1j)

@classmethod
def from_coefficient(
cls, coefficient: SymbolicComplex, *, eps: SymbolicFloat = 1e-11
) -> 'GlobalPhase':
"""Applies a global phase of `coefficient`."""
return cls(exponent=sarg(coefficient) / pi(coefficient), eps=eps)

@property
def cirq_gate(self) -> cirq.Gate:
return cirq.GlobalPhaseGate(self.coefficient, self.eps)
return cirq.GlobalPhaseGate(self.coefficient)

def decompose_bloq(self) -> 'CompositeBloq':
raise DecomposeTypeError(f"{self} is atomic")

def adjoint(self) -> 'GlobalPhase':
return attrs.evolve(self, coefficient=sconj(self.coefficient))
return attrs.evolve(self, exponent=-self.exponent)

def my_tensors(
self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT']
Expand All @@ -58,6 +86,29 @@ def my_tensors(

return [qtn.Tensor(data=self.coefficient, inds=[], tags=[str(self)])]

def get_ctrl_system(
self, ctrl_spec: Optional['CtrlSpec'] = None
) -> Tuple['Bloq', 'AddControlledT']:

# Delegate to superclass logic for more than one control.
if not (ctrl_spec is None or ctrl_spec == CtrlSpec() or ctrl_spec == CtrlSpec(cvs=0)):
return super().get_ctrl_system(ctrl_spec=ctrl_spec)

# Otherwise, it's a ZPowGate
if ctrl_spec == CtrlSpec(cvs=0):
bloq = ZPowGate(exponent=-self.exponent, global_shift=-1, eps=self.eps)
else:
bloq = ZPowGate(exponent=self.exponent, eps=self.eps)

def _add_ctrled(
bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT']
) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]:
(ctrl,) = ctrl_soqs
ctrl = bb.add(bloq, q=ctrl)
return [ctrl], []

return bloq, _add_ctrled

def pretty_name(self) -> str:
return 'GPhase'

Expand All @@ -70,12 +121,8 @@ def _t_complexity_(self) -> 'TComplexity':

@bloq_example
def _global_phase() -> GlobalPhase:
global_phase = GlobalPhase(1j)
global_phase = GlobalPhase(exponent=0.5)
return global_phase


_GLOBAL_PHASE_DOC = BloqDocSpec(
bloq_cls=GlobalPhase,
import_line='from qualtran.bloqs.basic_gates import GlobalPhase',
examples=[_global_phase],
)
_GLOBAL_PHASE_DOC = BloqDocSpec(bloq_cls=GlobalPhase, examples=[_global_phase])
32 changes: 29 additions & 3 deletions qualtran/bloqs/basic_gates/global_phase_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,48 @@

import cirq
import numpy as np
import pytest

from qualtran import CtrlSpec
from qualtran.bloqs.basic_gates.global_phase import _global_phase, GlobalPhase
from qualtran.cirq_interop import cirq_gate_to_bloq
from qualtran.cirq_interop.t_complexity_protocol import TComplexity


def test_unitary():
random_state = np.random.RandomState(2)

for alpha in random_state.random(size=20):
for alpha in random_state.random(size=10):
coefficient = np.exp(2j * np.pi * alpha)
bloq = GlobalPhase(coefficient)
bloq = GlobalPhase(exponent=2 * alpha)
np.testing.assert_allclose(cirq.unitary(bloq), coefficient)


@pytest.mark.parametrize("cv", [0, 1])
def test_controlled(cv: int):
ctrl_spec = CtrlSpec(cvs=cv)
random_state = np.random.RandomState(2)
for alpha in random_state.random(size=10):
coefficient = np.exp(2j * np.pi * alpha)
bloq = GlobalPhase(exponent=2 * alpha).controlled(ctrl_spec=ctrl_spec)
np.testing.assert_allclose(
cirq.unitary(cirq.GlobalPhaseGate(coefficient).controlled(control_values=[cv])),
bloq.tensor_contract(),
)


def test_cirq_interop():
bloq = GlobalPhase.from_coefficient(1.0j)
gate = cirq.GlobalPhaseGate(1.0j)

circuit = bloq.as_composite_bloq().to_cirq_circuit()
assert cirq.approx_eq(circuit, cirq.Circuit(gate.on()), atol=1e-16)

assert cirq_gate_to_bloq(gate) == bloq


def test_t_complexity():
assert GlobalPhase(1j).t_complexity() == TComplexity()
assert GlobalPhase(exponent=0.5).t_complexity() == TComplexity()


def test_global_phase(bloq_autotester):
Expand Down
6 changes: 4 additions & 2 deletions qualtran/bloqs/basic_gates/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,16 @@ class ZPowGate(CirqGateAsBloqBase):
"""

exponent: SymbolicFloat = 1.0
global_shift: float = 0.0
eps: float = 1e-11
global_shift: SymbolicFloat = 0.0
eps: SymbolicFloat = 1e-11

def decompose_bloq(self) -> 'CompositeBloq':
raise DecomposeTypeError(f"{self} is atomic")

@cached_property
def cirq_gate(self) -> cirq.Gate:
if isinstance(self.global_shift, sympy.Expr):
raise TypeError(f"cirq.ZPowGate does not support symbolic {self.global_shift=}")
return cirq.ZPowGate(exponent=self.exponent, global_shift=self.global_shift)

def __pow__(self, power):
Expand Down
18 changes: 11 additions & 7 deletions qualtran/bloqs/basic_gates/su2_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qualtran.bloqs.basic_gates import GlobalPhase, Ry, ZPowGate
from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.drawing import Text, TextBox
from qualtran.symbolics import is_symbolic, SymbolicFloat
from qualtran.symbolics import is_symbolic, pi, SymbolicFloat

if TYPE_CHECKING:
import quimb.tensor as qtn
Expand Down Expand Up @@ -125,13 +125,17 @@ def _unitary_(self):
return self.rotation_matrix

def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']:
pi = sympy.pi if self.is_symbolic() else np.pi
exp = sympy.exp if self.is_symbolic() else np.exp

bb.add(GlobalPhase(coefficient=-exp(1j * self.global_shift), eps=self.eps / 4))
q = bb.add(ZPowGate(exponent=1 - self.lambd / pi, global_shift=-1, eps=self.eps / 4), q=q)
bb.add(
GlobalPhase(exponent=1 + self.global_shift / pi(self.global_shift), eps=self.eps / 4)
)
q = bb.add(
ZPowGate(exponent=1 - self.lambd / pi(self.lambd), global_shift=-1, eps=self.eps / 4),
q=q,
)
q = bb.add(Ry(angle=2 * self.theta, eps=self.eps / 4), q=q)
q = bb.add(ZPowGate(exponent=-self.phi / pi, global_shift=-1, eps=self.eps / 4), q=q)
q = bb.add(
ZPowGate(exponent=-self.phi / pi(self.phi), global_shift=-1, eps=self.eps / 4), q=q
)
return {'q': q}

def adjoint(self) -> 'SU2RotationGate':
Expand Down
2 changes: 1 addition & 1 deletion qualtran/bloqs/basic_gates/su2_rotation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_call_graph():
gate = SU2RotationGate(theta, phi, lambd, alpha, eps)
_, sigma = gate.call_graph()
assert sigma == {
GlobalPhase(-sympy.exp(1j * alpha), eps / 4): 1,
GlobalPhase(exponent=1 + alpha / pi, eps=eps / 4): 1,
ZPowGate(-phi / pi, -1, eps / 4): 1,
ZPowGate(-lambd / pi + 1, -1, eps / 4): 1,
Ry(2 * theta, eps / 4): 1,
Expand Down
4 changes: 2 additions & 2 deletions qualtran/bloqs/phase_estimation/lp_resource_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def decompose_from_registers(

# Reset ancilla to |0> state.
yield [XGate().on(flag), XGate().on(anc)]
yield GlobalPhase(1j).on()
yield GlobalPhase(exponent=0.5).on()
context.qubit_manager.qfree([flag, anc])

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
Expand All @@ -173,7 +173,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
(Ry(angle=flag_angle), 3),
(MultiControlPauli(cvs, target_gate=cirq.Z), 1),
(XGate(), 4),
(GlobalPhase(1j), 1),
(GlobalPhase(exponent=0.5), 1),
(CZPowGate(), 1),
}

Expand Down
13 changes: 5 additions & 8 deletions qualtran/bloqs/reflections/reflection_using_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@
import numpy as np
from numpy.typing import NDArray

from qualtran import bloq_example, BloqDocSpec, QBit, Register, Signature
from qualtran import Bloq, bloq_example, BloqDocSpec, CtrlSpec, QBit, Register, Signature
from qualtran._infra.gate_with_registers import (
merge_qubits,
SpecializedSingleQubitControlledGate,
total_bits,
)
from qualtran.bloqs.basic_gates.global_phase import GlobalPhase
from qualtran.bloqs.basic_gates.rotation import ZPowGate
from qualtran.bloqs.basic_gates.x_basis import XGate
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlPauli
from qualtran.resource_counting.generalizers import ignore_split_join
Expand Down Expand Up @@ -176,12 +175,10 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
if self.control_val is None:
costs.add((XGate(), 2))
if self.global_phase != 1:
if self.control_val is None:
costs.add((GlobalPhase(self.global_phase, self.eps), 1))
else:
costs.add(
(ZPowGate(exponent=float(np.angle(self.global_phase)) / np.pi, eps=self.eps), 1)
)
phase_op: Bloq = GlobalPhase.from_coefficient(self.global_phase, eps=self.eps)
if self.control_val is not None:
phase_op = phase_op.controlled(ctrl_spec=CtrlSpec(cvs=self.control_val))
costs.add((phase_op, 1))
return costs


Expand Down
Loading

0 comments on commit 37d523c

Please sign in to comment.