Module hybridq.utils.transpose
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.utils.utils import load_library
from warnings import warn
import numpy as np
import ctypes
# Define shorthand for defining functions
def _define_function(lib, fname, restype, *argtypes):
func = lib.__getattr__(fname)
func.argtypes = argtypes
func.restype = restype
return func
# Load library
_lib_swap = load_library('hybridq_swap.so')
# If library isn't loaded properly, warn
if _lib_swap is None:
warn("Cannot find C++ HybridQ core. "
"Falling back to 'numpy.transpose' instead.")
# Get c_type
_c_types_map = {
np.dtype('float32'): ctypes.c_float,
np.dtype('float64'): ctypes.c_double,
np.dtype('int32'): ctypes.c_int32,
np.dtype('int64'): ctypes.c_int64,
np.dtype('uint32'): ctypes.c_uint32,
np.dtype('uint64'): ctypes.c_uint64,
}
# Get swap core
_swap_core = {
np.dtype(t): _define_function(_lib_swap, f"swap_{t}", ctypes.c_int,
ctypes.POINTER(_c_types_map[np.dtype(t)]),
ctypes.POINTER(ctypes.c_uint32),
ctypes.c_uint, ctypes.c_uint)
for t in (t + str(b) for t in ['float', 'int', 'uint'] for b in [32, 64])
} if _lib_swap else None
def transpose(a: np.ndarray,
axes: iter[int] = None,
inplace: bool = False,
backend: any = 'numpy',
**kwargs) -> np.ndarray:
"""
"""
# Set defaults
kwargs.setdefault('force_numpy', False)
kwargs.setdefault('raise_if_hcore_fails', False)
# Select the correct backend
if backend == 'numpy':
import numpy as backend
elif backend == 'jax':
import jax.numpy as backend
else:
raise ValueError(f"Backend {backend} is not supported.")
# If not provided, axis just invert 'a'
if axes is None:
axes = np.arange(a.ndim).astype(np.uint32)[::-1]
# Convert axes to np.ndarray
else:
axes = np.asarray(axes, dtype=np.uint32)
# Check all axes have been provided
if len(axes) != a.ndim or any(x >= a.ndim for x in axes):
raise IndexError("'axes' out of range.")
# Get array
def _get(x):
# Copy reference
_x = x
# Get array
x = np.asarray(x, order='C')
# Return array and True if new
return x, x is not _x
# Get array
a, _new = _get(a)
# Check if HybridQ core can be called
_use_hcore = not kwargs['force_numpy']
# Check library has been loaded
_use_hcore &= _lib_swap != None
# Check type is available
_use_hcore &= a.dtype in _c_types_map
# Check if all axes for a have dimension 2
_use_hcore &= a.shape == (2,) * a.ndim
# For debug purposes
if not _use_hcore and not kwargs['force_numpy'] and kwargs[
'raise_if_hcore_fails']:
raise AssertionError("Cannot use HybridQ core.")
# Check if HybridQ swap can be called
if _use_hcore:
# Get number of ordered axes
n_ord = next((i for i, x in enumerate(axes) if i != x), len(axes))
# If already ordered, just return
if n_ord == len(axes):
return a
# If sufficiently small, use HybridQ swap
if 3 < len(axes) - n_ord <= 16:
# Copy if needed
if not inplace and not _new:
a = np.array(a)
# Get only unordered axes
axes = axes[n_ord:]
# Rescale axes
axes = len(a.shape) - axes[::-1] - 1
# Get pointer to a and axes
a_ptr = a.ctypes.data_as(ctypes.POINTER(_c_types_map[a.dtype]))
axes_ptr = axes.ctypes.data_as(
ctypes.POINTER(_c_types_map[axes.dtype]))
# Call library
if _swap_core[a.dtype](a_ptr, axes_ptr, len(a.shape), len(axes)):
raise ValueError("Something went wrong.")
# Return array
return a
# Otherwise, fall back to numpy.transpose
else:
# Warn
if not kwargs['force_numpy']:
warn("Fallback to 'numpy.transpose'")
return backend.transpose(a, axes)
# Otherwise, fall back to numpy.transpose
else:
# Warn
if not kwargs['force_numpy']:
warn("Fallback to 'numpy.transpose'")
# Return
return backend.transpose(a, axes)
# It should never arrive here
assert (False)
Functions
def transpose(a: np.ndarray, axes: iter[int] = None, inplace: bool = False, backend: any = 'numpy', **kwargs) ‑> np.ndarray
-
Expand source code
def transpose(a: np.ndarray, axes: iter[int] = None, inplace: bool = False, backend: any = 'numpy', **kwargs) -> np.ndarray: """ """ # Set defaults kwargs.setdefault('force_numpy', False) kwargs.setdefault('raise_if_hcore_fails', False) # Select the correct backend if backend == 'numpy': import numpy as backend elif backend == 'jax': import jax.numpy as backend else: raise ValueError(f"Backend {backend} is not supported.") # If not provided, axis just invert 'a' if axes is None: axes = np.arange(a.ndim).astype(np.uint32)[::-1] # Convert axes to np.ndarray else: axes = np.asarray(axes, dtype=np.uint32) # Check all axes have been provided if len(axes) != a.ndim or any(x >= a.ndim for x in axes): raise IndexError("'axes' out of range.") # Get array def _get(x): # Copy reference _x = x # Get array x = np.asarray(x, order='C') # Return array and True if new return x, x is not _x # Get array a, _new = _get(a) # Check if HybridQ core can be called _use_hcore = not kwargs['force_numpy'] # Check library has been loaded _use_hcore &= _lib_swap != None # Check type is available _use_hcore &= a.dtype in _c_types_map # Check if all axes for a have dimension 2 _use_hcore &= a.shape == (2,) * a.ndim # For debug purposes if not _use_hcore and not kwargs['force_numpy'] and kwargs[ 'raise_if_hcore_fails']: raise AssertionError("Cannot use HybridQ core.") # Check if HybridQ swap can be called if _use_hcore: # Get number of ordered axes n_ord = next((i for i, x in enumerate(axes) if i != x), len(axes)) # If already ordered, just return if n_ord == len(axes): return a # If sufficiently small, use HybridQ swap if 3 < len(axes) - n_ord <= 16: # Copy if needed if not inplace and not _new: a = np.array(a) # Get only unordered axes axes = axes[n_ord:] # Rescale axes axes = len(a.shape) - axes[::-1] - 1 # Get pointer to a and axes a_ptr = a.ctypes.data_as(ctypes.POINTER(_c_types_map[a.dtype])) axes_ptr = axes.ctypes.data_as( ctypes.POINTER(_c_types_map[axes.dtype])) # Call library if _swap_core[a.dtype](a_ptr, axes_ptr, len(a.shape), len(axes)): raise ValueError("Something went wrong.") # Return array return a # Otherwise, fall back to numpy.transpose else: # Warn if not kwargs['force_numpy']: warn("Fallback to 'numpy.transpose'") return backend.transpose(a, axes) # Otherwise, fall back to numpy.transpose else: # Warn if not kwargs['force_numpy']: warn("Fallback to 'numpy.transpose'") # Return return backend.transpose(a, axes) # It should never arrive here assert (False)