Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GlobalPhase: use exponent, and implement controlled (ZPowGate) #1117

Merged
merged 8 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading