Module hybridq.gate.measure

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 Projection, BaseGate
from hybridq.gate import property as pr
import numpy as np


def _Measure(a: array_like,
             axes: tuple[int, ...],
             *,
             renormalize: bool = True,
             new_a: array_like = None,
             get_probs_only: bool = False,
             get_state_only: bool = False):
    from hybridq.utils import transpose, aligned

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

    # Get size and shape
    shape = a.shape
    size = np.prod([shape[x] for x in axes])

    # Complete axes
    axes += tuple(x for x in range(len(shape)) if x not in axes)

    # Transpose and reshape
    a = np.reshape(transpose(a, axes), (size, np.prod(shape) // size))

    # Get probabilities
    probs = np.sum(np.real(a * a.conj()), axis=1)

    # Return only probs if required
    if get_probs_only:
        return probs

    # Select state
    state = np.random.choice(size, p=probs)

    # Return only state if required
    if get_state_only:
        return state

    # Create an empty state if not provided
    new_a = aligned.zeros_like(a) if new_a is None else new_a

    # Assign to new_a (renormalize if required)
    new_a[state] = a[state] / np.linalg.norm(
        a[state]) if renormalize else a[state]

    # Transpose back
    new_a = transpose(np.reshape(new_a, shape),
                      [axes.index(x) for x in range(len(axes))])

    # Return new_a
    return new_a


def _MeasureGateApply(self, psi, order, renormalize: bool = True, **kwargs):
    from hybridq.utils.dot import to_complex, to_complex_array
    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.")

    # Set default
    kwargs.setdefault('get_probs_only', False)
    kwargs.setdefault('get_state_only', False)

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

    # If complex_array, just merge
    if complex_array:
        psi = to_complex(psi[0], psi[1])

    # If only probabilities are required, just return them
    if kwargs['get_probs_only']:
        return _Measure(psi, axes, get_probs_only=True)

    # If only the sampled state is required, just return it
    elif kwargs['get_state_only']:
        return _Measure(psi, axes, get_state_only=True)

    # Otherwise, return state with the right probability
    else:
        # Get state
        psi = _Measure(psi, axes, renormalize=renormalize)

        # If complex_array, split in real and imaginary part
        if complex_array:
            psi = to_complex_array(psi)

        # Return state and order
        return psi, order


def Measure(qubits: iter[any] = None,
            n_qubits: int = None,
            tags: dict[any, any] = None) -> pr.BaseGate:
    """
    Generator of measurement gates.

    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
    --------
    MeasureGate, FunctionalGate
    """

    # Check
    if qubits is None and n_qubits is None:
        raise ValueError("Either 'qubits' or 'n_qubits' must be specified.")

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

    # Convert n_qubits to int
    if n_qubits is not None:
        n_qubits = int(n_qubits)
    else:
        n_qubits = len(qubits)

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

    # Return gate
    return generate(
        'MeasureGate',
        (BaseGate, pr.FunctionalGate, pr.QubitGate, pr.TagGate, pr.NameGate),
        apply=_MeasureGateApply,
        n_qubits=n_qubits,
        name='MEASURE',
    )(qubits=qubits, tags=tags)

Functions

def Measure(qubits: iter[any] = None, n_qubits: int = None, tags: dict[any, any] = None) ‑> pr.BaseGate

Generator of measurement gates.

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

MeasureGate, FunctionalGate

Expand source code
def Measure(qubits: iter[any] = None,
            n_qubits: int = None,
            tags: dict[any, any] = None) -> pr.BaseGate:
    """
    Generator of measurement gates.

    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
    --------
    MeasureGate, FunctionalGate
    """

    # Check
    if qubits is None and n_qubits is None:
        raise ValueError("Either 'qubits' or 'n_qubits' must be specified.")

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

    # Convert n_qubits to int
    if n_qubits is not None:
        n_qubits = int(n_qubits)
    else:
        n_qubits = len(qubits)

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

    # Return gate
    return generate(
        'MeasureGate',
        (BaseGate, pr.FunctionalGate, pr.QubitGate, pr.TagGate, pr.NameGate),
        apply=_MeasureGateApply,
        n_qubits=n_qubits,
        name='MEASURE',
    )(qubits=qubits, tags=tags)