Module hybridq.gate.projection

Author: Salvatore Mandra (salvatore.mandra@nasa.gov)

Copyright © 2021, United States Government, as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.

The HybridQ: A Hybrid Simulator for Quantum Circuits platform is 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.

Expand source code
"""
Author: Salvatore Mandra (salvatore.mandra@nasa.gov)

Copyright © 2021, United States Government, as represented by the Administrator
of the National Aeronautics and Space Administration. All rights reserved.

The HybridQ: A Hybrid Simulator for Quantum Circuits platform is 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.
"""

from __future__ import annotations
from hybridq.gate.property import staticvars, generate
from hybridq.gate import property as pr
from hybridq.gate import BaseGate
import numpy as np


def _Projection(a: array_like,
                axes: tuple[int, ...],
                state: tuple[any, ...],
                *,
                renormalize: bool = True,
                new_a: array_like = None,
                atol: float = 1e-6):
    from hybridq.utils import aligned

    # Check number of axes is correct
    if len(axes) > a.ndim:
        raise ValueError("'axes' are not valid.")

    # Check that state and axes have the same number of dimensions
    if len(state) != len(axes):
        raise ValueError("'state' is not consistent with 'axes'.")

    # Only strings are accepted as states
    if any(x not in list('01') for x in state):
        raise ValueError(
            "Only projections to the z-basis are supported at the moment.")

    # Convert state to tuples
    state = tuple(map(int, state))

    # Get the right slices
    axes = tuple(state[axes.index(x)] if x in axes else slice(b)
                 for x, b in enumerate(a.shape))

    # Get new array if not provided
    new_a = aligned.zeros_like(a) if new_a is None else new_a

    # Compute norm
    norm = np.linalg.norm(a[axes].ravel())

    # Update new_a (if norm > atol)
    if norm > atol:
        new_a[axes] = a[axes]

    # Normalize if required
    if renormalize and norm > atol:
        new_a /= norm

    # Return projection
    return new_a


def _ProjectionGateApply(self, psi, order, renormalize: bool = True):
    from hybridq.utils import aligned

    # Check that psi is numpy array
    if not isinstance(psi, np.ndarray):
        raise ValueError("Only 'numpy.ndarray' are supported.")

    # Check dimensions
    if not 0 <= (psi.ndim - len(order)) <= 1:
        raise ValueError("'psi' is not consistent with order")

    # Check if psi is split in real and imaginary part
    complex_array = psi.ndim > len(order)

    # If complex_array, first dimension must be equal to 2
    if complex_array and (not psi.shape[0] == 2 or np.iscomplexobj(psi)):
        raise ValueError("'psi' is not valid.")

    # Get axes
    axes = tuple(order.index(q) for q in self.qubits)

    if complex_array:
        # Create a new empty array
        new_psi = aligned.zeros_like(psi)

        # Project
        _Projection(psi[0],
                    axes,
                    self.state,
                    renormalize=False,
                    new_a=new_psi[0])
        _Projection(psi[1],
                    axes,
                    self.state,
                    renormalize=False,
                    new_a=new_psi[1])

        # Renormalize if needed
        if renormalize:
            norm = np.linalg.norm(new_psi.ravel())
            if norm != 0:
                new_psi /= norm

    else:
        new_psi = _Projection(psi, axes, self.state, renormalize=renormalize)

    # Return projected
    return new_psi, order


@pr.staticvars('state')
class ProjectionGate(BaseGate):
    pass


def Projection(state: iter[any],
               qubits: iter[any] = None,
               tags: dict[any, any] = None) -> pr.BaseGate:
    """
    Generator of projectors.

    Parameters
    ----------
    state: str,
        State to project to.
    qubits: iter[any], optional
        List of qubits `Projection` is acting on.
    tags: dict[any, any], optional
        Dictionary of tags.

    See Also
    --------
    ProjectionGate, FunctionalGate
    """

    # Convert state to tuple
    state = tuple(str(state))

    # Check that state is a valid state
    if any(x not in '01' for x in state):
        raise ValueError("'state' must be a valid string of 0s and 1s.")

    # Get number of qubits
    n_qubits = len(state)

    # Check that state is consistent with n_qubits
    if len(state) != n_qubits:
        raise ValueError("'state' must be consistent with 'n_qubits'.")

    # Convert qubits to tuple
    if qubits is not None:
        qubits = tuple(qubits)

        # Check number of qubits
        if len(qubits) != n_qubits:
            raise ValueError("'qubits' has the wrong number of qubits "
                             f"(expected {n_qubits}, got {len(qubits)})")

    # Return gate
    return generate(
        'ProjectionGate', (ProjectionGate, pr.FunctionalGate, pr.QubitGate,
                           pr.TagGate, pr.NameGate),
        apply=_ProjectionGateApply,
        n_qubits=n_qubits,
        name='PROJECTION',
        state=state,
        methods=dict(__print__=lambda self:
                     {'state': (200, f"state='{''.join(self.state)}'", 0)}))(
                         qubits=qubits, tags=tags)

Functions

def Projection(state: iter[any], qubits: iter[any] = None, tags: dict[any, any] = None) ‑> pr.BaseGate

Generator of projectors.

Parameters

state : str,
State to project to.
qubits : iter[any], optional
List of qubits Projection() is acting on.
tags : dict[any, any], optional
Dictionary of tags.

See Also

ProjectionGate, FunctionalGate

Expand source code
def Projection(state: iter[any],
               qubits: iter[any] = None,
               tags: dict[any, any] = None) -> pr.BaseGate:
    """
    Generator of projectors.

    Parameters
    ----------
    state: str,
        State to project to.
    qubits: iter[any], optional
        List of qubits `Projection` is acting on.
    tags: dict[any, any], optional
        Dictionary of tags.

    See Also
    --------
    ProjectionGate, FunctionalGate
    """

    # Convert state to tuple
    state = tuple(str(state))

    # Check that state is a valid state
    if any(x not in '01' for x in state):
        raise ValueError("'state' must be a valid string of 0s and 1s.")

    # Get number of qubits
    n_qubits = len(state)

    # Check that state is consistent with n_qubits
    if len(state) != n_qubits:
        raise ValueError("'state' must be consistent with 'n_qubits'.")

    # Convert qubits to tuple
    if qubits is not None:
        qubits = tuple(qubits)

        # Check number of qubits
        if len(qubits) != n_qubits:
            raise ValueError("'qubits' has the wrong number of qubits "
                             f"(expected {n_qubits}, got {len(qubits)})")

    # Return gate
    return generate(
        'ProjectionGate', (ProjectionGate, pr.FunctionalGate, pr.QubitGate,
                           pr.TagGate, pr.NameGate),
        apply=_ProjectionGateApply,
        n_qubits=n_qubits,
        name='PROJECTION',
        state=state,
        methods=dict(__print__=lambda self:
                     {'state': (200, f"state='{''.join(self.state)}'", 0)}))(
                         qubits=qubits, tags=tags)

Classes

class ProjectionGate

Common type for all gates.

Expand source code
class ProjectionGate(BaseGate):
    pass

Ancestors