# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Symplectic functions."""
from collections import deque
from typing import Any, List, Optional, Tuple, Union
import numpy as np
from qiskit import QiskitError
from qiskit_qec.linear import matrix as mt
# pylint: disable=invalid-name
[docs]
def all_commute(matrix: np.ndarray) -> bool:
r"""Determines if each possible pair of different rows of the
GF(2) symplectic matrix have zero symplectic product. If the rows represent
Pauli operators then the this method determines if the operators
defined by the matrix generate an abelian subgroup.
Args:
matrix: Input GF(2) symplectic matrix
Returns:
True if operators mutually commute - have zero symplectic product
Examples:
>>> matrix = numpy.array([[1,0,0,1,0,0,1,0],
[0,1,1,1,0,0,0,1],
[1,1,1,0,1,0,0,0],
[1,0,0,1,0,1,0,1]], dtype=numpy.bool_)
>>> all_commute(matrix)
False
>>> matrix = np.array([[1,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[1,0,0,0,0,0,0,1]], dtype=numpy.bool_)
>>> all_commute(matrix)
True
"""
if matrix.shape[0] == 1:
return True
test_mat = np.asarray(symplectic_product(matrix, matrix))
return not test_mat.any()
# ---------------------------------------------------------------
[docs]
def symplectic_product(mat1: np.ndarray, mat2: np.ndarray) -> Union[int, np.ndarray]:
r"""Returns the symplectic product of two GF(2) symplectic matrices.
Let math:'A', math:'B' be two GF(2) symplectic matrices of width math:'2m',
then the symplectic product is defined as
.. math::
(A,B) = A \cdot \Lambda_n \cdot B^T.
Args:
mat1, mat2: Input GF(2) symplectic matrixes
Returns:
Symplectic product of mat1 and mat2
Raises:
QiskitError: Input matrices/vectors must be GF(2) symplectic matrices/vectors
QiskitError: Input matrices must have the same number of dimensions
QiskitError: Input matrices must be 1 or 2 dimensional
Examples:
>>> mat1 = numpy.array([[1,0,1,1],[0,1,1,0]], dtype=numpy.bool_)
>>> mat2 = numpy.array([[0,1,1,1],[0,0,1,0]], dtype=numpy.bool_)
>>> p = symplectic_product(mat1, mat2)
>>> p.astype(int)
array([[0, 1], [1, 0]]
See Also:
_symplectic_product_vv, _symplectic_product_dense
"""
mat1_np_array = np.array(mat1, dtype=np.int8)
mat2_np_array = np.array(mat2, dtype=np.int8)
if not is_symplectic_form(mat1) or not is_symplectic_form(mat2):
raise QiskitError("Input matrices/vectors must be GF(2) symplectic matrices/vectors")
if not mat1_np_array.ndim == mat2_np_array.ndim:
raise QiskitError(
f"Input matrices must have the \
same dimensions: {mat1_np_array.ndim} is \
not equal to {mat2_np_array.ndim}"
)
if mat1_np_array.ndim == 1:
if not mat1_np_array.shape[0] == mat2_np_array.shape[0]:
raise QiskitError(
f"Input vectors must have the same \
dimensions: {mat1_np_array.shape[0]} not equal \
to {mat2_np_array.shape[0]}"
)
if mat1_np_array.dtype == bool:
return _symplectic_product_vv_boolean(
mat1_np_array, mat2_np_array, mat1_np_array.shape[0] >> 1
)
return _symplectic_product_vv(mat1_np_array, mat2_np_array, mat1_np_array.shape[0] >> 1)
elif mat1_np_array.ndim == 2:
return _symplectic_product_dense(mat1_np_array, mat2_np_array)
else:
raise QiskitError(
f"Input matrices must be 1 or 2 dimensional:\
{mat1_np_array.ndim}, {mat2_np_array.ndim}"
)
def _symplectic_product_vv(vec1: np.ndarray, vec2: np.ndarray, n: int) -> int:
r"""Finds the sympletic product or two GF(2) symplectic vectors of
length 2n: vec1 . Lambda . vec2^T where
lambda = [0 I]
[I 0]
Warning: This method requires integer components. Python bool and nump bool_
type will give incorrect results. Use _symplectic_product_vv_boolean or the
more general symplectic_product
Args:
vec1, vec2: Input GF(2) symplectic vectors
n: Input size, half of the symplectic vector length
Returns:
Symplectic product of vec1 and vec2
Examples:
>>> a = np.array([1,0,0,0,1,1,1,0,1,0])
>>> b = np.array([1,1,1,1,0,0,1,0,1,1])
>>> _symplectic_product_vv(a, b, 5)
0
See Also:
symplectic_product, _symplectic_product_dense
Notes:
If changing the method please make sure that the new method
is faster. Note that this method is faster if the vectors are
numpy arrays with dtype=int8
"""
assert vec1.dtype != bool
r = 0
for i in range(n):
r += vec1[i] * vec2[n + i] + vec1[n + i] * vec2[i]
return r % 2
def _symplectic_product_vv_boolean(vec1: np.ndarray, vec2: np.ndarray, n: int) -> int:
r"""Finds the sympletic product or two GF(2) (boolean types) symplectic vectors of
length 2n: vec1 . Lambda . vec2^T where
lambda = [0 I]
[I 0]
Warning: This method requires boolean components.
Args:
vec1, vec2: Input GF(2) symplectic vectors
n: Input size, half of the symplectic vector length
Returns:
out: Symplectic product of vec1 and vec2
Examples:
>>> a = np.array([1,0,0,0,1,1,1,0,1,0], dtype=numpy.bool_)
>>> b = numpy.array([1,1,1,1,0,0,1,0,1,1], dtype=numpy.bool_)
>>> _symplectic_product_vv_boolean(a, b, 5)
0
See Also:
symplectic_product, _symplectic_product_dense, _symplectic_product_vv
Notes:
If changing the method please make sure that the new method
is faster. Note that this method is faster if the vectors are
numpy arrays with dtype=int8
"""
assert vec1.dtype == bool
r = False
for i in range(n):
r ^= vec1[i] & vec2[n + i] ^ vec1[n + i] & vec2[i]
return int(r)
def _symplectic_product_dense(mat1: np.ndarray, mat2: np.ndarray) -> Union[int, np.ndarray]:
r"""Returns the symplectic product of two GF(2) symplectic matrices.
Let math:'A', math:'B' be two GF(2) (as integer not boolean) symplectic matrices of width math:'2m',
then the symplectic product is defined as
.. math::
(A,B) = A \cdot \Lambda_n \cdot B^T.
Args:
mat1, mat2: Input GF(2) symplectic matrixes
Returns:
Symplectic product of mat1 and mat2
Examples:
>>> mat1 = numpy.array([[1,0,1,1],[0,1,1,0]], dtype=numpy.bool_)
>>> mat2 = numpy.array([[0,1,1,1],[0,0,1,0]], dtype=numpy.bool_)
>>> p = _symplectic_product_dense(mat1, mat2)
>>> p.astype(int)
array([[0, 1], [1, 0]]
See Also:
_symplectic_product_vv, symplectic_product
"""
assert mat1.dtype != bool
m1, m2 = np.hsplit(mat1, 2) # pylint: disable=unbalanced-tuple-unpacking
result = np.hstack((m2, m1)).dot(mat2.transpose()) % 2
if result.size == 1:
return int(result.item())
else:
return result
# ---------------------------------------------------------------
[docs]
def make_commute_hyper(
a: np.ndarray,
x: np.ndarray,
z: np.ndarray,
arange: Optional[np.ndarray] = None,
xrange: Optional[np.ndarray] = None,
zrange: Optional[np.ndarray] = None,
) -> np.ndarray:
r"""Makes an element(s) commute with hyperbolic pair(s)
Let a = [a_0,...,a_(k-1)] where a_i are GF(2) symplectic vectors. Let
x = [x_0,...,x_(l-1)] and z =[z_0,...,z_(l-1)] where x_i and z_i are
GF(2) symplectic vectors such that (x_i,z_i) are hyerbolic pairs from
the hyperbolic a basis <x_0,...,x_(l-1),z_0,...,x_(l-1)>. It is assumed
that {a_0,...,a_(k-1),x_0,...,x_(l-1),z_0,...,x_(l-1)} is an independent
set of symplectic vectors.
This method returns a set of vectors b = [b_0,...,b_(k-1)] such that
1) b_0, ..., b_(k-1) each have zero symplectic product with each of the
hyperbolic vectors x_0,...,x_(l-1),z_0,...,x_(l-1)
2) span(b_i, x_j,z_j) = span(a_i, x_j,z_j)
for i=0,..,k-1 and j=0,...,l-1
If the symplectic vectors are considered as Pauli operators then the
method returns a set of operators [op_(b_0),...,op_(b_(k-1))] such that
1) op_(b_0), ..., op_(b_(k-1)) each commute with the each of the
hyperbolic operators op_(x_0),...,op_(x_(l-1)),op_(z_0),...,op_(x_(l-1))
2) <op_(b_i), op_(x_j),op_(z_j)> = <op_(a_i), op_(x_j),op_(z_j)>
for each i=0,...,k-1 and j=0,...,l-1
Args:
a: Input GF(2) symplectic vectors
x: GF(2) hyperbolic pair vector
z: GF(2) hyperbolic pair vector
arange (optional): range of indices from a to make commute. Defaults to None.
xrange (optional): range of indices from x to use. Defaults to None.
zrange (optional): range of indices from z to use. Defaults to None.
Raises:
QiskitError: Input matrices/vectors must bf GF(2) symplectic matrices/vectors
QiskitError: Input range is not iterable")
QiskitError: Input matrices/vectors must have the same number of columns/length
Returns:
GF(2) symplectic vectors that commute with the given hyperbolic pairs
Examples:
>>> a = numpy.array([1,1,1,0,0,0],dtype=numpy.bool_)
>>> x = numpy.array([0,0,1,0,0,0],dtype=numpy.bool_)
>>> z = numpy.array([0,0,0,0,0,1],dtype=numpy.bool_)
>>> a = make_commute_hyper(a, x, z)
>>> a.astype(int)
array([1, 1, 0, 0, 0, 0])
>>> a = numpy.array([1,1,1,0,0,0,0,0], dtype=numpy.bool_)
>>> x = numpy.array([[0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1,0,0],[0,0,0,0,0,0,1,0]], dtype=numpy.bool_)
>>> xrange = [0,1]
>>> zrange = [0,1]
>>> a = make_commute_hyper(a, x, z, xrange = xrange, zrange=zrange)
>>> a.astype(int)
array([1, 0, 0, 0, 0, 0, 0, 0])
>>> a = numpy.array([[1,1,1,0,0,0,0,0],[0,1,1,0,0,0,0,0]], dtype=numpy.bool_) # X1X2X3, X2X3
>>> x = numpy.array([0,1,0,0,0,0,0,0], dtype=numpy.bool_) # X2
>>> z = numpy.array([0,0,0,0,0,1,0,0], dtype=numpy.bool_) # Z2
>>> arange = [0,1]
>>> a = make_commute_hyper(a, x, z, arange)
>>> a.astype(int)
array([[1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0]])
>>> a = numpy.array([[1,1,1,0,0,0,0,0],[0,1,1,1,0,0,0,0]], dtype=numpy.bool_)
>>> x = numpy.array([[0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1,0,0], [0,0,0,0,0,0,1,0]], dtype=numpy.bool_)
>>> arange = [0,1]
>>> a = make_commute_hyper(a, x, z, arange)
>>> a.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0]])
See Also:
_make_commute_hyper
"""
if not (is_symplectic_form(a) and is_symplectic_form(x) and is_symplectic_form(z)):
raise QiskitError("Input matrices/vectors must be GF(2) symplectic matrices/vectors")
def make_list(srange):
if srange is not None:
try:
srange = list(srange)
except TypeError as terror:
raise QiskitError(f"Input range {srange} is not iterable") from terror
return srange
arange = make_list(arange)
xrange = make_list(xrange)
zrange = make_list(zrange)
a = np.array(a)
# if 'a' is a vector then return result as a vector otherwise return as a matrix
squeeze = not bool(a.ndim - 1)
a = np.atleast_2d(a)
x = np.atleast_2d(np.array(x))
z = np.atleast_2d(np.array(z))
if not a.shape[1] == x.shape[1] == z.shape[1]:
raise QiskitError("Input matrices/vectors must have the same number of columns/length")
return _make_commute_hyper(a, x, z, arange, xrange, zrange, squeeze)
# ---------------------------------------------------------------
def _make_commute_hyper(
a: np.ndarray,
x: np.ndarray,
z: np.ndarray,
arange: Optional[np.ndarray] = None,
xrange: Optional[np.ndarray] = None,
zrange: Optional[np.ndarray] = None,
squeeze: bool = True,
) -> np.ndarray:
r"""Makes an element(s) commute with hyperbolic pair(s)
Let a = [a_0,...,a_(k-1)] where a_i are GF(2) symplectic vectors. Let
x = [x_0,...,x_(l-1)] and z =[z_0,...,z_(l-1)] where x_i and z_i are
GF(2) symplectic vectors such that (x_i,z_i) are hyerbolic pairs from
the hyperbolic a basis <x_0,...,x_(l-1),z_0,...,x_(l-1)>. It is assumed
that {a_0,...,a_(k-1),x_0,...,x_(l-1),z_0,...,x_(l-1)} is an independent
set of symplectic vectors.
This method returns a set of vectors b = [b_0,...,b_(k-1)] such that
1) b_0, ..., b_(k-1) each have zero symplectic product with each of the
hyperbolic vectors x_0,...,x_(l-1),z_0,...,x_(l-1)
2) span(b_i, x_j,z_j) = span(a_i, x_j,z_j)
for i=0,..,k-1 and j=0,...,l-1
If the symplectic vectors are considered as Pauli operators then the
method returns a set of operators [op_(b_0),...,op_(b_(k-1))] such that
1) op_(b_0), ..., op_(b_(k-1)) each commute with the each of the
hyperbolic operators op_(x_0),...,op_(x_(l-1)),op_(z_0),...,op_(x_(l-1))
2) <op_(b_i), op_(x_j),op_(z_j)> = <op_(a_i), op_(x_j),op_(z_j)>
for each i=0,...,k-1 and j=0,...,l-1
Args:
a: Input GF(2) symplectic vectors
x,z: GF(2) hyperbolic pair vectors
arange (optional): range of indices from a to make commute. Defaults to None.
xrange (optional): range of indices from x to use. Defaults to None.
zrange (optional): range of indices from z to use. Defaults to None.
squeeze (optional): squeeze = True will return a vector if a vector results
Returns:
GF(2) symplectic vectors that commute with the given hyperbolic pairs
Examples:
>>> a = numpy.array([[1,1,1,0,0,0]],dtype=numpy.bool_)
>>> x = numpy.array([[0,0,1,0,0,0]],dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1]],dtype=numpy.bool_)
>>> a = _make_commute_hyper(a, x, z, squeeze=True)
>>> a.astype(int)
array([1, 1, 0, 0, 0, 0])
>>> a = numpy.array([[1,1,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> x = numpy.array([[0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1,0,0],[0,0,0,0,0,0,1,0]], dtype=numpy.bool_)
>>> xrange = [0,1]
>>> zrange = [0,1]
>>> a = _make_commute_hyper(a, x, z, xrange = xrange, zrange=zrange, squeeze=False)
>>> a.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0]])
>>> a = numpy.array([[1,1,1,0,0,0,0,0],[0,1,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> x = numpy.array([[0,1,0,0,0,0,0,0]], dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1,0,0]], dtype=numpy.bool_)
>>> arange = [0,1]
>>> a = _make_commute_hyper(a, x, z, arange)
>>> a.astype(int)
array([[1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0]])
>>> a = numpy.array([[1,1,1,0,0,0,0,0],[0,1,1,1,0,0,0,0]], dtype=numpy.bool_)
>>> x = numpy.array([[0,1,0,0,0,0,0,0],[0,0,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> z = numpy.array([[0,0,0,0,0,1,0,0],[0,0,0,0,0,0,1,0]], dtype=numpy.bool_)
>>> arange = [0,1]
>>> a = _make_commute_hyper(a, x, z, arange)
>>> a.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0]])
See Also:
make_commute_hyper
"""
# Assuming that a, x, y are all 2d numpy arrays
a = a.copy()
if arange is None:
arange = range(a.shape[0])
if xrange is None:
xrange = range(x.shape[0])
if zrange is None:
zrange = range(z.shape[0])
for i, j in zip(xrange, zrange):
for k in arange:
# a[k] = _commute_with_pair(a[k], x[i], z[j])
num_qubits = x[i].shape[0] >> 1
if _symplectic_product_vv(a[k].astype(int), x[i].astype(int), num_qubits):
a[k] = a[k] ^ z[j]
if _symplectic_product_vv(a[k].astype(int), z[j].astype(int), num_qubits):
a[k] = a[k] ^ x[i]
if squeeze:
a = np.squeeze(a)
return a
# ---------------------------------------------------------------
[docs]
def locate_hyper_partner(
matrix: np.ndarray, vector: np.ndarray
) -> Union[None, Tuple[np.ndarray, int]]:
"""Locate a hyperbolic/noncommutative parter
Let [m_0,...,m_(k-1)] be the input search matrix and v be the input vector.
This method search through the vectors m_i to locate a (hyperbolic) vector that has
a symplectic product with v of 1. If a hyperbolic partner 'av' is found then it
and its index in the search matrix is turned as a tuple (av, index). If no such
vector exists then None value is returned.
Args:
matrix: GF(2) symplectic matrix (for search)
vector: GF(2) symplectic vector to find a hyperbolic pair for
Raises:
QiskitError: Input matrix must be a GF(2) symplectic matrix
and input vector must be a GF(2) symplectic vector
QiskitError: Input {matrix} must be a 2 dimensional array
QiskitError: Input {vector} must be a 1 dimensional array
QiskitError: Input matrix and vector must have the same number
of columns/length
Returns:
Tuple of the found hyperbolic partner (av) and its index in the
search matrix. 'None' if no hyperbolic partner is found.
Examples:
>>> matrix = numpy.array([[1,0,1,0,0,0,0,0],[0,1,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> vector = numpy.array([0,0,0,0,0,1,0,0], dtype=numpy.bool_)
>>> av, index = locate_hyper_partner(matrix, vector)
>>> av.astype(int)
array([0, 1, 1, 0, 0, 0, 0, 0])
>>> index
1
See Also:
_locate_hyper_partner, build_hyper_partner, _build_hyper_partner
"""
if not (is_symplectic_form(matrix) and is_symplectic_vector_form(vector)):
raise QiskitError(
f"Input {matrix} must be a GF(2) symplectic matrix\
and input {vector} must be a GF(2) symplectic vector"
)
if not matrix.ndim == 2:
raise QiskitError(f"Input {matrix} must be a 2 dimensional array")
if not vector.ndim == 1:
raise QiskitError(f"Input {vector} must be a 1 dimensional array")
if not matrix.shape[1] == vector.shape[0]:
raise QiskitError(
f"Input matrix and vector must have the same number \
of columns/length {matrix.shape[1]}!={vector.shape[0]}"
)
return _locate_hyper_partner(matrix, vector)
def _locate_hyper_partner(matrix: np.ndarray, vector: np.ndarray) -> Union[None, np.ndarray]:
"""Locate a hyperbolic/noncommutative parter
Let [m_0,...,m_(k-1)] be the input search matrix and v be the input vector.
This method search through the vectors m_i to locate a (hyperbolic) vector that has
a symplectic product with v of 1. If a hyperbolic partner 'av' is found then it
and its index in the search matrix is turned as a tuple (av, index). If no such
vector exists then None value is returned.
Args:
matrix: GF(2) symplectic matrix (for search)
vector: GF(2) symplectic vector to find a hyperbolic pair for
Returns:
Tuple of the found hyperbolic partner (av) and its index in the
search matrix. 'None' if no hyperbolic partner is found.
Examples:
>>> matrix = numpy.array([[1,0,1,0,0,0,0,0],[0,1,1,0,0,0,0,0]], dtype=numpy.bool_)
>>> vector = numpy.array([0,0,0,0,0,1,0,0], dtype=numpy.bool_)
>>> av, index = _locate_hyper_partner(matrix, vector)
>>> av.astype(int)
array([0, 1, 1, 0, 0, 0, 0, 0])
>>> index
1
See Also:
locate_hyper_partner, build_hyper_partner, _build_hyper_partner
"""
n = matrix.shape[1] >> 1
for index, item in enumerate(matrix):
if _symplectic_product_vv(item.astype(int), vector.astype(int), n) == 1:
return (item.copy(), index)
return None
# ---------------------------------------------------------------
[docs]
def build_hyper_partner(matrix, index: int) -> np.ndarray:
"""Builds an independent hyperbolic partner for the input vector indexed
Let the input matrix be [m_0,...,m_(k-1)] and v = m_index. It is assumed
that the vectors m_0,...,m_(k-1) have a zero pairwise
symplectic product (i.e. represent a set of Pauli operators that pairwise
commmute). This method will find a GF(2) symplectic vector that
has zero symplectic product with each m_i != v and a symplectic product of 1
with the vector v. If the vectors represent Pauli operators then this
method will find a Pauli operator that commute with the operators
represented by the vectors m_i != v and that anticommutes with the Pauli
operator that is represented by v.
Args:
matrix (np.array, SimplecticMatrix): GF(2) symplectic matrix representing a set of independent
commuting generators
index: index of generator to build a hyperbolic partner for
Raises:
QiskitError: Input matrix must be a GF(2) symplectic matrix
QiskitError: Input matrix must represent a set of commuting operators
QiskitError: Input matrix does not represent a set of independent
operators, it does not have have full rank
QiskitError: Input index out or range
Returns:
a hyperbolic partner for the given vector wrt the set of commuting
generators
Examples:
>>> matrix = numpy.array(
[[1,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> av = build_hyper_partner(matrix, 0)
>>> av.astype(int)
array([0, 0, 0, 0, 1, 0, 0, 0])
See Also:
_build_hyper_partner, locate_hyper_partner, _locate_hyper_partner
Notes: This method is the implementation of Proposition 10.4 from Nielsen
and Chuang's Quantum Computation and Quantum Information
"""
matrix = np.atleast_2d(np.array(matrix, dtype=bool))
if not is_symplectic_form(matrix):
raise QiskitError(f"Input {matrix} must be a GF(2) symplectic matrix")
# matrix -> all associated operators must commute
if not all_commute(matrix):
raise QiskitError("Input matrix must represent a set of commuting operators")
rank_ = mt.rank(matrix)
if rank_ != matrix.shape[0]:
raise QiskitError(
f"Input matrix does not represent a set of independent \
operators, it does not have have full rank: {rank_}"
)
if index not in range(matrix.shape[1] >> 1):
raise QiskitError(f"Input index out or range: {index}>={matrix.shape[1]>>1}")
return _build_hyper_partner(matrix, index)
def _build_hyper_partner(matrix, index: int) -> np.ndarray:
"""Builds an independent hyperbolic partner for the input vector indexed
Let the input matrix be [m_0,...,m_(k-1)] and v = m_index. It is assumed
that the vectors m_0,...,m_(k-1) have a zero pairwise
symplectic product (i.e. represent a set of Pauli operators that pairwise
commmute). This method will find a GF(2) symplectic vector that
has zero symplectic product with each m_i != v and a symplectic product of 1
with the vector v. If the vectors represent Pauli operators then this
method will find a Pauli operator that commute with the operators
represented by the vectors m_i != v and that anticommutes with the Pauli
operator that is represented by v.
Args:
matrix: GF(2) symplectic matrix representing a set of independent
commuting generators
index: index of generator to build a hyperbolic partner for
Returns:
a hyperbolic partner for the given vector wrt the set of commuting
generators
Examples:
>>> matrix = numpy.array(
[[1,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> av = _build_hyper_partner(matrix, 0)
>>> av.astype(int)
array([0, 0, 0, 0, 1, 0, 0, 0])
See Also:
build_hyper_partner, locate_hyper_partner, _locate_hyper_partner
Notes: This method is the implementation of Proposition 10.4 from Nielsen
and Chuang's Quantum Computation and Quantum Information
"""
nrows = matrix.shape[0]
ncols = matrix.shape[1]
_lambda = mt.create_lambda_matrix(ncols >> 1)
slambda = np.matmul(matrix, _lambda)
heads, _, transform_mat, _ = mt._rref_complete(slambda)
e_index = np.zeros(nrows, dtype=bool)
e_index[index] = True
trans_e_index = np.matmul(transform_mat, e_index)
pivot = 0
result = np.zeros(ncols, dtype=bool)
for i in range(ncols):
if heads[i] == 1:
result[i] = trans_e_index[pivot]
pivot += 1
return result
# ---------------------------------------------------------------
[docs]
def symplectic_gram_schmidt(
a: np.ndarray, x: Optional[np.ndarray] = None, z: Optional[np.ndarray] = None
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Applies the sympletic Gram-Schmidt process to the input matrix
Apply the symplectic GramSchmidt process to the input symplectic matrix. Resulting
hyperbolic pairs are added to x and z arrays. Elements of the center will be added to the
center array.
Args:
a: Symplectic matrix
x (optional): GF(2) Symplectic matrices representing hyperbolic pairs to
z (optional): GF(2) Symplectic matrices representing hyperbolic pairs to
build upon. Default is None.
Raises:
QiskitError Input matric not a GF(2) symplectic matrix
QiskitError: Input hyperbolic array x is not a GF(2) sympletic matrix
QiskitError: Input hyperbolic array z is not a GF(2) sympletic matrix
QiskitError: Input hyperbolic arrays have different dimensions
QiskitError: Input hyperbolic matrices do not represent a hyperbolic basis
Returns:
Center array and hyperbolic pairs split accross x and z
Examples:
>>> a = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> center_, x, z = symplectic_gram_schmidt(a)
>>> center_.astype(int)
array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]])
>>> x.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0]])
>>> z.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1]])
Also See:
_symplectic_gram_schmidt
TODO: Add an example that shows using the optional x and z arrays
"""
a = np.atleast_2d(np.array(a))
if not is_symplectic_matrix_form(a):
raise QiskitError("Input matrix not a GF(2) symplectic matrix")
if x is None:
x = []
else:
x = np.atleast_2d(x)
x = list(x)
if not is_symplectic_vector_form(x[0]):
raise QiskitError("Input hyperbolic array x is not a GF(2) sympletic matrix")
if z is None:
z = []
else:
z = np.atleast_2d(z)
z = list(z)
if not is_symplectic_vector_form(z[0]):
raise QiskitError("Input hyperbolic array z is not a GF(2) sympletic matrix")
if not len(x) == len(z):
raise QiskitError("Input hyperbolic arrays have different dimensions")
if len(x) > 0 and x[0].shape[0] != z[0].shape[0]:
raise QiskitError("Input hyperbolic arrays have different dimensions")
if x != []:
if not is_hyper_form(x, z):
raise QiskitError("Input hyperbolic matrices do not represent a hyperbolic basis")
return _symplectic_gram_schmidt(a, x, z)
def _symplectic_gram_schmidt(
a: np.ndarray, x: List[np.ndarray], z: List[np.ndarray]
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Applies the sympletic Gram-Schmidt process to the input matrix
Apply the symplectic GramSchmidt process to the input symplectic matrix. Resulting
hyperbolic pairs are added to x and z arrays. Elements of the center will we added to the
center array.
Args:
a: Symplectic matrix
x, z (optional): GF(2) Symplectic matrices representing hyperbolic pairs to
build upon. Default is None.
Raises:
QiskitError Input matric not a GF(2) symplectic matrix
QiskitError: Input hyperbolic array x is not a GF(2) sympletic matrix
QiskitError: Input hyperbolic array z is not a GF(2) sympletic matrix
QiskitError: Input hyperbolic arrays have different dimensions
Returns:
Center array and hyperbolic pairs split accross x and z
Examples:
>>> a = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> center_, x, z = _symplectic_gram_schmidt(a, [], [])
>>> center_.astype(int)
array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]])
>>> x.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0]])
>>> z.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1]])
Also See:
_symplectic_gram_schmid
TODO: Add an example that shows using the optional x and z arrays
"""
a = a.copy()
a_view = a
center_ = []
while a_view.shape[0] > 0:
elem = a_view[0]
# Remove elem from a_view
a_view = a_view[1:]
try:
elem_p, index = _locate_hyper_partner(a_view, elem)
x.append(elem)
z.append(elem_p)
# Revove elem_p from a_view
temp_view = a_view[:-1]
temp_view[index:] = a_view[index + 1 :]
a_view = temp_view
a_view = make_commute_hyper(a_view, elem, elem_p)
# TODO: Change the above line to use the faster version
# a_view = _make_elements_commute_with_hyper_pair(
# a_view, range(a_view.shape[0]), elem, elem_p
# )
except TypeError:
if elem.any():
center_.append(elem)
x = np.asarray(x)
z = np.asarray(z)
if len(center_) == 0:
center_ = np.zeros(shape=(1, x.shape[1]), dtype=np.bool_)
else:
center_ = np.asarray(center_)
return center_, x, z
# ---------------------------------------------------------------
def count_num_y(matrix: np.ndarray, scalar: bool = True) -> Union[np.ndarray, int]:
"""Returns the number of positions with 1's in k and n+k positions
for matrices/vectors of width 2n for all k
Args:
matrix: Input GF(2) symplectic matrix/vector
scalar: If scalar is True and a vector is input then a scalar will
be output else a vector will be output. Default is True
Raises:
QiskitError: Input matrix/vector not a GF(2) symplectic matrix
Returns:
result: number of positions with 1's in k and n+k positions
for matrices/vectors of width 2n for all k.
Examples:
>>> a = np.array([1,0,1,1], dtype=np.bool_)
>>> count_num_y(a)
1
>>> a = np.array([1,0,1,1], dtype=np.bool_)
>>> count_num_y(a, scalar=False)
array([1])
>>> b = np.array([[1,0,1,1],[0,0,1,1]], dtype=np.bool_)
>>> count_num_y(b)
array([1,0])
"""
matrix = np.atleast_2d(matrix)
if not is_symplectic_form(matrix):
raise QiskitError("Input matrix/vector not a GF(2) symplectic matrix")
num_qubits = matrix.shape[1] >> 1
result = _count_num_y(matrix, num_qubits)
if scalar and matrix.shape[0] == 1:
return result[0]
else:
return result
def _count_num_y(matrix: np.ndarray, n: int) -> np.ndarray:
"""Returns the number of positions with 1's in k and n+k positions
for matrices/vectors of width 2n for all k
Args:
matrix (np.ndarray): Input GF(2) symplectic matrix/vector
n (int): half of the number of columns of input matrix
Returns:
np.ndarray: number of positions with 1's in k and n+k positions
for matrices/vectors of width 2n for all k.
Examples:
>>> a = np.array([1,0,1,1], dtype=np.bool_)
>>> _count_num_y(a)
array([1])
>>> b = np.array([[1,0,1,1],[0,0,1,1]], dtype=np.bool_)
>>> count_num_y(a, scalar=False)
array([1,0])
"""
return np.sum(np.logical_and(matrix[:, :n], matrix[:, n:]), axis=1, dtype=int)
# ---------------------------------------------------------------
# ---------------------------------------------------------------
[docs]
def is_center(cntr: np.ndarray, matrix: np.ndarray) -> bool:
"""Does the input center matrix represent the center of the supplied matrix?
Let op(cntr) = [op(c_0),op(c_1),...,op(c_(k-1))] be the Pauli operators
represented by the input center_matrix. Let op(matrix) = [op(m_0),op(m_1),...,op(m_(t-1))]
be the Pauli operators represented by the input matrix. This method
returns True if <op(cntr)> = Z(<op(matrix)>) and False otherwise.
Args:
cntr (np.ndarray): Generators of center to be checked
matrix (np.ndarray): Generators of full group
Raises:
QiskitError: Not all inputs are not GF(2) symplectic matrices/vectors
Returns:
True if <op(cntr)> = Z(<op(matrix)>), False otherwise
Examples:
>>> matrix = numpy.array(
[[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> cntr = numpy.array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]], dtype=numpy.bool_)
>>> is_center(cntr, matrix)
True
"""
matrix = np.atleast_2d(np.array(matrix))
cntr = np.atleast_2d(np.array(cntr))
if not (is_symplectic_matrix_form(cntr) and is_symplectic_matrix_form(matrix)):
raise QiskitError("Not all inputs are not GF(2) symplectic matrices")
cal_center = center(matrix)
return is_same_span(cntr, cal_center)
# ---------------------------------------------------------------
[docs]
def is_same_span(matrix1: np.ndarray, matrix2: np.ndarray) -> bool:
"""Does span(rows of matrix1) = span(rows of matrix2)?
Args:
matrix1: First set of vectors
matrix2: Second set of vectors
Returns:
True if span(rows of matrix1) = span(rows of matrix2). False otherwise
Raises:
QiskitError: Inpiut matrices must by GF(2) symplectic matrices
Examples:
>>> matrix1 = numpy.array([[1,1,0,0],[0,0,1,0]], dtype=numpy.bool_)
>>> matrix2 = numpy.array([[1,1,1,0],[0,0,1,0]], dtype=numpy.bool_)
>>> is_same_span(matrix1, matrix2)
True
>>> matrix1 = numpy.array([[1,1,0,0],[0,0,1,0]], dtype=numpy.bool_)
>>> matrix2 = numpy.array([[1,1,1,0],[0,0,0,1]], dtype=numpy.bool_)
>>> is_same_span(matrix1, matrix2)
False
"""
matrix1 = np.atleast_2d(np.array(matrix1))
matrix2 = np.atleast_2d(np.array(matrix2))
if not (is_symplectic_form(matrix1) and is_symplectic_form(matrix2)):
raise QiskitError("Inpiut matrices must by GF(2) symplectic matrices")
if matrix1.shape[1] != matrix2.shape[1]:
return False
_, rref_matrix1, _, rank_matrix1 = mt.rref_complete(matrix1)
_, rref_matrix2, _, rank_matrix2 = mt.rref_complete(matrix2)
if rank_matrix1 != rank_matrix2:
return False
rref_matrix1 = rref_matrix1[:rank_matrix1]
rref_matrix2 = rref_matrix2[:rank_matrix2]
return np.array_equal(rref_matrix1, rref_matrix2)
[docs]
def is_stabilizer_group(matrix: np.ndarray) -> bool:
"""Do the rows of the input matrix represent the generators of an
abelian Pauli subgroup?
Args:
matrix: GF(2) symplectic matrix
Raises:
QiskitError: Input matrix not a GF(2) symplectic matrix
Returns:
True is matrix represents a Stabilizer group
Examples:
>>> matrix = numpy.array([[1,0,0,0,0,0],
[0,1,0,0,0,0],
[1,0,0,0,0,1]], dtype=numpy.bool_)
>>> is_stabilizer_group(matrix)
True
"""
matrix = np.atleast_2d(np.array(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError("Input matrix not a GF(2) symplectic matrix")
return all_commute(matrix)
# ---------------------------------------------------------------
def center(matrix: np.ndarray, preserve: bool = False) -> np.ndarray:
"""Find the center of the group with generators given by the symplectic matrix
Args:
matrix: GF(2) symplectic matrix
preserve: If True then an attempt will be made to preserve then generators form
Raises:
QiskitError: Input matrix is not a symplectic matrix
Returns:
Generators for the center, represented as a symplectic matrix,
of the group with generators given by the input symplectic matrix
Examples:
>>> matrix = numpy.array([[1,1,0,0,1,0],
[0,0,0,1,1,0],
[0,1,0,1,0,1]], dtype=numpy.bool_)
>>> center_ = center(matrix)
>>> center_.astype(int)
array([[1, 1, 0, 0, 1, 0]])
See Also:
_center, _center_preserve
TODO: Add in example with preserve=True is useful
"""
matrix = np.atleast_2d(np.array(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError("Input matrix is not a symplectic matrix")
if preserve:
return _center_preserve(matrix)
else:
return _center(matrix)
def _center(matrix: np.ndarray) -> bool:
"""Find the center of the group with generators given by the symplectic matrix
Args:
matrix: GF(2) symplectic matrix
Returns:
Generators for the center, represented as a symplectic matrix,
of the group with generators given by the input symplectic matrix
Examples:
>>> matrix = numpy.array([[1,1,0,0,1,0],
[0,0,0,1,1,0],
[0,1,0,1,0,1]], dtype=numpy.bool_)
>>> center_ = _center(matrix)
>>> center_.astype(int)
array([[1, 1, 0, 0, 1, 0]])
Notes:
This method may nbot preserve any input vectors
See Also:
center
TODO: Add in example with preserve=True is useful
"""
return _symplectic_gram_schmidt(matrix, [], [])[0]
def _center_preserve(matrix: np.ndarray) -> np.ndarray:
"""Find the center of the group with generators given by the symplectic matrix
Args:
matrix: GF(2) symplectic matrix
Returns:
Generators for the center, represented as a symplectic matrix,
of the group with generators given by the input symplectic matrix
Examples:
>>> matrix = numpy.array([[1,1,0,0,1,0],
[0,0,0,1,1,0],
[0,1,0,1,0,1]], dtype=numpy.bool_)
>>> center_ = _center_preserve(matrix)
>>> center_.astype(int)
array([[1, 1, 0, 0, 1, 0]])
Notes:
This method attempots to preserve any input vectors
See Also:
_center, center
TODO: Add in example with preserve is actually needed
"""
# First move any generator that is in the center to the front of the list
rematrix = deque()
num = matrix.shape[1] >> 1
for opi in reversed(matrix):
break_flag = False
for opj in matrix:
if _symplectic_product_vv(opi, opj, num) == 1:
break_flag = True
break
if break_flag:
rematrix.append(opi)
else:
rematrix.appendleft(opi)
# Now calculate the center as normal
rematrix = np.array(rematrix)
return _center(rematrix)
# ---------------------------------------------------------------
def basis_for_pauli_group(matrix: np.ndarray) -> np.ndarray:
"""Given a set of generators (not necessarily independent) find
a full basis using as many of the provided generators as possible.
Args:
matrix: Set of generators (in GF(2) symplectic form)
Raises:
QiskitError: Input matrix not in a GF(2) symplectic matrix
Returns:
A maximal independant set
Examples:
>>> matrix = numpy.array([[1,1,0,0,1,0],[0,0,0,1,1,0],[0,1,0,1,0,1]], dtype=numpy.bool_)
>>> basis = basis_for_pauli_group(matrix)
>>> basis.astype(int)
array([[1, 1, 0, 0, 1, 0],
[0, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0]])
"""
matrix = np.atleast_2d(np.asarray(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError("Input matrix not in a GF(2) symplectic matrix")
return _basis_for_pauli_group(matrix)
def _basis_for_pauli_group(matrix: np.ndarray) -> np.ndarray:
"""Given a set of generators (not necessarily independent) find
a full basis using as many of the provided generators as possible.
Args:
matrix: Set of generators (in GF(2) symplectic form)
Returns:
A maximal independant set
Examples:
>>> matrix = numpy.array([[1,1,0,0,1,0],[0,0,0,1,1,0],[0,1,0,1,0,1]], dtype=numpy.bool_)
>>> basis = _basis_for_pauli_group(matrix)
>>> basis.astype(int)
array([[1, 1, 0, 0, 1, 0],
[0, 0, 0, 1, 1, 0],
[0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0]])
"""
aug_matrix = mt.augment_mat(matrix, "bottom")
heads, _, _, rank = mt._rref_complete(aug_matrix.T)
shape = (rank, aug_matrix.shape[1])
ext_matrix = np.zeros(shape, dtype=np.bool_)
posns = np.flatnonzero(heads)
for k, index in enumerate(posns):
ext_matrix[k] = aug_matrix[index]
return ext_matrix
def make_hyperbolic(
center_: np.ndarray, x: np.ndarray, z: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""Makes a isotropic hyperbolic basis into an hyperbolic basis
Let center, x and z (where x, z and center are represented by GF(2)
symplectic matrices/vectors) be a isotropic-hyperbolic basis with
2m+k total generators. This method makes/extends this basis into
a hyperbolic basis for the (m+k)-th Pauli group by constructing hyperbolic
partners (acenter) for each element in the center such that
P_(n+k) = <iI, x_new, z_new>
where x_new = <x, center> and z_new = <z, acenter>
Args:
center_: isotropic part of basis (center)
x: hyperbolic part of basis
z: hyperbolic part of basis
Raises:
QiskitError: Input center is not center of group generated by vectors from center, x and z
QiskitError: Input matrices x, z are not in hyperbolic pairs
Examples:
>>> center_ = numpy.array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]], dtype=numpy.bool_)
>>> x = numpy.array([[0, 1, 0, 0, 1, 0, 1, 0]], dtype=numpy.bool_)
>>> z = numpy.array([[0, 0, 0, 0, 1, 1, 0, 1]], dtype=numpy.bool_)
>>> center_, x, z = symplectic_gram_schmidt(a, x, z)
>>> x, z = make_hyperbolic(center_, x, z)
>>> x
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1]])
>>> z
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0]])
See Also:
_make_hyperbolic
"""
if center_ is None:
return x, z
center_ = np.atleast_2d(np.array(center_))
if center_.shape[0] == 1 and not center_.any():
return x, z
center_ = min_generating(center_)
x = np.atleast_2d(np.array(x))
z = np.atleast_2d(np.array(z))
if not is_center(center_, np.vstack((x, z, center_))):
raise QiskitError(
"Input center is not center of group generated by vectors from center, x and z"
)
if not is_hyper_form(x, z):
raise QiskitError("Input matrices x, z are not in hyperbolic pairs")
return _make_hyperbolic(center_, x, z)
def _make_hyperbolic(
center_: np.ndarray, x: np.ndarray, z: np.ndarray
) -> Tuple[np.ndarray, np.ndarray]:
"""Makes a isotropic hyperbolic basis into an hyperbolic basis
Let center, x and z (where x, z and center are represented by GF(2)
symplectic matrices/vectors) be a isotropic-hyperbolic basis with
2m+k total generators. This method makes/extends this basis into
a hyperbolic basis for the (m+k)-th Pauli group by constructing hyperbolic
partners (acenter) for each element in the center such that
P_(n+k) = <iI, x_new, z_new>
where x_new = <x, center> and z_new = <z, acenter>
Args:
center_: isotropic part of basis (center)
x, z: hyperbolic part of basis
Examples:
>>> center_ = numpy.array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]], dtype=numpy.bool_)
>>> x = numpy.array([[0, 1, 0, 0, 1, 0, 1, 0]], dtype=numpy.bool_)
>>> z = numpy.array([[0, 0, 0, 0, 1, 1, 0, 1]], dtype=numpy.bool_)
>>> center_, x, z = symplectic_gram_schmidt(a, x, z)
>>> x, z = _make_hyperbolic(center_, x, z)
>>> x
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1]])
>>> z
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0]])
See Also:
make_hyperbolic
"""
center_size = center_.shape[0]
hyper_size = x.shape[0]
shape = (center_size, x.shape[1])
spacer = np.zeros(shape, dtype=np.bool_)
x = np.vstack((x, spacer))
z = np.vstack((z, spacer))
center_ = center_.copy()
while center_size > 0:
hop = _build_hyper_partner(center_[:center_size], center_size - 1)
# TODO: Change the use of make_commute_hyper to _make_commute_hyper
hop = make_commute_hyper(hop, x, z, xrange=range(hyper_size), zrange=range(hyper_size))
# hop = _make_element_commute_with_hyper_pairs(
# hop, x, z, range(hyper_size), range(hyper_size)
# )
center_size -= 1
x[hyper_size] = center_[center_size]
z[hyper_size] = hop
hyper_size += 1
return x, z
def make_isotropic_hyperbolic_form(
matrix: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Creates a isotrophic hyperbolic basis from a set of generators
Args:
matrix (np.ndarray): GF(2) symplectic matrix
Returns:
An isotrophic hyperbolic basis (center, x, z)
Examples:
>>> matrix = np.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=np.bool_)
>>> center_, x, z = make_isotropic_hyperbolic_form(matrix)
>>> center_.astype(int)
array([[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 1, 1, 1]])
>>> x.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0]])
>>> z.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1]])
See Also:
symplectic_gram_schmidt, _symplectic_gram_schmidt
"""
return _symplectic_gram_schmidt(matrix, [], [])
def hyperbolic_basis_for_pauli_group(
matrix: Optional[np.ndarray] = None,
x: Optional[np.ndarray] = None,
z: Optional[np.ndarray] = None,
n: Optional[int] = None,
) -> Tuple[np.ndarray, np.ndarray]:
"""Builds a hyperbolic basis for the associated Pauli group
Args:
matrix (Optional): Input GF(2) symplectic matrix
x: (Optional): Input GF(2) hyperbolic pairs
z (Optional): Input GF(2) hyperbolic pairs
n: Size of standard Pauli group basis to create (if matrix,x,z are None)
Raises:
QiskitError: x and z must be both be None or both be array like
QiskitError: x not a GF(2) symplectic matrix
QiskitError: z not a symplectic matrix
QiskitError: matrix not a GF(2) symplectic matrix
QiskitError: When providing an input hyperbolic set the input matrix
must be the center of the full set of generators
QiskitError: If matrix, x and z are None then n must be provided
Returns:
Hyperbolic basis for the associated Pauli group (new_x, new_z)
Examples:
>>> matrix = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> center_, x, z = make_isotropic_hyperbolic_form(matrix)
>>> nx, nz = sysp.hyperbolic_basis_for_pauli_group(center_, x, z)
>>> nx.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 1, 0]])
>>> matrix = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> nx, nz = hyperbolic_basis_for_pauli_group(matrix)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 1, 0]])
>>> x = numpy.array([[0, 1, 0, 0, 1, 0, 1, 0]], dtype=numpy.bool_)
>>> z = numpy.array([[0, 0, 0, 0, 1, 1, 0, 1]], dtype=numpy.bool_)
>>> nx, nz = hyperbolic_basis_for_pauli_group(x=x, z=z)
>>> nx.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 1, 0, 0, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1]])
>>> x, z = hyperbolic_basis_for_pauli_group(n=5)
>>> x.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0]])
>>> z.astype(int)
array([[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]])
See Also:
_hyperbolic_basis_for_pauli_group
"""
if (x is None) ^ (z is None):
raise QiskitError("x and z must be both be None or both be array like")
if x is not None:
x = np.atleast_2d(np.array(x))
z = np.atleast_2d(np.array(z))
if not is_symplectic_matrix_form(x):
raise QiskitError(f"{x} not a GF(2) symplectic matrix")
if not is_symplectic_matrix_form(z):
raise QiskitError(f"{z} not a symplectic matrix")
if matrix is not None:
matrix = np.atleast_2d(np.array(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError(f"{matrix} not a GF(2) symplectic matrix")
if not is_center(matrix, np.vstack((matrix, x, z))):
raise QiskitError(
"When providing an input hyperbolic set \
the input matrix must be the center of the full set of \
generators"
)
else:
matrix = None
else:
if matrix is None:
if n is None:
raise QiskitError("If matrix, x and z are None then n must be provided")
zero = np.zeros(shape=(n, n), dtype=np.bool_)
x = mt.augment_mat(zero, "left")
z = mt.augment_mat(zero, "right")
return x, z
matrix = np.atleast_2d(np.array(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError(f"{matrix} not a GF(2) symplectic matrix")
x = []
z = []
return _hyperbolic_basis_for_pauli_group(matrix, x, z)
def _hyperbolic_basis_for_pauli_group(
matrix: Optional[np.ndarray], x: Optional[np.ndarray], z: Optional[np.ndarray]
) -> Tuple[np.ndarray, np.ndarray]:
"""Builds a hyperbolic basis for the associated Pauli group
Args:
matrix (Optional): Input GF(2) symplectic matrix
x, z (Optional): Input GF(2) hyperbolic pairs
Returns:
Hyperbolic basis for the associated Pauli group (new_x, new_z)
Examples:
>>> matrix = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> center_, x, z = make_isotropic_hyperbolic_form(matrix)
>>> nx, nz = _hyperbolic_basis_for_pauli_group(center_, x, z)
>>> nx.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 1, 0]])
>>> matrix = numpy.array([[0,1,0,0,1,0,1,0],
[0,0,0,0,1,1,0,1],
[1,1,1,0,0,1,0,0],
[1,1,0,1,0,0,0,0]], dtype=numpy.bool_)
>>> nx, nz = _hyperbolic_basis_for_pauli_group(matrix)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 0, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 1, 0]])
>>> x = numpy.array([[0, 1, 0, 0, 1, 0, 1, 0]], dtype=numpy.bool_)
>>> z = numpy.array([[0, 0, 0, 0, 1, 1, 0, 1]], dtype=numpy.bool_)
>>> nx, nz = _hyperbolic_basis_for_pauli_group(x=x, z=z)
>>> nx.astype(int)
array([[0, 1, 0, 0, 1, 0, 1, 0],
[1, 1, 0, 0, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 0]])
>>> nz.astype(int)
array([[0, 0, 0, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1]])
See Also:
hyperbolic_basis_for_pauli_group
"""
if len(x) == 0:
center_, x, z = _symplectic_gram_schmidt(matrix, [], [])
else:
center_ = matrix
if center_ is not None:
x, z = _make_hyperbolic(center_, x, z)
basis = _basis_for_pauli_group(np.vstack((x, z)))
added = x.shape[1] - 2 * x.shape[0]
basis_com = _make_commute_hyper(basis[-added:], x, z)
_, x_new, z_new = symplectic_gram_schmidt(basis_com, x, z)
return x_new, z_new
def remove_hyper_elements_from_hyper_form(
center_: Optional[np.ndarray], x: np.ndarray, z: np.ndarray, indices: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Transfers those elements/vectors from x with an index in indices into center_
and deletes the corresponding hyperbolic partner from z.
Args:
center_: center array
x: Hyperbolic pairs, x being the source
z: Hyperbolic pairs, x being the source
indices: indices indicating which rows to transfer from source to sink
Raises:
QiskitError: x and z must be GF(2) symplectic matrices/vectors
QiskitError: x and z must have the same shape
QiskitError: x and z must have the same size in the second
dimension as the center
Returns:
isotropic hyperbolic basis (center, x, z)
Examples:
>>> x, z = sysp.hyperbolic_basis_for_pauli_group(n=5)
>>> center_, x_new, z_new = remove_hyper_elements_from_hyper_form(None,x,z,[0,1])
>>> center_.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]])
>>> x_new.astype(int)
array([[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]])
>>> z_new.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]])
"""
x = np.atleast_2d(np.array(x))
z = np.atleast_2d(np.array(z))
indices = list(indices)
if not (is_symplectic_matrix_form(x) and is_symplectic_matrix_form(z)):
raise QiskitError("x and z must be GF(2) symplectic matrices/vectors")
if not x.shape == z.shape:
raise QiskitError(
f"x (shape={x.shape})and z (shape={z.shape}) \
must have the same shape"
)
if center_ is not None:
center_ = np.atleast_2d(np.array(center_))
if not is_symplectic_matrix_form(center_):
raise QiskitError("Input center is not a GF(2) symplectiv matrix/vector")
if not x.shape[1] == center_.shape[1]:
raise QiskitError(
"x and z must have the same size in the second \
dimension as the center"
)
return _remove_hyper_elements_from_hyper_form(center_, x, z, indices)
def _remove_hyper_elements_from_hyper_form(
center_: np.ndarray, x: np.ndarray, z: np.ndarray, indices: List[int]
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Transfers those elements/vectors from x with an index in indices into center_
and delete the corresponding hyperbolic partner from z.
Args:
center_: center array
x, z: Hyperbolic pairs, x being the source
indices: indices indicating which rows to transfer from source to sink
Returns:
isotropic hyperbolic basis (center, x, z)
Examples:
>>> x, z = sysp.hyperbolic_basis_for_pauli_group(n=5)
>>> center_, x_new, z_new = _remove_hyper_elements_from_hyper_form(None,x,z,[0,1])
>>> center_.astype(int)
array([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]])
>>> x_new.astype(int)
array([[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]])
>>> z_new.astype(int)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]])
"""
rm_size = len(indices)
if rm_size == 0:
return np.zeros(shape=(0, x.shape[1]), dtype=np.bool_), x, z
size = x.shape[0] - rm_size
shape = (size, x.shape[1])
new_x = np.zeros(shape, dtype=np.bool_)
new_z = np.zeros(shape, dtype=np.bool_)
pos = 0
part_center = deque()
for i in reversed(range(x.shape[0])):
if i in indices:
part_center.appendleft(x[i].copy())
else:
new_x[pos] = x[i].copy()
new_z[pos] = z[i].copy()
pos += 1
part_center = np.array(part_center)
if center_ is None:
new_center = part_center
else:
new_center = np.vstack((center_, part_center))
if new_center.shape[0] > 1:
new_center = new_center[np.where(new_center.any(axis=1))[0]]
return new_center, new_x, new_z
def min_generating(
matrix: Optional[np.ndarray] = None,
x: Optional[np.ndarray] = None,
z: Optional[np.ndarray] = None,
) -> np.ndarray:
"""Returns a minimal generating/linearily independent set of rows.
If only a matrix is provided, the method returns a submatrix with maximally
independent set of rows. If a matrix and a set of hyperbolic pairs are provided then
the method returns a submatrix such that the rows of the submatrix and the
hyperbolic pairs are maximally linearily independent.
Note: This method returns matrix as a 2d matrix (relative to numpy)
Args:
matrix (Optional[np.ndarray]): Input GF(2) symplectic matrix
x (Optional[np.ndarray]): Input hyperbolic set - pair of GF(2) symplectic matrices
z (Optional[np.ndarray]): Input hyperbolic set - pair of GF(2) symplectic matrices
Raises:
QiskitError: An input matrix is required
QiskitError: Inputs x and z matrices do not have the same shape
QiskitError: Input hyper pair matrices do not represent a set of Hyperbolic pairs
QiskitError: Input matrix and x and z components must have the same number of columns
Returns:
matrix: minimal generating set or rows
Examples:
>>> mat = numpy.array([[1,0,1,0],[1,1,0,1],[0,1,1,1]])
>>> res = min_generating(mat)
>>> res.astype(int)
array([[1,0,1,0],
[1,1,0,1]])
>>> mat = numpy.array([[1,0,1,0],[1,1,0,1],[0,1,1,1]])
>>> x = numpy.array([[0,1,0,0]])
>>> z = numpy.array([[0,0,0,1]])
>>> res = symp.min_generating(mat,x,z)
>>> res.astype(int)
array([[1, 0, 1, 0],
[1, 1, 0, 1]])
See Also:
_min_generating_matrix, _min_generating_matrix_xz
"""
# all inputs are None
if matrix is None and x is None and z is None:
raise QiskitError("An input matrix is required")
# Only matrix is provided
if x is None and z is None:
matrix = np.atleast_2d(matrix)
return _min_generating_matrix(matrix)
# Only x and z are provided
if matrix is None:
raise QiskitError("An input matrix is required")
# All matrix, x and z are provided
if x.shape != z.shape:
raise QiskitError("Inputs x and z matrices do not have the same shape")
matrix = np.atleast_2d(matrix)
x = np.atleast_2d(x)
z = np.atleast_2d(z)
if matrix.shape[1] != x.shape[1]:
raise QiskitError(
"Input matrix and x and z components must have the same number of columns"
)
return _min_generating_matrix_xz(matrix, x, z)
def _min_generating_matrix(matrix: np.ndarray) -> np.ndarray:
"""Returns a matrix consisting of rows from the input matrix such that the resulting
matrix and the input matrix have the same rank.
Note: This method returns everything as 2d matrices
Args:
matrix: Input GF(2) symplectic matrix
Examples:
>>> mat = numpy.array([[1,0,1,0],[1,1,0,1],[0,1,1,1],[1,1,1,1],[0,0,0,0]])
>>> _min_generating_matrix(mat)
array([[1, 0, 1, 0],
[1, 1, 0, 1],
[0, 1, 1, 1]])
Returns:
matrix: submatrix of input matrix with full rank
"""
heads, _, _, rank_ = mt.rref_complete(matrix.T)
if rank_ == matrix.shape[0]:
return matrix
posns = np.flatnonzero(heads)
ext_matrix = np.zeros(shape=(posns.shape[0], matrix.shape[1]), dtype=np.bool_)
for k, index in enumerate(posns):
ext_matrix[k] = matrix[index]
return ext_matrix
def _min_generating_matrix_xz(matrix, x, z) -> np.ndarray:
"""Returns a submatrix of the input matrix such that combined rows of the submatrix and the
hyperbolic pairs are linearily independent of GF(2)
Args:
matrix: GF(2) symplectic matrix
x, y: hyperbolic basis pairs
Returns:
matrix: Submatrix of input matrix
"""
# Priority is given to preserving the hyperbolic set (x,z) and so matrix is stack on the bottom.
tmp_matrix = np.vstack((x, z, matrix))
heads, _, _, rank_ = mt.rref_complete(tmp_matrix)
if rank_ < tmp_matrix.shape[0]:
# Since (x,z) has already been reduced any removal will appear in the matrix part
posns = np.flatnonzero(heads[2 * x.shape[0] :])
ext_matrix = np.zeros(shape=(posns.shape[0], matrix.shape[1]), dtype=np.bool_)
for k, index in enumerate(posns):
ext_matrix[k] = matrix[index]
matrix = ext_matrix
return matrix
def normalizer(
matrix: Optional[np.ndarray] = None,
x: Optional[np.ndarray] = None,
z: Optional[np.ndarray] = None,
min_gen: bool = False,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Returns the normalizer of the group generated by the generators represented in the
symplectic matrix(s):
Args:
matrix (optional): GF(2) symplectic matrix. Defaults to None.
x (optional): Hyperbolic pairs. Defaults to None.
z (optional): Hyperbolic pairs. Defaults to None.
min_gen (optional): If True then the matrix will be reduced to be full
rank (i.e. the rows will be a minimal generating set). Default is False
Raises:
QiskitError: All inputs should not be None
QiskitError: matrix must be a GF(2) symplectic matrix
QiskitError: x and z must be both be None or both be array like
QiskitError: x and z must be GF(2) symplectic matrices/vectors
QiskitError: x and z must have the same shape
QiskitError: All inputs must have the same number of columns/length
Returns:
Isotropic hyperbolic form/basis of normalizer (center, x_new, y_new)
Examples:
>>> a = np.array([[1, 1, 1, 0, 1, 0, 0, 1]], dtype=np.bool_)
>>> x = np.array([[0, 1, 0, 0, 1, 0, 1, 0]], dtype=np.bool_)
>>> z = np.array([[0, 0, 0, 0, 1, 1, 0, 1]], dtype=np.bool_)
>>> center_, x_new, z_new = sysp.normalizer(a, x, z)
>>> center_.astype(int)
array([[1, 1, 1, 0, 1, 0, 0, 1]])
>>> x_new.astype(int)
array([[1, 0, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 1, 1],
[0, 1, 0, 0, 1, 0, 1, 0]])
>>> z_new.astype(int)
array([[1, 1, 1, 0, 1, 0, 0, 0],
[1, 1, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 1]])
"""
if matrix is None and x is None and z is None:
raise QiskitError("All inputs should not be None")
if matrix is not None:
matrix = np.atleast_2d(np.array(matrix))
if not is_symplectic_matrix_form(matrix):
raise QiskitError(f"{matrix} must be a GF(2) symplectic matrix")
if (x is None) ^ (z is None):
raise QiskitError("x and z must be both be None or both be array like")
if matrix is not None and x is None:
if min_gen:
matrix = min_generating(matrix)
if is_stabilizer_group(matrix):
return _normalizer_abelian_group(matrix)
else:
return _normailzer_group(matrix)
x = np.atleast_2d(np.array(x))
z = np.atleast_2d(np.array(z))
if not (is_symplectic_matrix_form(x) and is_symplectic_matrix_form(z)):
raise QiskitError("x and z must be GF(2) symplectic matrices/vectors")
zero_mat = False
if matrix is None:
matrix = np.zeros(shape=(0, x.shape[1]), dtype=np.bool_)
zero_mat = True
if not x.shape == z.shape:
raise QiskitError("x and z must have the same shape")
if not matrix.shape[1] == x.shape[1]:
raise QiskitError("All inputs must have the same number of columns/length")
if not is_center(matrix, np.vstack((matrix, x, z))):
raise QiskitError(
"When a matrix and hyperbolic parts are "
"provided the matrix must represent the center of the union"
)
if not zero_mat:
matrix = min_generating(matrix, x, z)
return _normalizer_group_preserve(matrix, x, z)
def _normalizer_abelian_group(
matrix: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Returns the normalizer of the abelian group generated by the generators represented in the
symplectic matrix(s):
Args:
matrix (optional): GF(2) symplectic matrix. Defaults to None.
x, z (optional): Hyperbolic pairs. Defaults to None.
Returns:
Isotropic hyperbolic form/basis of normalizer (center, x_new, y_new)
TODO: Add examples
"""
dist_center = mt.rank(matrix)
matrix_ext = _basis_for_pauli_group(matrix)
center_, x, z = _symplectic_gram_schmidt(matrix_ext, [], [])
center_, x, z = _remove_hyper_elements_from_hyper_form(center_, x, z, list(range(dist_center)))
if center_.shape[0] > 1:
center_ = center_[np.where(center_.any(axis=1))[0]]
return center_, x, z
def _normalizer_group_preserve(
center_: np.ndarray, x: np.ndarray, z: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
"""Returns the normalizer of the group generated by the generators represented in the
symplectic matrix(s) and trys to preserve elements:
This method assumes that the matrix has full rank. That is that the
set of generators is independent.
Args:
matrix (optional): GF(2) symplectic matrix. Defaults to None.
x, z (optional): Hyperbolic pairs. Defaults to None.
Returns:
Isotropic hyperbolic form/basis of normalizer (center, x_new, y_new)
TODO: Add examples
"""
center_size = center_.shape[0]
if center_size == 1:
if not center_[0].any():
center_size = 0
gauge_degree = x.shape[0]
if center_size > 0:
x, z = _make_hyperbolic(center_, x, z)
matrix_ext = _basis_for_pauli_group(np.vstack((x, z)))
matrix_ext = make_commute_hyper(matrix_ext, x, z, range(x.shape[0] << 1, matrix_ext.shape[0]))
# matrix_ext = _make_elements_commute_with_hyper_pairs(
# matrix_ext,
# range(x.shape[0] << 1, matrix_ext.shape[0]),
# x,
# range(x.shape[0]),
# z,
# range(z.shape[0]),
# )
matrix = matrix_ext[x.shape[0] << 1 :]
lx = [item.copy() for item in matrix_ext[: x.shape[0]]]
lz = [item.copy() for item in matrix_ext[x.shape[0] : x.shape[0] << 1]]
center_, x, z = _symplectic_gram_schmidt(matrix, lx, lz)
indices = list(range(gauge_degree, gauge_degree + center_size))
return _remove_hyper_elements_from_hyper_form(center_, x, z, indices)
def _normailzer_group(matrix):
"""Returns the normalizer of the group generated by the generators represented in the
symplectic matrix(s):
This method assumes that the matrix has full rank. That is that the
set of generators is independent.
Args:
matrix: GF(2) Sympletic matrix
Returns:
Isotropic hyperbolic form/basis of normalizer (center, x_new, y_new)
TODO: Add examples
"""
center_, hyper1, hyper2 = _symplectic_gram_schmidt(matrix, [], [])
return _normalizer_group_preserve(center_, hyper1, hyper2)
def _extend_symplectic(matrix: np.array, extend: int):
n = matrix.shape[1] // 2
ext_part = np.zeros(shape=(matrix.shape[0], extend), dtype=matrix.dtype)
ext_matrix = np.hstack((matrix[:, 0:n], ext_part, matrix[:, n:], ext_part))
return ext_matrix