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

Will be better if removing tensorflow prerequisite? #42

Closed
royess opened this issue Jul 14, 2022 · 6 comments
Closed

Will be better if removing tensorflow prerequisite? #42

royess opened this issue Jul 14, 2022 · 6 comments
Labels
feedback welcome We'd like to listen to the feedback from everyone

Comments

@royess
Copy link
Contributor

royess commented Jul 14, 2022

Since tensorcircuit does not rely on any specific backends, I wonder if it will be better to remove tensorflow from prerequisites, whose binaries are rather big (~500MB).

@refraction-ray
Copy link
Contributor

A very good question. Apart from the slow downloading due to the large size, TensorFlow as a requirement has other issues, too. Say in m1 mac, the package name is tensorflow-macos, which can lead to installation failure of tensorcircuit silently.

However, there are several reasons that support the inclusion of tensorflow as a requirement,

  1. some function utilities are now supported solely by tensorflow backend, such as tc.quantum.heisenberg_hamiltonian.
  2. if there is no requirement of tensorflow, then no backend with automatic differentiation is enabled by default which may confuse the users especially for newcomers. Namely, one need to manually install many things to make tensorcircuit work as expected. (this point is the main reason that I keep tensorflow as a requirement because I don't want to scare new users away by failure after pip install tensorcircuit)

Still, to remove or not to remove tf as a requirement, is an question to me. Not sure which side is better, and would love to listen to more feedbacks.

@refraction-ray refraction-ray added question Further information is requested feedback welcome We'd like to listen to the feedback from everyone and removed question Further information is requested labels Jul 14, 2022
@royess
Copy link
Contributor Author

royess commented Jul 15, 2022

2. if there is no requirement of tensorflow, then no backend with automatic differentiation is enabled by default which may confuse the users especially for newcomers. Namely, one need to manually install many things to make tensorcircuit work as expected. (this point is the main reason that I keep tensorflow as a requirement because I don't want to scare new users away by failure after pip install tensorcircuit)

For the second point, how about changing the installation guide to the following lines?

pip install tensorflow
pip install tensorcircuit

I suppose it will not be less friendly for new users.

@royess
Copy link
Contributor Author

royess commented Jul 15, 2022

  1. some function utilities are now supported solely by tensorflow backend, such as tc.quantum.heisenberg_hamiltonian.

And I am also curious about whether it would be convenient for us to re-implement those utilities by uniform API of tc.backend.

@refraction-ray
Copy link
Contributor

For now, my preference is to try something like below in furture releases
pip install tensorcircuit # only requires numpy
pip install tensorcircuit[tensorflow] # also install tensorflow

@refraction-ray
Copy link
Contributor

And I am also curious about whether it would be convenient for us to re-implement those utilities by uniform API of tc.backend.

It is more than welcome if anyone wants to make these utilities backend agnostic, which is a good first issue. These methods are in

def _id(x: Any) -> Any:
return x
if is_m1mac():
compiled_jit = _id
else:
compiled_jit = partial(get_backend("tensorflow").jit, jit_compile=True)
def heisenberg_hamiltonian(
g: Graph,
hzz: float = 1.0,
hxx: float = 1.0,
hyy: float = 1.0,
hz: float = 0.0,
hx: float = 0.0,
hy: float = 0.0,
sparse: bool = True,
numpy: bool = False,
) -> Tensor:
"""
Generate Heisenberg Hamiltonian with possible external fields.
:Example:
>>> g = tc.templates.graphs.Line1D(6)
>>> h = qu.heisenberg_hamiltonian(g, sparse=False)
>>> tc.backend.eigh(h)[0][:10]
array([-11.2111025, -8.4721365, -8.472136 , -8.472136 , -6. ,
-5.123106 , -5.123106 , -5.1231055, -5.1231055, -5.1231055],
dtype=float32)
:param g: input circuit graph
:type g: Graph
:param hzz: zz coupling, default is 1.0
:type hzz: float
:param hxx: xx coupling, default is 1.0
:type hxx: float
:param hyy: yy coupling, default is 1.0
:type hyy: float
:param hz: External field on z direction, default is 0.0
:type hz: float
:param hx: External field on y direction, default is 0.0
:type hx: float
:param hy: External field on x direction, default is 0.0
:type hy: float
:param sparse: Whether to return sparse Hamiltonian operator, default is True.
:type sparse: bool, defalts True
:param numpy: whether return the matrix in numpy or tensorflow form
:type numpy: bool, defaults False,
:return: Hamiltonian measurements
:rtype: Tensor
"""
n = len(g.nodes)
ls = []
weight = []
for e in g.edges:
if hzz != 0:
r = [0 for _ in range(n)]
r[e[0]] = 3
r[e[1]] = 3
ls.append(r)
weight.append(hzz)
if hxx != 0:
r = [0 for _ in range(n)]
r[e[0]] = 1
r[e[1]] = 1
ls.append(r)
weight.append(hxx)
if hyy != 0:
r = [0 for _ in range(n)]
r[e[0]] = 2
r[e[1]] = 2
ls.append(r)
weight.append(hyy)
for node in g.nodes:
if hz != 0:
r = [0 for _ in range(n)]
r[node] = 3
ls.append(r)
weight.append(hz)
if hx != 0:
r = [0 for _ in range(n)]
r[node] = 1
ls.append(r)
weight.append(hx)
if hy != 0:
r = [0 for _ in range(n)]
r[node] = 2
ls.append(r)
weight.append(hy)
ls = tf.constant(ls)
weight = tf.constant(weight)
ls = get_backend("tensorflow").cast(ls, dtypestr)
weight = get_backend("tensorflow").cast(weight, dtypestr)
if sparse:
r = PauliStringSum2COO_numpy(ls, weight)
if numpy:
return r
return backend.coo_sparse_matrix_from_numpy(r)
return PauliStringSum2Dense(ls, weight, numpy=numpy)
def PauliStringSum2Dense(
ls: Sequence[Sequence[int]],
weight: Optional[Sequence[float]] = None,
numpy: bool = False,
) -> Tensor:
"""
Generate dense matrix from Pauli string sum
:param ls: 2D Tensor, each row is for a Pauli string,
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
:type ls: Sequence[Sequence[int]]
:param weight: 1D Tensor, each element corresponds the weight for each Pauli string
defaults to None (all Pauli strings weight 1.0)
:type weight: Optional[Sequence[float]], optional
:param numpy: default False. If True, return numpy coo
else return backend compatible sparse tensor
:type numpy: bool
:return: the tensorflow dense matrix
:rtype: Tensor
"""
sparsem = PauliStringSum2COO_numpy(ls, weight)
if numpy:
return sparsem.todense()
sparsem = backend.coo_sparse_matrix_from_numpy(sparsem)
densem = backend.to_dense(sparsem)
return densem
# already implemented as backend method
#
# def _tf2numpy_sparse(a: Tensor) -> Tensor:
# return get_backend("numpy").coo_sparse_matrix(
# indices=a.indices,
# values=a.values,
# shape=a.get_shape(),
# )
# def _numpy2tf_sparse(a: Tensor) -> Tensor:
# return get_backend("tensorflow").coo_sparse_matrix(
# indices=np.array([a.row, a.col]).T,
# values=a.data,
# shape=a.shape,
# )
def PauliStringSum2COO(
ls: Sequence[Sequence[int]],
weight: Optional[Sequence[float]] = None,
numpy: bool = False,
) -> Tensor:
"""
Generate sparse tensor from Pauli string sum
:param ls: 2D Tensor, each row is for a Pauli string,
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
:type ls: Sequence[Sequence[int]]
:param weight: 1D Tensor, each element corresponds the weight for each Pauli string
defaults to None (all Pauli strings weight 1.0)
:type weight: Optional[Sequence[float]], optional
:param numpy: default False. If True, return numpy coo
else return backend compatible sparse tensor
:type numpy: bool
:return: the scipy coo sparse matrix
:rtype: Tensor
"""
# numpy version is 3* faster!
nterms = len(ls)
n = len(ls[0])
s = 0b1 << n
if weight is None:
weight = [1.0 for _ in range(nterms)]
if not (isinstance(weight, tf.Tensor) or isinstance(weight, tf.Variable)):
weight = tf.constant(weight, dtype=getattr(tf, dtypestr))
rsparse = get_backend("numpy").coo_sparse_matrix(
indices=np.array([[0, 0]], dtype=np.int64),
values=np.array([0.0], dtype=getattr(np, dtypestr)), # type: ignore
shape=(s, s),
)
for i in range(nterms):
rsparse += get_backend("tensorflow").numpy(PauliString2COO(ls[i], weight[i])) # type: ignore
# auto transformed into csr format!!
rsparse = rsparse.tocoo()
if numpy:
return rsparse
return backend.coo_sparse_matrix_from_numpy(rsparse)
PauliStringSum2COO_numpy = partial(PauliStringSum2COO, numpy=True)
def PauliStringSum2COO_tf(
ls: Sequence[Sequence[int]], weight: Optional[Sequence[float]] = None
) -> Tensor:
"""
Generate tensorflow sparse matrix from Pauli string sum
:param ls: 2D Tensor, each row is for a Pauli string,
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
:type ls: Sequence[Sequence[int]]
:param weight: 1D Tensor, each element corresponds the weight for each Pauli string
defaults to None (all Pauli strings weight 1.0)
:type weight: Optional[Sequence[float]], optional
:return: the tensorflow coo sparse matrix
:rtype: Tensor
"""
nterms = len(ls)
n = len(ls[0])
s = 0b1 << n
if weight is None:
weight = [1.0 for _ in range(nterms)]
if not (isinstance(weight, tf.Tensor) or isinstance(weight, tf.Variable)):
weight = tf.constant(weight, dtype=getattr(tf, dtypestr))
rsparse = tf.SparseTensor(
indices=tf.constant([[0, 0]], dtype=tf.int64),
values=tf.constant([0.0], dtype=weight.dtype), # type: ignore
dense_shape=(s, s),
)
for i in range(nterms):
rsparse = tf.sparse.add(rsparse, PauliString2COO(ls[i], weight[i])) # type: ignore
# very slow sparse.add?
return rsparse
@compiled_jit
def PauliString2COO(l: Sequence[int], weight: Optional[float] = None) -> Tensor:
"""
Generate tensorflow sparse matrix from Pauli string sum
:param l: 1D Tensor representing for a Pauli string,
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
:type l: Sequence[int]
:param weight: the weight for the Pauli string
defaults to None (all Pauli strings weight 1.0)
:type weight: Optional[float], optional
:return: the tensorflow sparse matrix
:rtype: Tensor
"""
n = len(l)
one = tf.constant(0b1, dtype=tf.int64)
idx_x = tf.constant(0b0, dtype=tf.int64)
idx_y = tf.constant(0b0, dtype=tf.int64)
idx_z = tf.constant(0b0, dtype=tf.int64)
i = tf.constant(0, dtype=tf.int64)
for j in l:
# i, j from enumerate is python, non jittable when cond using tensor
if j == 1: # xi
idx_x += tf.bitwise.left_shift(one, n - i - 1)
elif j == 2: # yi
idx_y += tf.bitwise.left_shift(one, n - i - 1)
elif j == 3: # zi
idx_z += tf.bitwise.left_shift(one, n - i - 1)
i += 1
if weight is None:
weight = tf.constant(1.0, dtype=tf.complex64)
return ps2coo_core(idx_x, idx_y, idx_z, weight, n)
@compiled_jit
def ps2coo_core(
idx_x: Tensor, idx_y: Tensor, idx_z: Tensor, weight: Tensor, nqubits: int
) -> Tuple[Tensor, Tensor]:
dtype = weight.dtype
s = 0b1 << nqubits
idx1 = tf.cast(tf.range(s), dtype=tf.int64)
idx2 = (idx1 ^ idx_x) ^ (idx_y)
indices = tf.transpose(tf.stack([idx1, idx2]))
tmp = idx1 & (idx_y | idx_z)
e = idx1 * 0
ny = 0
for i in range(nqubits):
# if tmp[i] is power of 2 (non zero), then e[i] = 1
e ^= tf.bitwise.right_shift(tmp, i) & 0b1
# how many 1 contained in idx_y
ny += tf.bitwise.right_shift(idx_y, i) & 0b1
ny = tf.math.mod(ny, 4)
values = (
tf.cast((1 - 2 * e), dtype)
* tf.math.pow(tf.constant(-1.0j, dtype=dtype), tf.cast(ny, dtype))
* weight
)
return tf.SparseTensor(indices=indices, values=values, dense_shape=(s, s)) # type: ignore
except (NameError, ImportError):

@refraction-ray
Copy link
Contributor

closed, as the remaining issue is separately open in #161

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feedback welcome We'd like to listen to the feedback from everyone
Projects
None yet
Development

No branches or pull requests

2 participants