From 7926471e5014d3acd5680fabc696e1b873360b3c Mon Sep 17 00:00:00 2001 From: Phionx Date: Sat, 2 Mar 2024 13:39:35 -0500 Subject: [PATCH 01/10] making progress towards qarray class! --- jaxquantum/__init__.py | 2 +- jaxquantum/core/__init__.py | 9 + jaxquantum/core/operations.py | 158 +++++++++ jaxquantum/core/operators.py | 115 ++++++ jaxquantum/core/qarray.py | 176 ++++++++++ jaxquantum/core/qutip.py | 45 +++ jaxquantum/{quantum => core}/solvers.py | 2 +- jaxquantum/core/states.py | 38 ++ jaxquantum/{quantum => core}/visualization.py | 2 +- jaxquantum/quantum/__init__.py | 5 - jaxquantum/quantum/base.py | 326 ------------------ tutorials/2-qarray.ipynb | 320 +++++++++++++++++ 12 files changed, 864 insertions(+), 334 deletions(-) create mode 100644 jaxquantum/core/__init__.py create mode 100644 jaxquantum/core/operations.py create mode 100644 jaxquantum/core/operators.py create mode 100644 jaxquantum/core/qarray.py create mode 100644 jaxquantum/core/qutip.py rename jaxquantum/{quantum => core}/solvers.py (99%) create mode 100644 jaxquantum/core/states.py rename jaxquantum/{quantum => core}/visualization.py (97%) delete mode 100644 jaxquantum/quantum/__init__.py delete mode 100644 jaxquantum/quantum/base.py create mode 100644 tutorials/2-qarray.ipynb diff --git a/jaxquantum/__init__.py b/jaxquantum/__init__.py index f64060a..02fdcc7 100644 --- a/jaxquantum/__init__.py +++ b/jaxquantum/__init__.py @@ -5,7 +5,7 @@ import os from .utils import * -from .quantum import * +from .core import * with open( diff --git a/jaxquantum/core/__init__.py b/jaxquantum/core/__init__.py new file mode 100644 index 0000000..bbf2990 --- /dev/null +++ b/jaxquantum/core/__init__.py @@ -0,0 +1,9 @@ +"""Quantum Tooling""" + +from .states import * +from .operators import * +from .operations import * +from .qutip import * +from .visualization import * +from .solvers import * +from .qarray import * \ No newline at end of file diff --git a/jaxquantum/core/operations.py b/jaxquantum/core/operations.py new file mode 100644 index 0000000..244894a --- /dev/null +++ b/jaxquantum/core/operations.py @@ -0,0 +1,158 @@ +""" Relevant linear algebra operations. """ + +from jax import config, Array + +import jax.numpy as jnp +import jax.scipy as jsp + +from jaxquantum.utils.utils import is_1d + +config.update("jax_enable_x64", True) + + + +# Kets & Density Matrices ----------------------------------------------------- + +def ket(vec: Array) -> Array: + """Turns a vector array into a ket. + + Args: + vec: vector + + Returns: + ket + """ + vec = jnp.asarray(vec) + return vec.reshape(vec.shape[0], 1) + + +def ket2dm(ket: jnp.ndarray) -> jnp.ndarray: + """Turns ket into density matrix. + + Args: + ket: ket + + Returns: + Density matrix + """ + ket = ket.reshape(ket.shape[0], 1) + return ket @ dag(ket) + + + +# Linear Algebra Operations ----------------------------------------------------- + + +def dag(op: jnp.ndarray) -> jnp.ndarray: + """Conjugate transpose. + + Args: + op: operator + + Returns: + conjugate transpose of op + """ + op = op.reshape(op.shape[0], -1) # adds dimension to 1D array if needed + return jnp.conj(op).T + + +def batch_dag(op: jnp.ndarray) -> jnp.ndarray: + """Conjugate transpose. + + Args: + op: operator + + Returns: + conjugate of op, and transposes last two axes + """ + return jnp.moveaxis( + jnp.conj(op), -1, -2 + ) # transposes last two axes, good for batching + + +def unit(rho: jnp.ndarray, use_density_matrix=False): + """Normalize density matrix. + + Args: + rho: density matrix + + Returns: + normalized density matrix + """ + if use_density_matrix: + evals, _ = jnp.linalg.eigh(rho @ jnp.conj(rho).T) + rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) + return rho / rho_norm + return rho / jnp.linalg.norm(rho) + +def ptrace(rho, indx, dims): + """Partial Trace. + + Args: + rho: density matrix + indx: index to trace out + dims: list of dimensions of the tensored hilbert spaces + + Returns: + partial traced out density matrix + + TODO: Fix weird tracing errors that arise with reshape + """ + if is_1d(rho): + rho = ket2dm(rho) + + Nq = len(dims) + + if isinstance(dims, jnp.ndarray): + dims2 = jnp.concatenate(jnp.array([dims, dims])) + else: + dims2 = dims + dims + + rho = rho.reshape(dims2) + + indxs = [indx, indx + Nq] + for j in range(Nq): + if j == indx: + continue + indxs.append(j) + indxs.append(j + Nq) + rho = rho.transpose(indxs) + + for j in range(Nq - 1): + rho = jnp.trace(rho, axis1=2, axis2=3) + + return rho + + +def expm(*args, **kwargs) -> jnp.ndarray: + """Matrix exponential wrapper. + + Returns: + matrix exponential + """ + return jsp.linalg.expm(*args, **kwargs) + + +def tensor(*args, **kwargs) -> jnp.ndarray: + """Tensor product. + + Args: + *args: tensors to take the product of + + Returns: + Tensor product of given tensors + + """ + ans = args[0] + for arg in args[1:]: + ans = jnp.kron(ans, arg) + return ans + + +def tr(*args, **kwargs) -> jnp.ndarray: + """Full trace. + + Returns: + Full trace. + """ + return jnp.trace(*args, **kwargs) diff --git a/jaxquantum/core/operators.py b/jaxquantum/core/operators.py new file mode 100644 index 0000000..603b184 --- /dev/null +++ b/jaxquantum/core/operators.py @@ -0,0 +1,115 @@ +""" States. """ + +from jax import config + +import jax.numpy as jnp + +from jaxquantum.core.operations import expm, dag + + +config.update("jax_enable_x64", True) + + + +def sigmax() -> jnp.ndarray: + """σx + + Returns: + σx Pauli Operator + """ + return jnp.array([[0.0, 1.0], [1.0, 0.0]]) + + +def sigmay() -> jnp.ndarray: + """σy + + Returns: + σy Pauli Operator + """ + return jnp.array([[0.0, -1.0j], [1.0j, 0.0]]) + + +def sigmaz() -> jnp.ndarray: + """σz + + Returns: + σz Pauli Operator + """ + return jnp.array([[1.0, 0.0], [0.0, -1.0]]) + + +def sigmam() -> jnp.ndarray: + """σ- + + Returns: + σ- Pauli Operator + """ + return jnp.array([[0.0, 0.0], [1.0, 0.0]]) + + +def sigmap() -> jnp.ndarray: + """σ+ + + Returns: + σ+ Pauli Operator + """ + return jnp.array([[0.0, 1.0], [0.0, 0.0]]) + + +def destroy(N) -> jnp.ndarray: + """annihilation operator + + Args: + N: Hilbert space size + + Returns: + annilation operator in Hilber Space of size N + """ + return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=1) + + +def create(N) -> jnp.ndarray: + """creation operator + + Args: + N: Hilbert space size + + Returns: + creation operator in Hilber Space of size N + """ + return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=-1) + + +def num(N) -> jnp.ndarray: + """Number operator + + Args: + N: Hilbert Space size + + Returns: + number operator in Hilber Space of size N + """ + return jnp.diag(jnp.arange(N)) + + +def identity(*args, **kwargs) -> jnp.ndarray: + """Identity matrix. + + Returns: + Identity matrix. + """ + return jnp.eye(*args, **kwargs) + + +def displace(N, α) -> jnp.ndarray: + """Displacement operator + + Args: + N: Hilbert Space Size + α: Phase space displacement + + Returns: + Displace operator D(α) + """ + a = destroy(N) + return expm(α * dag(a) - jnp.conj(α) * a) \ No newline at end of file diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py new file mode 100644 index 0000000..36aabc4 --- /dev/null +++ b/jaxquantum/core/qarray.py @@ -0,0 +1,176 @@ +""" QArray. """ + +from flax import struct +from enum import Enum +from jax import Array +from typing import List +from math import prod +import jax.numpy as jnp + + +DIMS_TYPE = List[List[int]] + +def isket_dims(dims: DIMS_TYPE) -> bool: + return prod(dims[1]) == 1 + +def isbra_dims(dims: DIMS_TYPE) -> bool: + return prod(dims[0]) == 1 + +def isop_dims(dims: DIMS_TYPE) -> bool: + return prod(dims[1]) == prod(dims[0]) + +class Qtypes(str, Enum): + ket = "ket" + bra = "bra" + oper = "oper" + + @classmethod + def from_dims(cls, dims: Array): + if isket_dims(dims): + return cls.ket + if isbra_dims(dims): + return cls.bra + if isop_dims(dims): + return cls.oper + raise ValueError("Invalid data shape") + + @classmethod + def from_str(cls, string: str): + return cls(string) + + def __str__(self): + return self.value + + def __repr__(self): + return self.__str__() + + def __eq__(self, other): + return self.value == other.value + + def __ne__(self, other): + return self.value != other.value + + def __hash__(self): + return hash(self.value) + + +def check_dims(dims: Array, data_shape: Array) -> bool: + assert data_shape[0] == prod(dims[0]), "Data shape should be consistent with dimensions." + assert data_shape[1] == prod(dims[1]), "Data shape should be consistent with dimensions." + +class Qdims: + def __init__(self, dims): + self._dims = dims + self._qtype = Qtypes.from_dims(self._dims) + + @property + def dims(self): + return self._dims + + @property + def from_(self): + return self._dims[1] + + @property + def to_(self): + return self._dims[0] + + @property + def qtype(self): + return self._qtype + + def __str__(self): + return str(self.dims) + + def __repr__(self): + return self.__str__() + + def __eq__(self, other): + return self.dims == other.dims + + def __ne__(self, other): + return self.dims != other.dims + + def __hash__(self): + return hash(self.dims) + + def __matmul__(self, other): + if self.from_ != other.to_: + raise TypeError(f"incompatible dimensions {self} and {other}") + + new_dims = [self.to_, other.from_] + return Qdims(new_dims) + + +@struct.dataclass # this allows us to send in and return Qarray from jitted functions +class Qarray: + _data: Array + _qdims: Qdims = struct.field(pytree_node=False) + + @classmethod + def create(cls, N, params, label=0, use_linear=True, N_pre_diag=None): + if N_pre_diag is None: + N_pre_diag = N + return cls(N, N_pre_diag, params, label, use_linear) + + @classmethod + def create(cls, data, dims=None): + # Prepare data ---- + data = jnp.asarray(data) + if len(data.shape) == 1: + data = data.reshape(data.shape[0], 1) + + # Prepare dimensions ---- + if dims is None: + dims = [[data.shape[0]], [data.shape[1]]] + + check_dims(dims, data.shape) + + qdims = Qdims(dims) + + return cls(data, qdims) + + + def __matmul__(self, other): + if not isinstance(other, Qarray): + try: + other = Qarray.create(other) + except TypeError: + return NotImplemented + + _qdims_new = self._qdims @ other._qdims + return Qarray.create( + self.data @ other.data, + dims=_qdims_new.dims, + ) + + @property + def qtype(self): + return self._qdims.qtype + + @property + def dtype(self): + return self._data.dtype + + @property + def dims(self): + return self._qdims.dims + + @property + def data(self): + return self._data + + + def _str_header(self): + out = ", ".join([ + "Quantum array: dims = " + str(self.dims), + "shape = " + str(self._data.shape), + "type = " + str(self.qtype), + ]) + return out + + def __str__(self): + return self._str_header() + "\nQarray data =\n" + str(self.data) + + def __repr__(self): + return self.__str__() \ No newline at end of file diff --git a/jaxquantum/core/qutip.py b/jaxquantum/core/qutip.py new file mode 100644 index 0000000..8c5bd32 --- /dev/null +++ b/jaxquantum/core/qutip.py @@ -0,0 +1,45 @@ +""" +Common jax <-> qutip-inspired functions +""" + +from jax import config + +import jax.numpy as jnp +import numpy as np +from qutip import Qobj + + +config.update("jax_enable_x64", True) + +# Convert between QuTiP and JAX +# =============================================================== +def qt2jax(qt_obj, dtype=jnp.complex128): + """QuTiP state -> JAX array. + + Args: + qt_obj: QuTiP state. + dtype: JAX dtype. + + Returns: + JAX array. + """ + if isinstance(qt_obj, jnp.ndarray) or qt_obj is None: + return qt_obj + return jnp.array(qt_obj, dtype=dtype) + + +def jax2qt(jax_obj, dims=None): + """JAX array -> QuTiP state. + + Args: + jax_obj: JAX array. + dims: QuTiP dims. + + Returns: + QuTiP state. + """ + if isinstance(jax_obj, Qobj) or jax_obj is None: + return jax_obj + if dims is not None: + dims = np.array(dims).astype(int).tolist() + return Qobj(np.array(jax_obj), dims=dims) diff --git a/jaxquantum/quantum/solvers.py b/jaxquantum/core/solvers.py similarity index 99% rename from jaxquantum/quantum/solvers.py rename to jaxquantum/core/solvers.py index 560cd4e..f2a3b73 100644 --- a/jaxquantum/quantum/solvers.py +++ b/jaxquantum/core/solvers.py @@ -8,7 +8,7 @@ from jax.experimental.ode import odeint import jax.numpy as jnp -from jaxquantum.quantum.base import dag +from jaxquantum.core.operations import dag from jaxquantum.utils.utils import ( is_1d, real_to_complex_iso_matrix, diff --git a/jaxquantum/core/states.py b/jaxquantum/core/states.py new file mode 100644 index 0000000..afb9443 --- /dev/null +++ b/jaxquantum/core/states.py @@ -0,0 +1,38 @@ +""" States. """ + +from jax import config +from jax.nn import one_hot + +import jax.numpy as jnp + +from jaxquantum.core.operators import displace + + +config.update("jax_enable_x64", True) + +def basis(N, k): + """Creates a |k> (i.e. fock state) ket in a specified Hilbert Space. + + Args: + N: Hilbert space dimension + k: fock number + + Returns: + Fock State |k> + """ + return one_hot(k, N).reshape(N, 1) + + +def coherent(N, α) -> jnp.ndarray: + """Coherent state. + + TODO: add trimming! + + Args: + N: Hilbert Space Size. + α: coherent state amplitude. + + Return: + Coherent state |α⟩. + """ + return displace(N, α) @ basis(N, 0) \ No newline at end of file diff --git a/jaxquantum/quantum/visualization.py b/jaxquantum/core/visualization.py similarity index 97% rename from jaxquantum/quantum/visualization.py rename to jaxquantum/core/visualization.py index 9f94f82..0e6fd40 100644 --- a/jaxquantum/quantum/visualization.py +++ b/jaxquantum/core/visualization.py @@ -6,7 +6,7 @@ import numpy as np import matplotlib.pyplot as plt -from jaxquantum.quantum.base import jax2qt +from jaxquantum.core.qutip import jax2qt WIGNER = "wigner" QFUNC = "qfunc" diff --git a/jaxquantum/quantum/__init__.py b/jaxquantum/quantum/__init__.py deleted file mode 100644 index 89a6ea0..0000000 --- a/jaxquantum/quantum/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Quantum Tooling""" - -from .base import * -from .visualization import * -from .solvers import * \ No newline at end of file diff --git a/jaxquantum/quantum/base.py b/jaxquantum/quantum/base.py deleted file mode 100644 index f47afa1..0000000 --- a/jaxquantum/quantum/base.py +++ /dev/null @@ -1,326 +0,0 @@ -""" -Common jax <-> qutip-inspired functions -""" - -from jax import config, Array -from jax.nn import one_hot - -import jax.numpy as jnp -import jax.scipy as jsp -import numpy as np -from qutip import Qobj - -from jaxquantum.utils.utils import is_1d - -config.update("jax_enable_x64", True) - - -# Convert between QuTiP and JAX -# =============================================================== -def qt2jax(qt_obj, dtype=jnp.complex128): - """QuTiP state -> JAX array. - - Args: - qt_obj: QuTiP state. - dtype: JAX dtype. - - Returns: - JAX array. - """ - if isinstance(qt_obj, jnp.ndarray) or qt_obj is None: - return qt_obj - return jnp.array(qt_obj, dtype=dtype) - - -def jax2qt(jax_obj, dims=None): - """JAX array -> QuTiP state. - - Args: - jax_obj: JAX array. - dims: QuTiP dims. - - Returns: - QuTiP state. - """ - if isinstance(jax_obj, Qobj) or jax_obj is None: - return jax_obj - if dims is not None: - dims = np.array(dims).astype(int).tolist() - return Qobj(np.array(jax_obj), dims=dims) - - -# QuTiP alternatives in JAX (some are a WIP) -# =============================================================== - - -def unit(rho: jnp.ndarray, use_density_matrix=False): - """Normalize density matrix. - - Args: - rho: density matrix - - Returns: - normalized density matrix - """ - if use_density_matrix: - evals, _ = jnp.linalg.eigh(rho @ jnp.conj(rho).T) - rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) - return rho / rho_norm - return rho / jnp.linalg.norm(rho) - - -def ket(vec: Array) -> Array: - """Turns a vector array into a ket. - - Args: - vec: vector - - Returns: - ket - """ - return vec.reshape(vec.shape[0], 1) - - -def dag(op: jnp.ndarray) -> jnp.ndarray: - """Conjugate transpose. - - Args: - op: operator - - Returns: - conjugate transpose of op - """ - op = op.reshape(op.shape[0], -1) # adds dimension to 1D array if needed - return jnp.conj(op).T - - -def batch_dag(op: jnp.ndarray) -> jnp.ndarray: - """Conjugate transpose. - - Args: - op: operator - - Returns: - conjugate of op, and transposes last two axes - """ - return jnp.moveaxis( - jnp.conj(op), -1, -2 - ) # transposes last two axes, good for batching - - -def ket2dm(ket: jnp.ndarray) -> jnp.ndarray: - """Turns ket into density matrix. - - Args: - ket: ket - - Returns: - Density matrix - """ - ket = ket.reshape(ket.shape[0], 1) - return ket @ dag(ket) - - -def basis(N, k): - """Creates a |k> (i.e. fock state) ket in a specified Hilbert Space. - - Args: - N: Hilbert space dimension - k: fock number - - Returns: - Fock State |k> - """ - return one_hot(k, N).reshape(N, 1) - - -def sigmax() -> jnp.ndarray: - """σx - - Returns: - σx Pauli Operator - """ - return jnp.array([[0.0, 1.0], [1.0, 0.0]]) - - -def sigmay() -> jnp.ndarray: - """σy - - Returns: - σy Pauli Operator - """ - return jnp.array([[0.0, -1.0j], [1.0j, 0.0]]) - - -def sigmaz() -> jnp.ndarray: - """σz - - Returns: - σz Pauli Operator - """ - return jnp.array([[1.0, 0.0], [0.0, -1.0]]) - - -def sigmam() -> jnp.ndarray: - """σ- - - Returns: - σ- Pauli Operator - """ - return jnp.array([[0.0, 0.0], [1.0, 0.0]]) - - -def sigmap() -> jnp.ndarray: - """σ+ - - Returns: - σ+ Pauli Operator - """ - return jnp.array([[0.0, 1.0], [0.0, 0.0]]) - - -def destroy(N) -> jnp.ndarray: - """annihilation operator - - Args: - N: Hilbert space size - - Returns: - annilation operator in Hilber Space of size N - """ - return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=1) - - -def create(N) -> jnp.ndarray: - """creation operator - - Args: - N: Hilbert space size - - Returns: - creation operator in Hilber Space of size N - """ - return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=-1) - - -def num(N) -> jnp.ndarray: - """Number operator - - Args: - N: Hilbert Space size - - Returns: - number operator in Hilber Space of size N - """ - return jnp.diag(jnp.arange(N)) - - -def coherent(N, α) -> jnp.ndarray: - """Coherent state. - - TODO: add trimming! - - Args: - N: Hilbert Space Size. - α: coherent state amplitude. - - Return: - Coherent state |α⟩. - """ - return displace(N, α) @ basis(N, 0) - - -def identity(*args, **kwargs) -> jnp.ndarray: - """Identity matrix. - - Returns: - Identity matrix. - """ - return jnp.eye(*args, **kwargs) - - -def displace(N, α) -> jnp.ndarray: - """Displacement operator - - Args: - N: Hilbert Space Size - α: Phase space displacement - - Returns: - Displace operator D(α) - """ - a = destroy(N) - return expm(α * dag(a) - jnp.conj(α) * a) - - -def ptrace(rho, indx, dims): - """Partial Trace. - - Args: - rho: density matrix - indx: index to trace out - dims: list of dimensions of the tensored hilbert spaces - - Returns: - partial traced out density matrix - - TODO: Fix weird tracing errors that arise with reshape - """ - if is_1d(rho): - rho = ket2dm(rho) - - Nq = len(dims) - - if isinstance(dims, jnp.ndarray): - dims2 = jnp.concatenate(jnp.array([dims, dims])) - else: - dims2 = dims + dims - - rho = rho.reshape(dims2) - - indxs = [indx, indx + Nq] - for j in range(Nq): - if j == indx: - continue - indxs.append(j) - indxs.append(j + Nq) - rho = rho.transpose(indxs) - - for j in range(Nq - 1): - rho = jnp.trace(rho, axis1=2, axis2=3) - - return rho - - -def expm(*args, **kwargs) -> jnp.ndarray: - """Matrix exponential wrapper. - - Returns: - matrix exponential - """ - return jsp.linalg.expm(*args, **kwargs) - - -def tensor(*args, **kwargs) -> jnp.ndarray: - """Tensor product. - - Args: - *args: tensors to take the product of - - Returns: - Tensor product of given tensors - - """ - ans = args[0] - for arg in args[1:]: - ans = jnp.kron(ans, arg) - return ans - - -def tr(*args, **kwargs) -> jnp.ndarray: - """Full trace. - - Returns: - Full trace. - """ - return jnp.trace(*args, **kwargs) diff --git a/tutorials/2-qarray.ipynb b/tutorials/2-qarray.ipynb new file mode 100644 index 0000000..0a02f98 --- /dev/null +++ b/tutorials/2-qarray.ipynb @@ -0,0 +1,320 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/qutip/__init__.py:66: UserWarning: The new version of Cython, (>= 3.0.0) is not supported.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "import jaxquantum as jqt\n", + "from jax import jit" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = [[2, 2], [2, 2]], shape = (4, 4), type = oper\n", + "Qarray data =\n", + "[[0. 1. 0. 0.]\n", + " [1. 0. 0. 0.]\n", + " [0. 0. 0. 1.]\n", + " [0. 0. 1. 0.]]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jqt.Qarray.create(jqt.tensor(jqt.sigmax(),jqt.sigmax()), dims=[[2,2], [2,2]])\n", + "b = jqt.Qarray.create(jqt.tensor(jqt.sigmax(), jqt.identity(2)), dims=[[2,2], [2,2]])\n", + "a @ b" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "@jit\n", + "def test(input_a):\n", + " b = jqt.Qarray.create(jqt.tensor(jqt.sigmax(), jqt.identity(2)), dims=[[2,2], [2,2]])\n", + " return input_a @ b" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5.1 µs ± 57.7 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit test(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.1 µs ± 52.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit a.data @ b.data" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/qutip/__init__.py:66: UserWarning: The new version of Cython, (>= 3.0.0) is not supported.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "import qutip as qt" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "Quantum object: dims = [[10, 10], [1, 1]], shape = (100, 1), type = ket $ \\\\ \\left(\\begin{matrix}0.368\\\\0.368\\\\0.260\\\\0.150\\\\0.075\\\\\\vdots\\\\6.091\\times10^{-05}\\\\2.486\\times10^{-05}\\\\9.404\\times10^{-06}\\\\3.297\\times10^{-06}\\\\1.210\\times10^{-06}\\\\\\end{matrix}\\right)$" + ], + "text/plain": [ + "Quantum object: dims = [[10, 10], [1, 1]], shape = (100, 1), type = ket\n", + "Qobj data =\n", + "[[3.67879441e-01]\n", + " [3.67879441e-01]\n", + " [2.60130047e-01]\n", + " [1.50186155e-01]\n", + " [7.50930613e-02]\n", + " [3.35827506e-02]\n", + " [1.37094277e-02]\n", + " [5.18515195e-03]\n", + " [1.81760459e-03]\n", + " [6.67227779e-04]\n", + " [3.67879441e-01]\n", + " [3.67879441e-01]\n", + " [2.60130047e-01]\n", + " [1.50186155e-01]\n", + " [7.50930613e-02]\n", + " [3.35827506e-02]\n", + " [1.37094277e-02]\n", + " [5.18515195e-03]\n", + " [1.81760459e-03]\n", + " [6.67227779e-04]\n", + " [2.60130047e-01]\n", + " [2.60130047e-01]\n", + " [1.83939720e-01]\n", + " [1.06197648e-01]\n", + " [5.30988128e-02]\n", + " [2.37465906e-02]\n", + " [9.69402930e-03]\n", + " [3.66645610e-03]\n", + " [1.28524053e-03]\n", + " [4.71801286e-04]\n", + " [1.50186155e-01]\n", + " [1.50186155e-01]\n", + " [1.06197648e-01]\n", + " [6.13132417e-02]\n", + " [3.06566143e-02]\n", + " [1.37101007e-02]\n", + " [5.59685050e-03]\n", + " [2.11682944e-03]\n", + " [7.42033975e-04]\n", + " [2.72394603e-04]\n", + " [7.50930613e-02]\n", + " [7.50930613e-02]\n", + " [5.30988128e-02]\n", + " [3.06566143e-02]\n", + " [1.53283038e-02]\n", + " [6.85504886e-03]\n", + " [2.79842465e-03]\n", + " [1.05841449e-03]\n", + " [3.71016908e-04]\n", + " [1.36197272e-04]\n", + " [3.35827506e-02]\n", + " [3.35827506e-02]\n", + " [2.37465906e-02]\n", + " [1.37101007e-02]\n", + " [6.85504886e-03]\n", + " [3.06568133e-03]\n", + " [1.25149775e-03]\n", + " [4.73338939e-04]\n", + " [1.65924362e-04]\n", + " [6.09094762e-05]\n", + " [1.37094277e-02]\n", + " [1.37094277e-02]\n", + " [9.69402930e-03]\n", + " [5.59685050e-03]\n", + " [2.79842465e-03]\n", + " [1.25149775e-03]\n", + " [5.10896743e-04]\n", + " [1.93230330e-04]\n", + " [6.77350130e-05]\n", + " [2.48649693e-05]\n", + " [5.18515195e-03]\n", + " [5.18515195e-03]\n", + " [3.66645610e-03]\n", + " [2.11682944e-03]\n", + " [1.05841449e-03]\n", + " [4.73338939e-04]\n", + " [1.93230330e-04]\n", + " [7.30831835e-05]\n", + " [2.56185992e-05]\n", + " [9.40437825e-06]\n", + " [1.81760459e-03]\n", + " [1.81760459e-03]\n", + " [1.28524053e-03]\n", + " [7.42033975e-04]\n", + " [3.71016908e-04]\n", + " [1.65924362e-04]\n", + " [6.77350130e-05]\n", + " [2.56185992e-05]\n", + " [8.98035082e-06]\n", + " [3.29661334e-06]\n", + " [6.67227779e-04]\n", + " [6.67227779e-04]\n", + " [4.71801286e-04]\n", + " [2.72394603e-04]\n", + " [1.36197272e-04]\n", + " [6.09094762e-05]\n", + " [2.48649693e-05]\n", + " [9.40437825e-06]\n", + " [3.29661334e-06]\n", + " [1.21015979e-06]]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qt.tensor(qt.coherent(10,1),qt.coherent(10,1))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "module 'qutip' has no attribute 'Dimensions'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m a \u001b[38;5;241m=\u001b[39m qt\u001b[38;5;241m.\u001b[39mcoherent(\u001b[38;5;241m10\u001b[39m,\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m \u001b[43mqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mDimensions\u001b[49m()\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'qutip' has no attribute 'Dimensions'" + ] + } + ], + "source": [ + "a = qt.coherent(10,1)\n", + "qt.Dimensions()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import jax.numpy as jnp\n", + "a = jnp.array([1,2])\n", + "a.size" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "jax-framework", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 21083eb689a541f3371a4229ae1e1a769465638e Mon Sep 17 00:00:00 2001 From: Phionx Date: Sat, 2 Mar 2024 22:53:13 -0500 Subject: [PATCH 02/10] almost finished migration to qarray, just solvers left --- jaxquantum/core/__init__.py | 1 - jaxquantum/core/operations.py | 128 +---------------- jaxquantum/core/operators.py | 52 +++++-- jaxquantum/core/qarray.py | 237 +++++++++++++++++++++++++++++-- jaxquantum/core/qutip.py | 31 ++-- jaxquantum/core/solvers.py | 163 +++++++++++---------- jaxquantum/core/states.py | 38 ----- jaxquantum/core/visualization.py | 6 +- tutorials/2-qarray.ipynb | 229 ++--------------------------- 9 files changed, 384 insertions(+), 501 deletions(-) delete mode 100644 jaxquantum/core/states.py diff --git a/jaxquantum/core/__init__.py b/jaxquantum/core/__init__.py index bbf2990..f88ee5b 100644 --- a/jaxquantum/core/__init__.py +++ b/jaxquantum/core/__init__.py @@ -1,6 +1,5 @@ """Quantum Tooling""" -from .states import * from .operators import * from .operations import * from .qutip import * diff --git a/jaxquantum/core/operations.py b/jaxquantum/core/operations.py index 244894a..d7ea9b1 100644 --- a/jaxquantum/core/operations.py +++ b/jaxquantum/core/operations.py @@ -1,61 +1,15 @@ """ Relevant linear algebra operations. """ -from jax import config, Array +from jax import config import jax.numpy as jnp -import jax.scipy as jsp -from jaxquantum.utils.utils import is_1d config.update("jax_enable_x64", True) - -# Kets & Density Matrices ----------------------------------------------------- - -def ket(vec: Array) -> Array: - """Turns a vector array into a ket. - - Args: - vec: vector - - Returns: - ket - """ - vec = jnp.asarray(vec) - return vec.reshape(vec.shape[0], 1) - - -def ket2dm(ket: jnp.ndarray) -> jnp.ndarray: - """Turns ket into density matrix. - - Args: - ket: ket - - Returns: - Density matrix - """ - ket = ket.reshape(ket.shape[0], 1) - return ket @ dag(ket) - - - # Linear Algebra Operations ----------------------------------------------------- - -def dag(op: jnp.ndarray) -> jnp.ndarray: - """Conjugate transpose. - - Args: - op: operator - - Returns: - conjugate transpose of op - """ - op = op.reshape(op.shape[0], -1) # adds dimension to 1D array if needed - return jnp.conj(op).T - - def batch_dag(op: jnp.ndarray) -> jnp.ndarray: """Conjugate transpose. @@ -70,89 +24,9 @@ def batch_dag(op: jnp.ndarray) -> jnp.ndarray: ) # transposes last two axes, good for batching -def unit(rho: jnp.ndarray, use_density_matrix=False): - """Normalize density matrix. - Args: - rho: density matrix - Returns: - normalized density matrix - """ - if use_density_matrix: - evals, _ = jnp.linalg.eigh(rho @ jnp.conj(rho).T) - rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) - return rho / rho_norm - return rho / jnp.linalg.norm(rho) - -def ptrace(rho, indx, dims): - """Partial Trace. - - Args: - rho: density matrix - indx: index to trace out - dims: list of dimensions of the tensored hilbert spaces - - Returns: - partial traced out density matrix - - TODO: Fix weird tracing errors that arise with reshape - """ - if is_1d(rho): - rho = ket2dm(rho) - - Nq = len(dims) - - if isinstance(dims, jnp.ndarray): - dims2 = jnp.concatenate(jnp.array([dims, dims])) - else: - dims2 = dims + dims - - rho = rho.reshape(dims2) - - indxs = [indx, indx + Nq] - for j in range(Nq): - if j == indx: - continue - indxs.append(j) - indxs.append(j + Nq) - rho = rho.transpose(indxs) - - for j in range(Nq - 1): - rho = jnp.trace(rho, axis1=2, axis2=3) - - return rho -def expm(*args, **kwargs) -> jnp.ndarray: - """Matrix exponential wrapper. - - Returns: - matrix exponential - """ - return jsp.linalg.expm(*args, **kwargs) - - -def tensor(*args, **kwargs) -> jnp.ndarray: - """Tensor product. - - Args: - *args: tensors to take the product of - - Returns: - Tensor product of given tensors - - """ - ans = args[0] - for arg in args[1:]: - ans = jnp.kron(ans, arg) - return ans -def tr(*args, **kwargs) -> jnp.ndarray: - """Full trace. - - Returns: - Full trace. - """ - return jnp.trace(*args, **kwargs) diff --git a/jaxquantum/core/operators.py b/jaxquantum/core/operators.py index 603b184..c5c39b3 100644 --- a/jaxquantum/core/operators.py +++ b/jaxquantum/core/operators.py @@ -3,9 +3,9 @@ from jax import config import jax.numpy as jnp +from jax.nn import one_hot -from jaxquantum.core.operations import expm, dag - +from jaxquantum.core.qarray import Qarray config.update("jax_enable_x64", True) @@ -17,7 +17,7 @@ def sigmax() -> jnp.ndarray: Returns: σx Pauli Operator """ - return jnp.array([[0.0, 1.0], [1.0, 0.0]]) + return Qarray.create(jnp.array([[0.0, 1.0], [1.0, 0.0]])) def sigmay() -> jnp.ndarray: @@ -26,7 +26,7 @@ def sigmay() -> jnp.ndarray: Returns: σy Pauli Operator """ - return jnp.array([[0.0, -1.0j], [1.0j, 0.0]]) + return Qarray.create(jnp.array([[0.0, -1.0j], [1.0j, 0.0]])) def sigmaz() -> jnp.ndarray: @@ -35,7 +35,7 @@ def sigmaz() -> jnp.ndarray: Returns: σz Pauli Operator """ - return jnp.array([[1.0, 0.0], [0.0, -1.0]]) + return Qarray.create(jnp.array([[1.0, 0.0], [0.0, -1.0]])) def sigmam() -> jnp.ndarray: @@ -44,7 +44,7 @@ def sigmam() -> jnp.ndarray: Returns: σ- Pauli Operator """ - return jnp.array([[0.0, 0.0], [1.0, 0.0]]) + return Qarray.create(jnp.array([[0.0, 0.0], [1.0, 0.0]])) def sigmap() -> jnp.ndarray: @@ -53,7 +53,7 @@ def sigmap() -> jnp.ndarray: Returns: σ+ Pauli Operator """ - return jnp.array([[0.0, 1.0], [0.0, 0.0]]) + return Qarray.create(jnp.array([[0.0, 1.0], [0.0, 0.0]])) def destroy(N) -> jnp.ndarray: @@ -65,7 +65,7 @@ def destroy(N) -> jnp.ndarray: Returns: annilation operator in Hilber Space of size N """ - return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=1) + return Qarray.create(jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=1)) def create(N) -> jnp.ndarray: @@ -77,7 +77,7 @@ def create(N) -> jnp.ndarray: Returns: creation operator in Hilber Space of size N """ - return jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=-1) + return Qarray.create(jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=-1)) def num(N) -> jnp.ndarray: @@ -89,7 +89,7 @@ def num(N) -> jnp.ndarray: Returns: number operator in Hilber Space of size N """ - return jnp.diag(jnp.arange(N)) + return Qarray.create(jnp.diag(jnp.arange(N))) def identity(*args, **kwargs) -> jnp.ndarray: @@ -98,7 +98,7 @@ def identity(*args, **kwargs) -> jnp.ndarray: Returns: Identity matrix. """ - return jnp.eye(*args, **kwargs) + return Qarray.create(jnp.eye(*args, **kwargs)) def displace(N, α) -> jnp.ndarray: @@ -112,4 +112,32 @@ def displace(N, α) -> jnp.ndarray: Displace operator D(α) """ a = destroy(N) - return expm(α * dag(a) - jnp.conj(α) * a) \ No newline at end of file + return (α * a.dag() - jnp.conj(α) * a).expm() + + +# States --------------------------------------------------------------------- + +def basis(N, k): + """Creates a |k> (i.e. fock state) ket in a specified Hilbert Space. + + Args: + N: Hilbert space dimension + k: fock number + + Returns: + Fock State |k> + """ + return Qarray.create(one_hot(k, N).reshape(N, 1)) + + +def coherent(N, α) -> jnp.ndarray: + """Coherent state. + + Args: + N: Hilbert Space Size. + α: coherent state amplitude. + + Return: + Coherent state |α⟩. + """ + return displace(N, α) @ basis(N, 0) \ No newline at end of file diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index 36aabc4..1c7849b 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -1,12 +1,17 @@ """ QArray. """ +import functools from flax import struct from enum import Enum -from jax import Array +from jax import Array, config from typing import List from math import prod +from copy import deepcopy +from numbers import Number import jax.numpy as jnp +import jax.scipy as jsp +config.update("jax_enable_x64", True) DIMS_TYPE = List[List[int]] @@ -102,17 +107,36 @@ def __matmul__(self, other): return Qdims(new_dims) +def _ensure_equal_type(method): + """ + Function decorator for Qarray method to ensure both operands are Qarray and + of the same type and dimensions. Promotes numeric scalar a to a*I, where I + is the identity matrix of the same type and dims. + """ + @functools.wraps(method) + def out(self, other): + if isinstance(other, Qarray): + if self.dims != other.dims: + msg = ( + "Dimensions are incompatible: " + + repr(self.dims) + " and " + repr(other.dims) + ) + raise ValueError(msg) + return method(self, other) + if other == 0: + return method(self, other) + if (self.data.shape[0] == self.data.shape[1]) and isinstance(other, Number): + scalar = complex(other) + other = Qarray.create(jnp.eye(self.data.shape[0], dtype=self.data.dtype) * scalar, dims=self.dims) + return method(self, other) + return NotImplemented + return out + @struct.dataclass # this allows us to send in and return Qarray from jitted functions class Qarray: _data: Array _qdims: Qdims = struct.field(pytree_node=False) - @classmethod - def create(cls, N, params, label=0, use_linear=True, N_pre_diag=None): - if N_pre_diag is None: - N_pre_diag = N - return cls(N, N_pre_diag, params, label, use_linear) - @classmethod def create(cls, data, dims=None): # Prepare data ---- @@ -128,22 +152,63 @@ def create(cls, data, dims=None): qdims = Qdims(dims) + dims = deepcopy(dims) return cls(data, qdims) - def __matmul__(self, other): + def _covert_to_qarray(self, other): if not isinstance(other, Qarray): try: other = Qarray.create(other) except TypeError: return NotImplemented - + return other + + def __matmul__(self, other): + other = self._covert_to_qarray(other) _qdims_new = self._qdims @ other._qdims return Qarray.create( self.data @ other.data, dims=_qdims_new.dims, ) + def __mul__(self, other): + if isinstance(other, Qarray): + return self.__matmul__(other) + + multiplier = complex(other) + return Qarray.create( + self.data * multiplier, + dims=self._qdims.dims, + ) + + def __rmul__(self, other): + return self.__mul__(other) + + def __truediv__(self, other): + return self.__mul__(1 / other) + + def __neg__(self): + return self.__mul__(-1) + + @_ensure_equal_type + def __add__(self, other): + if other == 0: + return self.copy() + return Qarray.create(self.data + other.data, dims=self.dims) + + def __radd__(self, other): + return self.__add__(other) + + @_ensure_equal_type + def __sub__(self, other): + if other == 0: + return self.copy() + return Qarray.create(self.data - other.data, dims=self.dims) + + def __rsub__(self, other): + return self.__neg__().__add__(other) + @property def qtype(self): return self._qdims.qtype @@ -173,4 +238,156 @@ def __str__(self): return self._str_header() + "\nQarray data =\n" + str(self.data) def __repr__(self): - return self.__str__() \ No newline at end of file + return self.__str__() + + def __xor__(self, other): + other = self._covert_to_qarray(other) + return tensor(self, other) + + def dag(self): + return dag(self) + + def to_dm(self): + return ket2dm(self) + + def copy(self): + return Qarray.create(deepcopy(self.data), dims=self.dims) + + def unit(self): + data = self.data + + if self.qtype == Qtypes.oper: + evals, _ = jnp.linalg.eigh(data @ jnp.conj(data).T) + rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) + data = data / rho_norm + elif self.qtype in [Qtypes.ket, Qtypes.bra]: + data = data / jnp.linalg.norm(data) + + return Qarray.create(data, dims=self.dims) + + def expm(self): + return expm(self) + + def tr(self, **kwargs): + return tr(self, **kwargs) + + def ptrace(self, indx, dims): + return ptrace(self, indx, dims) + + + +# Qarray operations --------------------------------------------------------------------- + +def tensor(*args, **kwargs) -> jnp.ndarray: + """Tensor product. + + Args: + *args (Qarray): tensors to take the product of + + Returns: + Tensor product of given tensors + + """ + data = args[0].data + dims = deepcopy(args[0].dims) + for arg in args[1:]: + data = jnp.kron(data, arg.data) + dims[0] += arg.dims[0] + dims[1] += arg.dims[1] + return Qarray.create(data, dims=dims) + +def tr(qarr: Qarray, **kwargs) -> jnp.ndarray: + """Full trace. + + Args: + qarr (Qarray): quantum array + + Returns: + Full trace. + """ + return jnp.trace(qarr.data, **kwargs) + +def expm(qarr: Qarray, **kwargs) -> jnp.ndarray: + """Matrix exponential wrapper. + + Returns: + matrix exponential + """ + data = jsp.linalg.expm(qarr.data, **kwargs) + dims = deepcopy(qarr.dims) + return Qarray.create(data, dims=dims) + +def ptrace(qarr: Qarray, indx, dims): + """Partial Trace. + + Args: + rho: density matrix + indx: index to trace out + dims: list of dimensions of the tensored hilbert spaces + + Returns: + partial traced out density matrix + + TODO: Fix weird tracing errors that arise with reshape + TODO: return Qarray + """ + + qarr = ket2dm(qarr) + rho = qarr.data + + Nq = len(dims) + + if isinstance(dims, jnp.ndarray): + dims2 = jnp.concatenate(jnp.array([dims, dims])) + else: + dims2 = dims + dims + + rho = rho.reshape(dims2) + + indxs = [indx, indx + Nq] + for j in range(Nq): + if j == indx: + continue + indxs.append(j) + indxs.append(j + Nq) + rho = rho.transpose(indxs) + + for j in range(Nq - 1): + rho = jnp.trace(rho, axis1=2, axis2=3) + + return rho + +# Kets & Density Matrices ----------------------------------------------------- + +def dag(qarr: Qarray) -> Qarray: + """Conjugate transpose. + + Args: + qarr (Qarray): quantum array + + Returns: + conjugate transpose of qarr + """ + data = jnp.conj(qarr.data).T + dims = deepcopy(qarr.dims) + dims = dims[::-1] + return Qarray.create(data, dims=dims) + +def ket2dm(qarr: Qarray) -> Qarray: + """Turns ket into density matrix. + Does nothing if already operator. + + Args: + qarr (Qarray): qarr + + Returns: + Density matrix + """ + + if qarr.qtype == Qtypes.oper: + return qarr + + if qarr.qtype == Qtypes.bra: + qarr = qarr.dag() + + return qarr @ qarr.dag() \ No newline at end of file diff --git a/jaxquantum/core/qutip.py b/jaxquantum/core/qutip.py index 8c5bd32..fea4cfc 100644 --- a/jaxquantum/core/qutip.py +++ b/jaxquantum/core/qutip.py @@ -3,43 +3,44 @@ """ from jax import config - +from qutip import Qobj import jax.numpy as jnp import numpy as np -from qutip import Qobj + + +from jaxquantum.core.qarray import Qarray config.update("jax_enable_x64", True) # Convert between QuTiP and JAX # =============================================================== -def qt2jax(qt_obj, dtype=jnp.complex128): - """QuTiP state -> JAX array. +def qt2jqt(qt_obj, dtype=jnp.complex128): + """QuTiP state -> Qarray. Args: qt_obj: QuTiP state. dtype: JAX dtype. Returns: - JAX array. + Qarray. """ - if isinstance(qt_obj, jnp.ndarray) or qt_obj is None: + if isinstance(qt_obj, Qarray) or qt_obj is None: return qt_obj - return jnp.array(qt_obj, dtype=dtype) + return Qarray.create(jnp.array(qt_obj, dtype=dtype), dims=qt_obj.dims) -def jax2qt(jax_obj, dims=None): - """JAX array -> QuTiP state. +def jqt2qt(jqt_obj): + """Qarray -> QuTiP state. Args: - jax_obj: JAX array. + jqt_obj: Qarray. dims: QuTiP dims. Returns: QuTiP state. """ - if isinstance(jax_obj, Qobj) or jax_obj is None: - return jax_obj - if dims is not None: - dims = np.array(dims).astype(int).tolist() - return Qobj(np.array(jax_obj), dims=dims) + if isinstance(jqt_obj, Qobj) or jqt_obj is None: + return jqt_obj + + return Qobj(np.array(jqt_obj.data), dims=jqt_obj.dims) diff --git a/jaxquantum/core/solvers.py b/jaxquantum/core/solvers.py index f2a3b73..33fadf4 100644 --- a/jaxquantum/core/solvers.py +++ b/jaxquantum/core/solvers.py @@ -8,7 +8,6 @@ from jax.experimental.ode import odeint import jax.numpy as jnp -from jaxquantum.core.operations import dag from jaxquantum.utils.utils import ( is_1d, real_to_complex_iso_matrix, @@ -21,22 +20,37 @@ ) -def spre(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: - """Superoperator generator. + +# ---- + +@jit +def calc_expect(op: jnp.ndarray, states: jnp.ndarray) -> jnp.ndarray: + """Calculate expectation value of an operator given a list of states. Args: - op: operator to be turned into a superoperator + op: operator + states: list of states Returns: - superoperator function + list of expectation values """ - op_dag = op.conj().T - return lambda rho: 0.5 * ( - 2 * op @ rho @ op_dag - rho @ op_dag @ op - op_dag @ op @ rho - ) + def calc_expect_ket_single(state: jnp.ndarray): + return (dag(state) @ op @ state)[0][0] -def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: + def calc_expect_dm_single(state: jnp.ndarray): + return jnp.trace(op @ state) + + if is_1d(states[0]): + return vmap(calc_expect_ket_single)(states) + else: + return vmap(calc_expect_dm_single)(states) + +# ---- + +# ---- + +def spre(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: """Superoperator generator. Args: @@ -45,12 +59,11 @@ def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: Returns: superoperator function """ - op_dag = conj_transpose_iso_matrix(op) + op_dag = op.conj().T return lambda rho: 0.5 * ( 2 * op @ rho @ op_dag - rho @ op_dag @ op - op_dag @ op @ rho ) - @partial( jit, static_argnums=(4,), @@ -120,60 +133,51 @@ def f( return sol.ys - @partial( jit, - static_argnums=(4,), + static_argnums=(3,), ) -def mesolve_iso( - ρ0: jnp.ndarray, +def sesolve( + ψ: jnp.ndarray, t_list: jnp.ndarray, - c_ops: Optional[List[jnp.ndarray]] = jnp.array([]), H0: Optional[jnp.ndarray] = None, Ht: Optional[Callable[[float], jnp.ndarray]] = None, ): - """Quantum Master Equation solver. + """Schrödinger Equation solver. Args: - ρ0: initial state, must be a density matrix. For statevector evolution, please use sesolve. + ψ: initial statevector t_list: time list - c_ops: list of collapse operators H0: time independent Hamiltonian. If H0 is not None, it will override Ht. Ht: time dependent Hamiltonian function. Returns: list of states """ - - ρ0 = complex_to_real_iso_matrix(jnp.asarray(ρ0) + 0.0j) - c_ops = vmap(complex_to_real_iso_matrix)(jnp.asarray(c_ops) + 0.0j) - H0 = None if H0 is None else complex_to_real_iso_matrix(jnp.asarray(H0) + 0.0j) + ψ = jnp.asarray(ψ) + 0.0j + H0 = None if H0 is None else jnp.asarray(H0) + 0.0j def f( t: float, - rho: jnp.ndarray, + ψₜ: jnp.ndarray, args: jnp.ndarray, ): H0_val = args[0] - c_ops_val = args[1] if H0_val is not None: H = H0_val # use H0 if given else: H = Ht(t) # type: ignore - H = complex_to_real_iso_matrix(H + 0.0j) - - rho_dot = -1 * imag_times_iso_matrix(H @ rho - rho @ H) - - for op in c_ops_val: - rho_dot += spre(op)(rho) + # print("H", H.shape) + # print("psit", ψₜ.shape) + ψₜ_dot = -1j * (H @ ψₜ) - return rho_dot + return ψₜ_dot term = ODETerm(f) solver = Dopri5() saveat = SaveAt(ts=t_list) - stepsize_controller = PIDController(rtol=1e-7, atol=1e-7) + stepsize_controller = PIDController(rtol=1e-6, atol=1e-6) sol = diffeqsolve( term, @@ -181,61 +185,87 @@ def f( t0=t_list[0], t1=t_list[-1], dt0=t_list[1] - t_list[0], - y0=ρ0, + y0=ψ, saveat=saveat, stepsize_controller=stepsize_controller, - args=[H0, c_ops], - max_steps=16**5, + args=[H0], ) - return vmap(real_to_complex_iso_matrix)(sol.ys) + return sol.ys + +# ---- + + +# ---- + +def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: + """Superoperator generator. + + Args: + op: operator to be turned into a superoperator + + Returns: + superoperator function + """ + op_dag = conj_transpose_iso_matrix(op) + return lambda rho: 0.5 * ( + 2 * op @ rho @ op_dag - rho @ op_dag @ op - op_dag @ op @ rho + ) @partial( jit, - static_argnums=(3,), + static_argnums=(4,), ) -def sesolve( - ψ: jnp.ndarray, +def mesolve_iso( + ρ0: jnp.ndarray, t_list: jnp.ndarray, + c_ops: Optional[List[jnp.ndarray]] = jnp.array([]), H0: Optional[jnp.ndarray] = None, Ht: Optional[Callable[[float], jnp.ndarray]] = None, ): - """Schrödinger Equation solver. + """Quantum Master Equation solver. Args: - ψ: initial statevector + ρ0: initial state, must be a density matrix. For statevector evolution, please use sesolve. t_list: time list + c_ops: list of collapse operators H0: time independent Hamiltonian. If H0 is not None, it will override Ht. Ht: time dependent Hamiltonian function. Returns: list of states """ - ψ = jnp.asarray(ψ) + 0.0j - H0 = None if H0 is None else jnp.asarray(H0) + 0.0j + + ρ0 = complex_to_real_iso_matrix(jnp.asarray(ρ0) + 0.0j) + c_ops = vmap(complex_to_real_iso_matrix)(jnp.asarray(c_ops) + 0.0j) + H0 = None if H0 is None else complex_to_real_iso_matrix(jnp.asarray(H0) + 0.0j) def f( t: float, - ψₜ: jnp.ndarray, + rho: jnp.ndarray, args: jnp.ndarray, ): H0_val = args[0] + c_ops_val = args[1] if H0_val is not None: H = H0_val # use H0 if given else: H = Ht(t) # type: ignore - # print("H", H.shape) - # print("psit", ψₜ.shape) - ψₜ_dot = -1j * (H @ ψₜ) + H = complex_to_real_iso_matrix(H + 0.0j) - return ψₜ_dot + rho_dot = -1 * imag_times_iso_matrix(H @ rho - rho @ H) + + for op in c_ops_val: + rho_dot += spre(op)(rho) + + return rho_dot term = ODETerm(f) solver = Dopri5() saveat = SaveAt(ts=t_list) - stepsize_controller = PIDController(rtol=1e-6, atol=1e-6) + stepsize_controller = PIDController(rtol=1e-7, atol=1e-7) sol = diffeqsolve( term, @@ -243,14 +273,14 @@ def f( t0=t_list[0], t1=t_list[-1], dt0=t_list[1] - t_list[0], - y0=ψ, + y0=ρ0, saveat=saveat, stepsize_controller=stepsize_controller, - args=[H0], + args=[H0, c_ops], + max_steps=16**5, ) - return sol.ys - + return vmap(real_to_complex_iso_matrix)(sol.ys) @partial( jit, @@ -313,26 +343,5 @@ def f( return vmap(real_to_complex_iso_vector)(sol.ys) +# ---- -@jit -def calc_expect(op: jnp.ndarray, states: jnp.ndarray) -> jnp.ndarray: - """Calculate expectation value of an operator given a list of states. - - Args: - op: operator - states: list of states - - Returns: - list of expectation values - """ - - def calc_expect_ket_single(state: jnp.ndarray): - return (dag(state) @ op @ state)[0][0] - - def calc_expect_dm_single(state: jnp.ndarray): - return jnp.trace(op @ state) - - if is_1d(states[0]): - return vmap(calc_expect_ket_single)(states) - else: - return vmap(calc_expect_dm_single)(states) diff --git a/jaxquantum/core/states.py b/jaxquantum/core/states.py deleted file mode 100644 index afb9443..0000000 --- a/jaxquantum/core/states.py +++ /dev/null @@ -1,38 +0,0 @@ -""" States. """ - -from jax import config -from jax.nn import one_hot - -import jax.numpy as jnp - -from jaxquantum.core.operators import displace - - -config.update("jax_enable_x64", True) - -def basis(N, k): - """Creates a |k> (i.e. fock state) ket in a specified Hilbert Space. - - Args: - N: Hilbert space dimension - k: fock number - - Returns: - Fock State |k> - """ - return one_hot(k, N).reshape(N, 1) - - -def coherent(N, α) -> jnp.ndarray: - """Coherent state. - - TODO: add trimming! - - Args: - N: Hilbert Space Size. - α: coherent state amplitude. - - Return: - Coherent state |α⟩. - """ - return displace(N, α) @ basis(N, 0) \ No newline at end of file diff --git a/jaxquantum/core/visualization.py b/jaxquantum/core/visualization.py index 0e6fd40..5152034 100644 --- a/jaxquantum/core/visualization.py +++ b/jaxquantum/core/visualization.py @@ -6,13 +6,13 @@ import numpy as np import matplotlib.pyplot as plt -from jaxquantum.core.qutip import jax2qt +from jaxquantum.core.qutip import jqt2qt WIGNER = "wigner" QFUNC = "qfunc" -def plot_qp(state, pts, dims=None, ax=None, contour=True, qp_type=WIGNER): +def plot_qp(state, pts, ax=None, contour=True, qp_type=WIGNER): """Plot quasi-probability distribution. TODO: decouple this from qutip. @@ -29,7 +29,7 @@ def plot_qp(state, pts, dims=None, ax=None, contour=True, qp_type=WIGNER): axis on which the plot was plotted. """ pts = np.array(pts) - state = jax2qt(state, dims=dims) + state = jqt2qt(state) if ax is None: _, ax = plt.subplots(1, figsize=(4, 3), dpi=200) # fig = ax.get_figure() diff --git a/tutorials/2-qarray.ipynb b/tutorials/2-qarray.ipynb index 0a02f98..df1ca6e 100644 --- a/tutorials/2-qarray.ipynb +++ b/tutorials/2-qarray.ipynb @@ -26,6 +26,7 @@ ], "source": [ "import jaxquantum as jqt\n", + "import jax.numpy as jnp\n", "from jax import jit" ] }, @@ -33,51 +34,35 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantum array: dims = [[2, 2], [2, 2]], shape = (4, 4), type = oper\n", - "Qarray data =\n", - "[[0. 1. 0. 0.]\n", - " [1. 0. 0. 0.]\n", - " [0. 0. 0. 1.]\n", - " [0. 0. 1. 0.]]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "a = jqt.Qarray.create(jqt.tensor(jqt.sigmax(),jqt.sigmax()), dims=[[2,2], [2,2]])\n", - "b = jqt.Qarray.create(jqt.tensor(jqt.sigmax(), jqt.identity(2)), dims=[[2,2], [2,2]])\n", - "a @ b" + "a = jqt.sigmax() ^ jqt.sigmay()" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "@jit\n", "def test(input_a):\n", - " b = jqt.Qarray.create(jqt.tensor(jqt.sigmax(), jqt.identity(2)), dims=[[2,2], [2,2]])\n", - " return input_a @ b" + " b = jqt.sigmax() ^ jqt.identity(2)\n", + " c = input_a @ b\n", + " d = c - 1\n", + " return d + input_a" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "5.1 µs ± 57.7 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" + "4.94 µs ± 55.2 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" ] } ], @@ -87,24 +72,7 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4.1 µs ± 52.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n" - ] - } - ], - "source": [ - "%timeit a.data @ b.data" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -119,181 +87,6 @@ "source": [ "import qutip as qt" ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "Quantum object: dims = [[10, 10], [1, 1]], shape = (100, 1), type = ket $ \\\\ \\left(\\begin{matrix}0.368\\\\0.368\\\\0.260\\\\0.150\\\\0.075\\\\\\vdots\\\\6.091\\times10^{-05}\\\\2.486\\times10^{-05}\\\\9.404\\times10^{-06}\\\\3.297\\times10^{-06}\\\\1.210\\times10^{-06}\\\\\\end{matrix}\\right)$" - ], - "text/plain": [ - "Quantum object: dims = [[10, 10], [1, 1]], shape = (100, 1), type = ket\n", - "Qobj data =\n", - "[[3.67879441e-01]\n", - " [3.67879441e-01]\n", - " [2.60130047e-01]\n", - " [1.50186155e-01]\n", - " [7.50930613e-02]\n", - " [3.35827506e-02]\n", - " [1.37094277e-02]\n", - " [5.18515195e-03]\n", - " [1.81760459e-03]\n", - " [6.67227779e-04]\n", - " [3.67879441e-01]\n", - " [3.67879441e-01]\n", - " [2.60130047e-01]\n", - " [1.50186155e-01]\n", - " [7.50930613e-02]\n", - " [3.35827506e-02]\n", - " [1.37094277e-02]\n", - " [5.18515195e-03]\n", - " [1.81760459e-03]\n", - " [6.67227779e-04]\n", - " [2.60130047e-01]\n", - " [2.60130047e-01]\n", - " [1.83939720e-01]\n", - " [1.06197648e-01]\n", - " [5.30988128e-02]\n", - " [2.37465906e-02]\n", - " [9.69402930e-03]\n", - " [3.66645610e-03]\n", - " [1.28524053e-03]\n", - " [4.71801286e-04]\n", - " [1.50186155e-01]\n", - " [1.50186155e-01]\n", - " [1.06197648e-01]\n", - " [6.13132417e-02]\n", - " [3.06566143e-02]\n", - " [1.37101007e-02]\n", - " [5.59685050e-03]\n", - " [2.11682944e-03]\n", - " [7.42033975e-04]\n", - " [2.72394603e-04]\n", - " [7.50930613e-02]\n", - " [7.50930613e-02]\n", - " [5.30988128e-02]\n", - " [3.06566143e-02]\n", - " [1.53283038e-02]\n", - " [6.85504886e-03]\n", - " [2.79842465e-03]\n", - " [1.05841449e-03]\n", - " [3.71016908e-04]\n", - " [1.36197272e-04]\n", - " [3.35827506e-02]\n", - " [3.35827506e-02]\n", - " [2.37465906e-02]\n", - " [1.37101007e-02]\n", - " [6.85504886e-03]\n", - " [3.06568133e-03]\n", - " [1.25149775e-03]\n", - " [4.73338939e-04]\n", - " [1.65924362e-04]\n", - " [6.09094762e-05]\n", - " [1.37094277e-02]\n", - " [1.37094277e-02]\n", - " [9.69402930e-03]\n", - " [5.59685050e-03]\n", - " [2.79842465e-03]\n", - " [1.25149775e-03]\n", - " [5.10896743e-04]\n", - " [1.93230330e-04]\n", - " [6.77350130e-05]\n", - " [2.48649693e-05]\n", - " [5.18515195e-03]\n", - " [5.18515195e-03]\n", - " [3.66645610e-03]\n", - " [2.11682944e-03]\n", - " [1.05841449e-03]\n", - " [4.73338939e-04]\n", - " [1.93230330e-04]\n", - " [7.30831835e-05]\n", - " [2.56185992e-05]\n", - " [9.40437825e-06]\n", - " [1.81760459e-03]\n", - " [1.81760459e-03]\n", - " [1.28524053e-03]\n", - " [7.42033975e-04]\n", - " [3.71016908e-04]\n", - " [1.65924362e-04]\n", - " [6.77350130e-05]\n", - " [2.56185992e-05]\n", - " [8.98035082e-06]\n", - " [3.29661334e-06]\n", - " [6.67227779e-04]\n", - " [6.67227779e-04]\n", - " [4.71801286e-04]\n", - " [2.72394603e-04]\n", - " [1.36197272e-04]\n", - " [6.09094762e-05]\n", - " [2.48649693e-05]\n", - " [9.40437825e-06]\n", - " [3.29661334e-06]\n", - " [1.21015979e-06]]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "qt.tensor(qt.coherent(10,1),qt.coherent(10,1))" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "module 'qutip' has no attribute 'Dimensions'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[11], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m a \u001b[38;5;241m=\u001b[39m qt\u001b[38;5;241m.\u001b[39mcoherent(\u001b[38;5;241m10\u001b[39m,\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m----> 2\u001b[0m \u001b[43mqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mDimensions\u001b[49m()\n", - "\u001b[0;31mAttributeError\u001b[0m: module 'qutip' has no attribute 'Dimensions'" - ] - } - ], - "source": [ - "a = qt.coherent(10,1)\n", - "qt.Dimensions()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import jax.numpy as jnp\n", - "a = jnp.array([1,2])\n", - "a.size" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From da921c9c786170725a79d27c9bb20a2bd386bc44 Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 00:10:39 -0500 Subject: [PATCH 03/10] migrated solvers to use Qarray --- jaxquantum/core/__init__.py | 2 +- jaxquantum/core/{qutip.py => conversions.py} | 18 +- jaxquantum/core/qarray.py | 10 +- jaxquantum/core/solvers.py | 65 +++-- jaxquantum/core/visualization.py | 2 +- tutorials/1-single-qubit-rabi-qarray.ipynb | 287 +++++++++++++++++++ tutorials/1-single-qubit-rabi.ipynb | 2 +- tutorials/2-qarray.ipynb | 40 +++ 8 files changed, 387 insertions(+), 39 deletions(-) rename jaxquantum/core/{qutip.py => conversions.py} (69%) create mode 100644 tutorials/1-single-qubit-rabi-qarray.ipynb diff --git a/jaxquantum/core/__init__.py b/jaxquantum/core/__init__.py index f88ee5b..28a73ac 100644 --- a/jaxquantum/core/__init__.py +++ b/jaxquantum/core/__init__.py @@ -2,7 +2,7 @@ from .operators import * from .operations import * -from .qutip import * +from .conversions import * from .visualization import * from .solvers import * from .qarray import * \ No newline at end of file diff --git a/jaxquantum/core/qutip.py b/jaxquantum/core/conversions.py similarity index 69% rename from jaxquantum/core/qutip.py rename to jaxquantum/core/conversions.py index fea4cfc..1ba1877 100644 --- a/jaxquantum/core/qutip.py +++ b/jaxquantum/core/conversions.py @@ -1,14 +1,15 @@ """ -Common jax <-> qutip-inspired functions +Converting between different object types. """ -from jax import config +from jax import config, Array from qutip import Qobj +from typing import Optional import jax.numpy as jnp import numpy as np -from jaxquantum.core.qarray import Qarray +from jaxquantum.core.qarray import Qarray, DIMS_TYPE config.update("jax_enable_x64", True) @@ -44,3 +45,14 @@ def jqt2qt(jqt_obj): return jqt_obj return Qobj(np.array(jqt_obj.data), dims=jqt_obj.dims) + +def jnp2jqt(arr: Array, dims: Optional[DIMS_TYPE] = None): + """JAX array -> QuTiP state. + + Args: + jnp_obj: JAX array. + + Returns: + QuTiP state. + """ + return Qarray.create(arr, dims=dims) \ No newline at end of file diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index 1c7849b..fa59c36 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -123,10 +123,8 @@ def out(self, other): ) raise ValueError(msg) return method(self, other) - if other == 0: - return method(self, other) - if (self.data.shape[0] == self.data.shape[1]) and isinstance(other, Number): - scalar = complex(other) + if (self.data.shape[0] == self.data.shape[1]): + scalar = other + 0.0j other = Qarray.create(jnp.eye(self.data.shape[0], dtype=self.data.dtype) * scalar, dims=self.dims) return method(self, other) return NotImplemented @@ -176,7 +174,7 @@ def __mul__(self, other): if isinstance(other, Qarray): return self.__matmul__(other) - multiplier = complex(other) + multiplier = other + 0.0j return Qarray.create( self.data * multiplier, dims=self._qdims.dims, @@ -193,8 +191,6 @@ def __neg__(self): @_ensure_equal_type def __add__(self, other): - if other == 0: - return self.copy() return Qarray.create(self.data + other.data, dims=self.dims) def __radd__(self, other): diff --git a/jaxquantum/core/solvers.py b/jaxquantum/core/solvers.py index 33fadf4..92b3f66 100644 --- a/jaxquantum/core/solvers.py +++ b/jaxquantum/core/solvers.py @@ -5,7 +5,6 @@ from diffrax import diffeqsolve, Dopri5, ODETerm, SaveAt, PIDController from jax import jit, vmap -from jax.experimental.ode import odeint import jax.numpy as jnp from jaxquantum.utils.utils import ( @@ -19,12 +18,14 @@ conj_transpose_iso_matrix, ) +from jaxquantum.core.qarray import Qarray + # ---- @jit -def calc_expect(op: jnp.ndarray, states: jnp.ndarray) -> jnp.ndarray: +def calc_expect(op: Qarray, states: jnp.ndarray) -> jnp.ndarray: """Calculate expectation value of an operator given a list of states. Args: @@ -35,8 +36,10 @@ def calc_expect(op: jnp.ndarray, states: jnp.ndarray) -> jnp.ndarray: list of expectation values """ + op = op.data + def calc_expect_ket_single(state: jnp.ndarray): - return (dag(state) @ op @ state)[0][0] + return (jnp.conj(state).T @ op @ state)[0][0] def calc_expect_dm_single(state: jnp.ndarray): return jnp.trace(op @ state) @@ -69,11 +72,11 @@ def spre(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: static_argnums=(4,), ) def mesolve( - ρ0: jnp.ndarray, + ρ0: Qarray, t_list: jnp.ndarray, - c_ops: Optional[List[jnp.ndarray]] = jnp.array([]), - H0: Optional[jnp.ndarray] = None, - Ht: Optional[Callable[[float], jnp.ndarray]] = None, + c_ops: Optional[List[Qarray]] = None, + H0: Optional[Qarray] = None, + Ht: Optional[Callable[[float], Qarray]] = None, ): """Quantum Master Equation solver. @@ -88,9 +91,10 @@ def mesolve( list of states """ - ρ0 = jnp.asarray(ρ0) + 0.0j - c_ops = jnp.asarray(c_ops) + 0.0j - H0 = jnp.asarray(H0) + 0.0j if H0 is not None else H0 + ρ0 = jnp.asarray(ρ0.data) + 0.0j + c_ops = c_ops or [] + c_ops = jnp.asarray([c_op.data for c_op in c_ops]) + 0.0j + H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None def f( t: float, @@ -103,7 +107,7 @@ def f( if H0_val is not None: H = H0_val # use H0 if given else: - H = Ht(t) # type: ignore + H = Ht(t).data # type: ignore H = H + 0.0j rho_dot = -1j * (H @ rho - rho @ H) @@ -138,10 +142,10 @@ def f( static_argnums=(3,), ) def sesolve( - ψ: jnp.ndarray, + ψ: Qarray, t_list: jnp.ndarray, - H0: Optional[jnp.ndarray] = None, - Ht: Optional[Callable[[float], jnp.ndarray]] = None, + H0: Optional[Qarray] = None, + Ht: Optional[Callable[[float], Qarray]] = None, ): """Schrödinger Equation solver. @@ -154,8 +158,8 @@ def sesolve( Returns: list of states """ - ψ = jnp.asarray(ψ) + 0.0j - H0 = None if H0 is None else jnp.asarray(H0) + 0.0j + ψ = jnp.asarray(ψ.data) + 0.0j + H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None def f( t: float, @@ -167,7 +171,7 @@ def f( if H0_val is not None: H = H0_val # use H0 if given else: - H = Ht(t) # type: ignore + H = Ht(t).data # type: ignore # print("H", H.shape) # print("psit", ψₜ.shape) ψₜ_dot = -1j * (H @ ψₜ) @@ -218,11 +222,11 @@ def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: static_argnums=(4,), ) def mesolve_iso( - ρ0: jnp.ndarray, + ρ0: Qarray, t_list: jnp.ndarray, - c_ops: Optional[List[jnp.ndarray]] = jnp.array([]), - H0: Optional[jnp.ndarray] = None, - Ht: Optional[Callable[[float], jnp.ndarray]] = None, + c_ops: Optional[List[Qarray]] = None, + H0: Optional[Qarray] = None, + Ht: Optional[Callable[[float], Qarray]] = None, ): """Quantum Master Equation solver. @@ -236,6 +240,11 @@ def mesolve_iso( Returns: list of states """ + + ρ0 = jnp.asarray(ρ0.data) + 0.0j + c_ops = c_ops or [] + c_ops = jnp.asarray([c_op.data for c_op in c_ops]) + 0.0j + H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None ρ0 = complex_to_real_iso_matrix(jnp.asarray(ρ0) + 0.0j) c_ops = vmap(complex_to_real_iso_matrix)(jnp.asarray(c_ops) + 0.0j) @@ -252,7 +261,7 @@ def f( if H0_val is not None: H = H0_val # use H0 if given else: - H = Ht(t) # type: ignore + H = Ht(t).data # type: ignore H = complex_to_real_iso_matrix(H + 0.0j) rho_dot = -1 * imag_times_iso_matrix(H @ rho - rho @ H) @@ -287,10 +296,10 @@ def f( static_argnums=(3,), ) def sesolve_iso( - ψ: jnp.ndarray, + ψ: Qarray, t_list: jnp.ndarray, - H0: Optional[jnp.ndarray] = None, - Ht: Optional[Callable[[float], jnp.ndarray]] = None, + H0: Optional[Qarray] = None, + Ht: Optional[Callable[[float], Qarray]] = None, ): """Schrödinger Equation solver. @@ -303,6 +312,10 @@ def sesolve_iso( Returns: list of states """ + + ψ = jnp.asarray(ψ.data) + 0.0j + H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None + ψ = complex_to_real_iso_vector(jnp.asarray(ψ) + 0.0j) H0 = None if H0 is None else complex_to_real_iso_matrix(jnp.asarray(H0) + 0.0j) @@ -316,7 +329,7 @@ def f( if H0_val is not None: H = H0_val # use H0 if given else: - H = Ht(t) # type: ignore + H = Ht(t).data # type: ignore H = complex_to_real_iso_matrix(H) # print("H", H.shape) # print("psit", ψₜ.shape) diff --git a/jaxquantum/core/visualization.py b/jaxquantum/core/visualization.py index 5152034..794f874 100644 --- a/jaxquantum/core/visualization.py +++ b/jaxquantum/core/visualization.py @@ -6,7 +6,7 @@ import numpy as np import matplotlib.pyplot as plt -from jaxquantum.core.qutip import jqt2qt +from jaxquantum.core.conversions import jqt2qt WIGNER = "wigner" QFUNC = "qfunc" diff --git a/tutorials/1-single-qubit-rabi-qarray.ipynb b/tutorials/1-single-qubit-rabi-qarray.ipynb new file mode 100644 index 0000000..5e04e15 --- /dev/null +++ b/tutorials/1-single-qubit-rabi-qarray.ipynb @@ -0,0 +1,287 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "# After following the installation (or building from source) instructions,\n", + "# you should be able to import jaxquantum without having to add it to\n", + "# your path explicitly. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/qutip/__init__.py:66: UserWarning: The new version of Cython, (>= 3.0.0) is not supported.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "import jaxquantum as jqt\n", + "from jax import jit\n", + "import jax.numpy as jnp\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "omega_q = 5.0 #GHz\n", + "Omega = .1\n", + "g_state = jqt.basis(2,0) ^ jqt.basis(2,0)\n", + "g_state_dm = g_state.to_dm()\n", + "\n", + "ts = jnp.linspace(0,5*jnp.pi/Omega,101)\n", + "c_ops = [0.1*jqt.sigmam()^jqt.identity(N=2)]\n", + "\n", + "sz0 = jqt.sigmaz() ^ jqt.identity(N=2)\n", + "\n", + "@jit\n", + "def Ht(t):\n", + " H0 = omega_q/2.0*((jqt.sigmaz()^jqt.identity(N=2)) + (jqt.identity(N=2)^jqt.sigmaz()))\n", + " H1 = Omega*jnp.cos((omega_q)*t)*((jqt.sigmax()^jqt.identity(N=2)) + (jqt.identity(N=2)^jqt.sigmax()))\n", + " return H0 + H1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Schroedinger's Equation" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:49: UserWarning: Complex dtype support is work in progress, please read https://github.com/patrick-kidger/diffrax/pull/197 and proceed carefully.\n", + " out = fun(*args, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "457 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "1.39 ms ± 10.7 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" + ] + } + ], + "source": [ + "%timeit -n1 -r1 jqt.sesolve(g_state, ts, Ht=Ht) \n", + "%timeit jqt.sesolve(g_state, ts, Ht=Ht) " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "states = jqt.sesolve(g_state, ts, Ht=Ht) \n", + "szt = jnp.real(jqt.calc_expect(sz0, states))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwwAAAJECAYAAAC7A6POAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAB7CAAAewgFu0HU+AACoYElEQVR4nOzdd3xb1d0/8M/VtuW9Z+IkjuNMCBlNSEISRiihEAKFUFbY0AIP7Y8CT8dDaZ8OoEChPB0EAoEyG1ooECgzAbIJCUnIsp3lvbdsWZZ0f3+4VnyPLE9JV9L9vF+vvF729bV0rNwrne853/M9kizLMoiIiIiIiPqhU7sBREREREQUuhgwEBERERGRTwwYiIiIiIjIJwYMRERERETkEwMGIiIiIiLyiQEDERERERH5xICBiIiIiIh8YsBAREREREQ+MWAgIiIiIiKfGDAQEREREZFPDBiIiIiIiMgnBgxEREREROQTAwYiIiIiIvKJAQMREREREfnEgIGIiIiIiHxiwEBERERERD4xYCAiIiIiIp8MajeAwpvdbsf+/fsBAKmpqTAYeEkRERERqcHpdKKurg4AMH36dFgsFr88Lnt3NCr79+/H3Llz1W4GEREREfWxc+dOzJkzxy+PxZQkIiIiIiLyiTMMNCqpqamer3fu3InMzEwVW0NERESkXVVVVZ7Mj759tNFiwECj0nfNQmZmJnJyclRsDREREREB8Ou6UqYkERERERGRTwwYiIiIiIjIJwYMRERERETkEwMGIiIiIiLyiQEDERERERH5xICBiIiIiIh8YsBAREREREQ+MWAgIiIiIiKfGDAEQG1tLd5991088MADuOCCC5CSkgJJkiBJEq6//vqAPOerr76KZcuWISMjAxaLBWPHjsU111yDbdu2BeT5iIiIiEgbuNNzAKSnpwftuTo7O/Hd734X7733nuJ4aWkpXn75Zbz66qt44IEH8Itf/CJobSIiIiKiyMEZhgAbM2YMli1bFrDHv/HGGz3BwtKlS/HWW29h586dWLt2LSZMmAC3240HH3wQa9asCVgbiIiIiChycYYhAB544AHMmTMHc+bMQXp6Ok6cOIFx48b5/Xk+/fRTvPbaawCAiy66CG+++Sb0ej0AYM6cObj44osxa9YslJaW4v7778fll1+OxMREv7eDiIiIiCIXZxgC4Je//CW+853vBDw1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4LbbbkNlZSX+93//F0ePHsWNN97odc7y5cvx3HPPjejxiYiIiEjbmJIUAX75y19i8+bNuOqqq5CbmwuTyYS0tDScd955eOWVV7BhwwbFRm9EREREREMlybIsq90ICl/l5eWevR3Kysq40RsRERGRSgLVL+MMAxERERER+cSAgYiIiIiIfGLAQEREREREPjFgICIiIiIinxgwEBERERGRTwwYiIiIiIjIJ27cRqPidDo9X1dVVanYEiIiIiJt69sX69tHGy0GDDQqdXV1nq/nzp2rYkuIiIiIqFddXR3y8vL88lhMSSIiIiIiIp+40zONit1ux/79+wEAqampMBgCO2lVVVXlmcnYuXMnMjMzA/p8WsTXOPD4GgcWX9/A42sceHyNAy8SX2On0+nJ/pg+fTosFotfHpcpSTQqFosFc+bMUeW5MzMz/bblOfWPr3Hg8TUOLL6+gcfXOPD4GgdeJL3G/kpD6ospSURERERE5BMDBiIiIiIi8okBAxERERER+cSAgYiIiIiIfGLAQEREREREPjFgICIiIiIinxgwEBERERGRT9y4jYiIiIiIfOIMAxERERER+cSAgYiIiIiIfGLAQEREREREPjFgICIiIiIinxgwEBERERGRTwwYiIiIiIjIJwYMRERERETkEwMGIiIiIiLyiQEDERERERH5xICBiIiIiIh8YsBAYePkyZO45557UFhYCKvViqSkJMyZMwe///3v0dHRoXbzQtKuXbvwq1/9CsuWLUNOTg7MZjNiYmJQUFCAG264AZs3bx70MdatWwdJkob0b926dYH/o0LMUF+bJUuWDPpY77//PlauXOn5v8rJycHKlSvx/vvvB/4PCVFLliwZ8mvc+2/Tpk2Kx9D6NVxbW4t3330XDzzwAC644AKkpKR4/t7rr79+2I/nj+vU6XTir3/9KxYtWoTU1FRERUVhwoQJuO2223DgwIFht0lt/niNOzo68M9//hPf//73MWfOHCQmJsJoNCI5ORnz58/Hgw8+iOrq6kEfZzj3TLjwx+vr7/eBjo4OPPLII5gzZw6SkpJgtVpRWFiIe+65BydPnhzdHxyKZKIw8Pbbb8txcXEygH7/FRQUyMXFxWo3M6QsWrTI5+vV9991110nd3V1+Xyc559/fkiPA0B+/vnng/cHhoihvjaLFy/2+Rgul0u+6aabBvz9m2++WXa5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fhvXXXcd3G43MjMz8cknn/g1cOK17ttAr81Qr2Gn0+nZWC9S3q8ZMFBIW7hwIYCeaP6rr77yeV7fqcMFCxYEvF3hpKWlBeeff75nRPahhx7CHXfc4dfnqKur85TAzcrK8utjR4K+tbv7vj7jxo3zfD9YWsDnn38OAMjOzkZeXp7/Gxkmuru78dprrwHoGbm74IIL/PK4vIZ989d12vt+PtjjVFdXo6ioCIA2388/+eQTXHHFFXA6nUhOTsZHH32ECRMm+PU5fL0nad1g7wNDvYZ37drlyXqIlGuYAQOFtEsuucTz9fPPP9/vOW632zPimJCQgKVLlwajaWGho6MDF154IXbv3g0A+NnPfob777/f78+zZs0ayLIMYPCdYLXm+PHj+OijjwAAEyZMQHZ2tudnkiR50sIOHz6M7du39/sY27dv94xmrVixQtOjg++//z7q6uoAAFddddWAGzoOB69h3/x1nRYUFHhGW//+97+jo6Oj38dZt26d5+uVK1eOtvlhZevWrVixYgW6uroQHx+PDz74AFOnTvXrczidTjz33HOe7/2xji1SDPY+sGTJEsTHxwMAXnjhBc+5ooi8htUt0kQ0uEWLFskAZIPBIG/dutXr54888oindNkvfvGL4DcwRHV1dcnLli3zvDZ33333sB/j+PHj8u7duwc855133pFNJpMMQI6KipLLy8tH2OLw8/bbb8vd3d0+fy6WVX3ssce8zjly5Iis1+tlAPLs2bPljo4Oxc87Ojrk2bNne+6BoqIiv/8d4eSyyy7zvJ5fffXVoOfzGvY2kpKU/rpO165d63nuO+64w+vnJSUlclxcnAxAzs/PH/D+CmUjeY337NkjJyQkyABkq9Uqb968edjP++mnnw6rrOpFF1007OcIBcN9ff35PtC35O8jjzzi9fOtW7d6ypYvXrx4KH9OWODGbRTynnzySSxYsACdnZ1YtmwZfvrTn2Lp0qXo7OzEa6+9hjVr1gDoGb265557VG5t6Pje976HDz/8EABw9tln46abbsI333zj83yTyYSCggLFsRMnTmDp0qWYP38+LrroIpx22mmeEqzHjh3DG2+8gTfeeMMzyvLoo48qRtAj3V133YXu7m5cdtllmD9/PvLy8hAVFYX6+nps2rQJTz/9tGd6e+HChf2mghUUFODee+/FQw89hF27dmHBggW4//77MWHCBBw9ehQPP/ww9uzZAwC49957MXHixKD+jaGkqakJ7777LgBg2rRpOOOMMwb9HV7DwObNm1FSUuL5vveaBHrWh/UdDQWA66+/3usx/HWdrl69Gs899xy2bNmCP/3pT6iursYtt9yCxMRE7Ny5E//7v/+L1tZW6HQ6/PGPf/TbDFKgjfY1Pnr0KM4//3zPQtpf//rXiI+PH/A9Oy0tzask9gsvvICLL74YF198MZYsWYJJkyYhLi4O7e3t+Oqrr7BmzRpPOlJaWhqefPLJEfy1wTfa19ef7wP33nsvXn/9dRQVFeG+++5DSUkJrrzySkRFRWHjxo347W9/C6fTiaioKDzxxBOj/+NDhcoBC9GQvP32255Rp/7+FRQUyMXFxWo3M6T4eq18/Rs7dqzXY2zcuHFIvxsdHS0//fTTwf8jVTZ27NghvT6XXXbZgKN+LpdLvvHGGwd8jJtuumnQza4i3V/+8pcBR/b6w2tYVowoD+WfL/66Tuvq6uQ5c+b4fAyz2Sw/88wz/n4ZAmq0r/Hzzz8/7Pfs/mbUh9qO6dOnywcOHAjCK+Mfo319/f0+UFxcLE+cONHn48TFxcnvvPNOIF4K1YRH6E6ad9FFF2Hfvn148sknsWHDBpSXl8NkMiE/Px+XX3457rzzTkRHR6vdzIgza9YsvPTSS9i2bRt27dqFqqoq1NfXw+l0IjExEVOnTsU555yDm2++ecDN3yLVCy+8gM8++wzbtm3DsWPHUF9fj9bWVsTExCA3NxdnnnkmVq9ejfnz5w/4ODqdDmvXrsVll12GNWvW4Msvv0R9fT1SUlIwZ84c3HbbbX5b3BvOevde0Ov1uPrqq4f0O7yG/cdf12lKSgq2bt2KZ555Bq+88goOHToEm82GrKwsnHPOObj77rv9nrevFffffz9OP/10bNu2DQcPHkRdXR0aGxthNpuRnp6O2bNn47vf/S5WrlwJvV6vdnODxt/vA/n5+dizZw/+9Kc/Yf369SgpKYHD4UBubi6WL1+Ou+++G2PHjg3CXxY8kiz7WLFBRERERESaxypJRERERETkEwMGIiIiIiLyiQEDERERERH5xICBiIiIiIh8YsBAREREREQ+MWAgIiIiIiKfGDAQEREREZFPDBiIiIiIiMgnBgxEREREROQTAwYiIiIiIvKJAQMREREREfnEgIGIiIiIiHxiwEBERERERD4xYCAiIiIiIp8YMBARERERkU8MGIiIiIiIyCcGDERERERE5BMDBiIi8qt169ZBkiRIkoQTJ06o3ZygO3LkCEwmEywWCyoqKtRujkJHRwfS0tIgSRI2bdqkdnOI/n979x9TVf3HcfyFwgXZcFcZNIRANFxG6DQjcUtcUBpuVitDq9t01gr7Ja6WtfXD0K21tpbGcg2itubdcrLMpRUCo5uIXq1rNZgzCzahoKJcAinE+f7h7vle4J7r5Qpdgedju9vZ/bzP+bw/96/P+34+5xyMERQMAABJUnNzsznRv5LPRLd582b19vZqw4YNSk5ODnc6A8TGxmrz5s2SpE2bNskwjDBnBGAsoGAAAGCE1NfX68CBA7LZbNqyZUu40/HriSee0PTp03Xy5Ent2bMn3OkAGAMiDP5eAABI6u3t1alTpyzbs7KyJEmLFi1SRUWFZdyNN9444rmNFQUFBTp48KAefPBBffTRR+FOx9ILL7yg119/XVlZWfruu+/CnQ6AqxwFAwAgKN7tRrm5uex/9+PUqVOaO3euDMPQwYMHtWLFinCnZOn777/XvHnzJEm1tbVatmxZeBMCcFVjSxIAACOgoqJChmEoMTFR+fn54U4noKysLHPFqLy8PMzZALjaUTAAAEbU5Z6StGzZMkVERJj/av/44496/PHHNWvWLE2ZMkUzZ87Uhg0b1NLSMuC8H374QevXr9esWbMUExOja6+9VkVFRero6Agqr08++USrV69WamqqYmJiZLfbtWjRIm3dulV//vnnlQ5bH3/8sSTprrvuUmRkpGWc97d59dVXJUlut1tr165VSkqKoqOjlZycLIfDoaampoD9/fXXX9q+fbtycnI0bdo0RUVFKSEhQTfccIPuuecevfvuu2pvb7c8/95775V06Xf5559/hjlaABOKAQBAECQZkozc3NyAcRUVFWbszz//PKQ9NzfXvE5VVZURFxdnxvt+EhMTjaamJsMwDGP37t2GzWbzG5eWlma0trZa5tPZ2Wncdtttfs/17evIkSMh/zbNzc3mtcrLywPGeuNeeeUVo7S01IiMjPSbU2xsrFFXV+f3Go2NjcaMGTMCjkmSsXPnTss8Pv/8czPuyy+/DHnsAMY/VhgAAGHR1tam+++/X3a7XTt37tTRo0flcrm0adMmRUREqKOjQ4888ojcbrcefvhhzZ49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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, dpi=200, figsize=(4,3))\n", + "ax.plot(ts, szt)\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"<σz(t)>\")\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Master Equation in Lindbladian Form" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:49: UserWarning: Complex dtype support is work in progress, please read https://github.com/patrick-kidger/diffrax/pull/197 and proceed carefully.\n", + " out = fun(*args, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "450 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "21.9 ms ± 693 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit -n1 -r1 jqt.mesolve(g_state_dm, ts, c_ops=c_ops, Ht=Ht) \n", + "%timeit jqt.mesolve(g_state_dm, ts, c_ops=c_ops, Ht=Ht) " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "states = jqt.mesolve(g_state_dm, ts, c_ops=c_ops, Ht=Ht) \n", + "szt = jnp.real(jqt.calc_expect(sz0, states))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = [[2, 2], [2, 2]], shape = (4, 4), type = oper\n", + "Qarray data =\n", + "[[-1.08592737e-04+1.56340956e-17j -1.74892334e-03+2.47742701e-05j\n", + " -7.18368060e-05+3.20902574e-06j 3.70550669e-04+2.43359258e-04j]\n", + " [-1.74892334e-03-2.47742701e-05j 3.42869815e-01+1.91647544e-17j\n", + " 2.38985586e-04+3.20485815e-04j -4.56959388e-03-6.42805453e-02j]\n", + " [-7.18368060e-05-3.20902574e-06j 2.38985586e-04-3.20485815e-04j\n", + " 2.29187706e-04-3.69939024e-17j -3.16393333e-03+2.90917200e-05j]\n", + " [ 3.70550669e-04-2.43359258e-04j -4.56959388e-03+6.42805453e-02j\n", + " -3.16393333e-03-2.90917200e-05j 6.57009590e-01+2.09416692e-16j]]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_state = jqt.jnp2jqt(states[-1], dims=g_state_dm.dims)\n", + "final_state" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwwAAAJECAYAAAC7A6POAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAB7CAAAewgFu0HU+AAC6lElEQVR4nOzdd3iUZdY/8O+UJJPeSCEFEkhC6KGEDgFUVKrY4LWBgr3hjxVX310XfdcGuoquuwuCAgqCDUQBQTQghBo6hDQgpDfSeyaZ3x/ZTPI8k0mdmWfK93NdXDtz55lnDrsLzJn7nHPLNBqNBkRERERERG2QSx0AERERERGZLyYMRERERESkFxMGIiIiIiLSiwkDERERERHpxYSBiIiIiIj0YsJARERERER6MWEgIiIiIiK9mDAQEREREZFeTBiIiIiIiEgvJgxERERERKQXEwYiIiIiItKLCQMREREREenFhIGIiIiIiPRiwkBERERERHoxYSAiIiIiIr2YMBARERERkV5MGIiIiIiISC+l1AEQAUBNTQ0uXrwIAPDx8YFSyf9rEhEREXWFWq1GQUEBAGDo0KFQqVQGuS8/lZFZuHjxIsaMGSN1GERERERW4eTJk4iOjjbIvViSREREREREenGHgcyCj4+P9vHJkyfRu3dvCaMhIiIisjw5OTnaio3Wn616igkDmYXWPQu9e/dGUFCQhNEQERERWTZD9oOyJImIiIiIiPRiwkBERERERHoxYSAiIiIiIr2YMBARERERkV5MGIiIiIiISC8mDEREREREpBcTBiIiIiIi0osJAxERERER6cWEwQjy8/Px888/4/XXX8edd96JXr16QSaTQSaTYfHixUZ5z6+//hozZsyAv78/VCoV+vbti4ceegjHjh3r9D2qqqqwatUqREdHw8vLC87OzoiMjMTy5ctx48YNo8RNREREROaNJz0bgZ+fn8neq7q6Gvfeey/27NkjWE9PT8eWLVvw9ddf4/XXX8ff/va3du+TmpqKmTNnIiUlRbCelJSEpKQkrF+/Hlu2bMHs2bMN/nsgIiIiIvPFHQYj69OnD2bMmGG0+z/22GPaZGHatGnYuXMnTp48iQ0bNqB///5obGzEypUrsW7dOr33KC8vx6xZs7TJwuOPP47ffvsNR48exVtvvQUXFxeUlZVhwYIFOHfunNF+L0RERERkfrjD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yMM/d5zGtTuaD0u/OdAX3b2cDBUyERERqVEIUk/lEjWg7OxseHl5Aah8P4Wnp6eRIyK5/e++8/jmoObzMlbmZlg5sTsGdnaFtcV/3xmS96AES37LwK6T16vV093LCVtfC4aFOR/BIiIiUmeo4yleYSCiBjFvsD8KS8qx7sh/3/pcWl6B2T8dh6W5Ap3dHPGkZ0u4OtggMuES7j9UVaujpa0lvp7QnckCERFRA2LCQEQNQqFQ4KORnaEsVWFTyjWNeWXlAk5l38Op7HuSy/fq0BrLXwhA+zZ86J6IiKghMWEgogajUCiwLCwAytJyrbcbaeNoY4EPR3TGuKe8YGbGN44TERE1NCYMRNSgzM0U+HJ8Nzzp2RI/H/sbl/KLJMuO7uaOj0Z24YvZiIiIjIgJAxE1OEtzM8zs2xEz+3bEveIy/JVzDyezC3Dq2j2cu3kfreytMDfED/0f1+99IkRERGQ4TBiIyKha2lqij28b9PFtY+xQiIiISAsONUJERERERJKYMBARERERkSQmDEREREREJIkJAxERERERSWLCQEREREREkpgwEBERERGRJCYMREREREQkiQkDERERERFJYsJARERERESSmDAQEREREZEkJgxERERERCSJCQMREREREUliwkBERERERJKYMBARERERkSQmDEREREREJMnC2AEQAYBKpRKnb9y4YcRIiIiIiJom9WMo9WOr+mLCQI1CXl6eON2rVy8jRkJERETU9OXl5aF9+/ay1MVbkoiIiIiISJJCEATB2EEQPXz4EKdPnwYAuLi4wMLCcBe/bty4IV7FSE5Ohpubm8HWZUrYrobBdjUctq1hsF0Ng+1qGM2tXVUqlXjXRkBAAGxsbGSpl7ckUaNgY2ODoKCgBl+vm5sbPD09G3y9zR3b1TDYrobDtjUMtqthsF0No7m0q1y3IanjLUlERERERCSJCQMREREREUliwkBERERERJKYMBARERERkSQmDEREREREJIkJAxERERERSWLCQEREREREkvjiNiIiIiIiksQrDEREREREJIkJAxERERERSWLCQEREREREkpgwEBERERGRJCYMREREREQkiQkDERERERFJYsJARERERESSmDAQEREREZEkJgxERERERCSJCQMREREREUliwkAm5erVq3j33XfRqVMn2Nvbo3Xr1ggKCsK///1vKJVKY4fX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kYG8vDzcuXMH1tbWaNu2LQIDA/Hiiy8iLCwM5ubmxg7X4OTe1319fZGeno5Vq1Zhy5YtyMrKQmlpKby8vBAaGoq5c+fC29u7AbbM8BSCIPGkBhERERERmTyOkkRERERERJKYMBARERERkSQmDEREREREJIkJAxERERERSWLCQEREREREkpgwEBERERGRJCYMREREREQkiQkDERERERFJYsJARERERESSmDAQEREREZEkJgxERERERCSJCQMREREREUliwkBERERERJKYMBARERERkSQmDEREREREJIkJAxERERERSWLCQEREREREkpgwEBFRg1u/fj0UCgUUCgWuXLli7HAa3Pnz52FlZQUbGxvk5OQYOxwNSqUSrq6uUCgUOHTokLHDIaJGgAkDERHp7cqVK+KBfn0+pu6dd95BWVkZZsyYAQ8PD2OHo8HOzg7vvPMOAODtt9+GIAhGjoiIjI0JAxERUQNKTEzE77//DisrK7z33nvGDker119/Ha1bt8bJkyexZcsWY4dDREamEHjqgIiI9FRWVobz589Lzg8ICAAABAYGIjo6WrLcE088IXtsTUVoaCj27NmDl19+GT/++KOxw5H0/vvvY/ny5QgICMCpU6eMHQ4RGRETBiIikk3V7Ub9+vXj/e9anD9/Hp07d4YgCNizZw+GDRtm7JAknT59Gk8++SQAIC4uDv379zduQERkNLwliYiIqIFER0dDEAS4urpi0KBBxg5Hp4CAAPGKUVRUlJGjISJjYsJAREQNrqZRkvr37w+FQiGe1c7KysJrr72Gjh07wtbWFu3bt8eMGTNw9epVjeX++usvTJ8+HR07doSNjQ28vLwwe/Zs3Lp1S6+4duzYgXHjxqFdu3awsbGBk5MTAgMD8emnn+Lu3bv13Wxs3rwZADBmzBhYWFhIlqtqm08++QQAkJKSgpdeegmenp6wtraGh4cHJk+ejLNnz+pcX0FBAZYtW4bg4GC0atUKlpaWcHFxQZcuXRAWFobvvvsOubm5ksuPHTsWQGW7PHz4sJZbS0TNhkBERCQTAAIAoV+/fjrLRUdHi2UvX75cbX6/fv3Eevbv3y84ODiI5dU/rq6uwtmzZwVBEISff/5ZsLKy0lrO29tbyMnJkYznzp07wsCBA7Uuq76upKSkOrfNlStXxLqioqJ0lq0q9/HHHwurVq0SLCwstMZk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax = plt.subplots(1, dpi=200, figsize=(4,3))\n", + "ax.plot(ts, szt)\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"<σz(t)>\")\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Auto-Diff" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.39 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "103 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "from jax import grad\n", + "\n", + "@jit\n", + "def test_grad(a):\n", + " states = jqt.mesolve(g_state_dm, ts*a, c_ops=c_ops, Ht=Ht) \n", + " return jnp.real(states[-1][0,0])\n", + "\n", + "%timeit -n1 -r1 grad(test_grad)(1.0)\n", + "%timeit grad(test_grad)(1.0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.8.13 ('bosonic-jax-env')", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" + }, + "vscode": { + "interpreter": { + "hash": "4e7140121d6186a0e0d54242403dd67cbe837e742b641fe55d27758b9ab7b56d" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorials/1-single-qubit-rabi.ipynb b/tutorials/1-single-qubit-rabi.ipynb index 4906561..f0260b2 100644 --- a/tutorials/1-single-qubit-rabi.ipynb +++ b/tutorials/1-single-qubit-rabi.ipynb @@ -212,7 +212,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.12.0" }, "vscode": { "interpreter": { diff --git a/tutorials/2-qarray.ipynb b/tutorials/2-qarray.ipynb index df1ca6e..74cb1ab 100644 --- a/tutorials/2-qarray.ipynb +++ b/tutorials/2-qarray.ipynb @@ -53,6 +53,46 @@ " return d + input_a" ] }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "@jit\n", + "def test(a):\n", + " b = jqt.sigmax()\n", + " return a @ b" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "Unexpected input type for array: ", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[35], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mjax\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m vmap\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mjax\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnumpy\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mjnp\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m a \u001b[38;5;241m=\u001b[39m \u001b[43mjnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43marray\u001b[49m\u001b[43m(\u001b[49m\u001b[43m[\u001b[49m\u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msigmax\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msigmay\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# vmap(test)(a)\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/lax_numpy.py:2164\u001b[0m, in \u001b[0;36marray\u001b[0;34m(object, dtype, copy, order, ndmin)\u001b[0m\n\u001b[1;32m 2162\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\u001b[38;5;28mobject\u001b[39m, (\u001b[38;5;28mlist\u001b[39m, \u001b[38;5;28mtuple\u001b[39m)):\n\u001b[1;32m 2163\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mobject\u001b[39m:\n\u001b[0;32m-> 2164\u001b[0m out \u001b[38;5;241m=\u001b[39m stack([asarray(elt, dtype\u001b[38;5;241m=\u001b[39mdtype) \u001b[38;5;28;01mfor\u001b[39;00m elt \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mobject\u001b[39m])\n\u001b[1;32m 2165\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 2166\u001b[0m out \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray([], dtype\u001b[38;5;241m=\u001b[39mdtype) \u001b[38;5;66;03m# type: ignore[arg-type]\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/lax_numpy.py:2164\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 2162\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\u001b[38;5;28mobject\u001b[39m, (\u001b[38;5;28mlist\u001b[39m, \u001b[38;5;28mtuple\u001b[39m)):\n\u001b[1;32m 2163\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mobject\u001b[39m:\n\u001b[0;32m-> 2164\u001b[0m out \u001b[38;5;241m=\u001b[39m stack([\u001b[43masarray\u001b[49m\u001b[43m(\u001b[49m\u001b[43melt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m elt \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mobject\u001b[39m])\n\u001b[1;32m 2165\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 2166\u001b[0m out \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray([], dtype\u001b[38;5;241m=\u001b[39mdtype) \u001b[38;5;66;03m# type: ignore[arg-type]\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/lax_numpy.py:2217\u001b[0m, in \u001b[0;36masarray\u001b[0;34m(a, dtype, order, copy)\u001b[0m\n\u001b[1;32m 2215\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m dtype \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 2216\u001b[0m dtype \u001b[38;5;241m=\u001b[39m dtypes\u001b[38;5;241m.\u001b[39mcanonicalize_dtype(dtype, allow_extended_dtype\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;66;03m# type: ignore[assignment]\u001b[39;00m\n\u001b[0;32m-> 2217\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43marray\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcopy\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mbool\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mcopy\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43morder\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43morder\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/lax_numpy.py:2170\u001b[0m, in \u001b[0;36marray\u001b[0;34m(object, dtype, copy, order, ndmin)\u001b[0m\n\u001b[1;32m 2168\u001b[0m out \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray(\u001b[38;5;28mmemoryview\u001b[39m(\u001b[38;5;28mobject\u001b[39m), copy\u001b[38;5;241m=\u001b[39mcopy)\n\u001b[1;32m 2169\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 2170\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mUnexpected input type for array: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mtype\u001b[39m(\u001b[38;5;28mobject\u001b[39m)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2172\u001b[0m out_array: Array \u001b[38;5;241m=\u001b[39m lax_internal\u001b[38;5;241m.\u001b[39m_convert_element_type(\n\u001b[1;32m 2173\u001b[0m out, dtype, weak_type\u001b[38;5;241m=\u001b[39mweak_type)\n\u001b[1;32m 2174\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ndmin \u001b[38;5;241m>\u001b[39m ndim(out_array):\n", + "\u001b[0;31mTypeError\u001b[0m: Unexpected input type for array: " + ] + } + ], + "source": [ + "from jax import vmap\n", + "import jax.numpy as jnp\n", + "a = jnp.array([jqt.sigmax(), jqt.sigmay()])\n", + "# vmap(test)(a)" + ] + }, { "cell_type": "code", "execution_count": 10, From 00475badd0b079f219c0168f41e9dffa540b6c80 Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 00:16:48 -0500 Subject: [PATCH 04/10] added some convertors --- jaxquantum/core/conversions.py | 25 +++++++++++++++++++++- tutorials/1-single-qubit-rabi-qarray.ipynb | 15 ++++++------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/jaxquantum/core/conversions.py b/jaxquantum/core/conversions.py index 1ba1877..131ef99 100644 --- a/jaxquantum/core/conversions.py +++ b/jaxquantum/core/conversions.py @@ -55,4 +55,27 @@ def jnp2jqt(arr: Array, dims: Optional[DIMS_TYPE] = None): Returns: QuTiP state. """ - return Qarray.create(arr, dims=dims) \ No newline at end of file + return Qarray.create(arr, dims=dims) + + +def jnps2jqts(arrs: Array, dims: Optional[DIMS_TYPE] = None): + """JAX array -> QuTiP state. + + Args: + jnp_obj: JAX array. + + Returns: + QuTiP state. + """ + return [Qarray.create(arr, dims=dims) for arr in arrs] + +def jqts2jnps(qarrs: Qarray): + """QuTiP state -> JAX array. + + Args: + qt_obj: QuTiP state. + + Returns: + JAX array. + """ + return jnp.array([qarr.data for qarr in qarrs]) \ No newline at end of file diff --git a/tutorials/1-single-qubit-rabi-qarray.ipynb b/tutorials/1-single-qubit-rabi-qarray.ipynb index 5e04e15..5e694f2 100644 --- a/tutorials/1-single-qubit-rabi-qarray.ipynb +++ b/tutorials/1-single-qubit-rabi-qarray.ipynb @@ -82,8 +82,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "457 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "1.39 ms ± 10.7 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" + "444 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "1.39 ms ± 16 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" ] } ], @@ -150,8 +150,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "450 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "21.9 ms ± 693 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + "472 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "21.1 ms ± 504 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -172,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -190,14 +190,13 @@ " -3.16393333e-03-2.90917200e-05j 6.57009590e-01+2.09416692e-16j]]" ] }, - "execution_count": 12, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "final_state = jqt.jnp2jqt(states[-1], dims=g_state_dm.dims)\n", - "final_state" + "jqt.jnps2jqts(states, dims=g_state_dm.dims)[-1]" ] }, { From 21cebd69e0dce8e2256427f9ae72930dd403c0a6 Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 00:27:08 -0500 Subject: [PATCH 05/10] added conversions to solvers --- jaxquantum/core/solvers.py | 20 ++++++--- tutorials/1-single-qubit-rabi-qarray.ipynb | 47 +++++----------------- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/jaxquantum/core/solvers.py b/jaxquantum/core/solvers.py index 92b3f66..511b982 100644 --- a/jaxquantum/core/solvers.py +++ b/jaxquantum/core/solvers.py @@ -19,13 +19,14 @@ ) from jaxquantum.core.qarray import Qarray +from jaxquantum.core.conversions import jnps2jqts, jqts2jnps # ---- @jit -def calc_expect(op: Qarray, states: jnp.ndarray) -> jnp.ndarray: +def calc_expect(op: Qarray, states: List[Qarray]) -> jnp.ndarray: """Calculate expectation value of an operator given a list of states. Args: @@ -37,6 +38,7 @@ def calc_expect(op: Qarray, states: jnp.ndarray) -> jnp.ndarray: """ op = op.data + states = jqts2jnps(states) def calc_expect_ket_single(state: jnp.ndarray): return (jnp.conj(state).T @ op @ state)[0][0] @@ -90,7 +92,7 @@ def mesolve( Returns: list of states """ - + dims = ρ0.dims ρ0 = jnp.asarray(ρ0.data) + 0.0j c_ops = c_ops or [] c_ops = jnp.asarray([c_op.data for c_op in c_ops]) + 0.0j @@ -135,7 +137,7 @@ def f( max_steps=16**5, ) - return sol.ys + return jnps2jqts(sol.ys, dims=dims) @partial( jit, @@ -158,6 +160,9 @@ def sesolve( Returns: list of states """ + + dims = ψ.dims + ψ = jnp.asarray(ψ.data) + 0.0j H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None @@ -195,7 +200,7 @@ def f( args=[H0], ) - return sol.ys + return jnps2jqts(sol.ys, dims=dims) # ---- @@ -241,6 +246,8 @@ def mesolve_iso( list of states """ + dims = ρ0.dims + ρ0 = jnp.asarray(ρ0.data) + 0.0j c_ops = c_ops or [] c_ops = jnp.asarray([c_op.data for c_op in c_ops]) + 0.0j @@ -289,7 +296,7 @@ def f( max_steps=16**5, ) - return vmap(real_to_complex_iso_matrix)(sol.ys) + return jnps2jqts(vmap(real_to_complex_iso_matrix)(sol.ys), dims=dims) @partial( jit, @@ -312,6 +319,7 @@ def sesolve_iso( Returns: list of states """ + dims = ψ.dims ψ = jnp.asarray(ψ.data) + 0.0j H0 = jnp.asarray(H0.data) + 0.0j if H0 is not None else None @@ -354,7 +362,7 @@ def f( args=[H0], ) - return vmap(real_to_complex_iso_vector)(sol.ys) + return jnps2jqts(vmap(real_to_complex_iso_vector)(sol.ys), dims=dims) # ---- diff --git a/tutorials/1-single-qubit-rabi-qarray.ipynb b/tutorials/1-single-qubit-rabi-qarray.ipynb index 5e694f2..415a1bc 100644 --- a/tutorials/1-single-qubit-rabi-qarray.ipynb +++ b/tutorials/1-single-qubit-rabi-qarray.ipynb @@ -82,8 +82,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "444 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "1.39 ms ± 16 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" + "594 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "1.88 ms ± 27.2 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n" ] } ], @@ -135,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -150,8 +150,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "472 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "21.1 ms ± 504 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + "693 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "21.7 ms ± 170 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -162,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -174,35 +174,6 @@ "cell_type": "code", "execution_count": 10, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Quantum array: dims = [[2, 2], [2, 2]], shape = (4, 4), type = oper\n", - "Qarray data =\n", - "[[-1.08592737e-04+1.56340956e-17j -1.74892334e-03+2.47742701e-05j\n", - " -7.18368060e-05+3.20902574e-06j 3.70550669e-04+2.43359258e-04j]\n", - " [-1.74892334e-03-2.47742701e-05j 3.42869815e-01+1.91647544e-17j\n", - " 2.38985586e-04+3.20485815e-04j -4.56959388e-03-6.42805453e-02j]\n", - " [-7.18368060e-05-3.20902574e-06j 2.38985586e-04-3.20485815e-04j\n", - " 2.29187706e-04-3.69939024e-17j -3.16393333e-03+2.90917200e-05j]\n", - " [ 3.70550669e-04-2.43359258e-04j -4.56959388e-03+6.42805453e-02j\n", - " -3.16393333e-03-2.90917200e-05j 6.57009590e-01+2.09416692e-16j]]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "jqt.jnps2jqts(states, dims=g_state_dm.dims)[-1]" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, "outputs": [ { "data": { @@ -232,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -240,7 +211,7 @@ "output_type": "stream", "text": [ "2.39 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "103 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + "102 ms ± 883 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -250,7 +221,7 @@ "@jit\n", "def test_grad(a):\n", " states = jqt.mesolve(g_state_dm, ts*a, c_ops=c_ops, Ht=Ht) \n", - " return jnp.real(states[-1][0,0])\n", + " return jnp.real(states[-1].data[0,0])\n", "\n", "%timeit -n1 -r1 grad(test_grad)(1.0)\n", "%timeit grad(test_grad)(1.0)" From 5a6e7e6996c1a0884be54d8cc351a601888c10e6 Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 10:59:21 -0500 Subject: [PATCH 06/10] added tidying up --- jaxquantum/core/__init__.py | 3 +- jaxquantum/core/qarray.py | 18 +++++ jaxquantum/core/settings.py | 5 ++ tutorials/1-single-qubit-rabi-qarray.ipynb | 92 +++++++++++++++++++--- 4 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 jaxquantum/core/settings.py diff --git a/jaxquantum/core/__init__.py b/jaxquantum/core/__init__.py index 28a73ac..69823a0 100644 --- a/jaxquantum/core/__init__.py +++ b/jaxquantum/core/__init__.py @@ -5,4 +5,5 @@ from .conversions import * from .visualization import * from .solvers import * -from .qarray import * \ No newline at end of file +from .qarray import * +from .settings import SETTINGS \ No newline at end of file diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index fa59c36..9bef24a 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -11,6 +11,8 @@ import jax.numpy as jnp import jax.scipy as jsp +from jaxquantum.core.settings import SETTINGS + config.update("jax_enable_x64", True) DIMS_TYPE = List[List[int]] @@ -130,6 +132,15 @@ def out(self, other): return NotImplemented return out +def tidy_up(data, atol): + data_re = jnp.real(data) + data_im = jnp.imag(data) + data_re_mask = jnp.abs(data_re) > atol + data_im_mask = jnp.abs(data_im) > atol + data_new = data_re * data_re_mask + 1j * data_im * data_im_mask + return data_new + + @struct.dataclass # this allows us to send in and return Qarray from jitted functions class Qarray: _data: Array @@ -151,6 +162,13 @@ def create(cls, data, dims=None): qdims = Qdims(dims) dims = deepcopy(dims) + + # TODO: Constantly tidying up on Qarray creation might be a bit overkill. + # It increases the compilation time, but only very slightly + # increased the runtime of the jit compiled function. + # We could instead use this tidy_up where we think we need it. + data = tidy_up(data, SETTINGS["auto_tidyup_atol"]) + return cls(data, qdims) diff --git a/jaxquantum/core/settings.py b/jaxquantum/core/settings.py new file mode 100644 index 0000000..19722a6 --- /dev/null +++ b/jaxquantum/core/settings.py @@ -0,0 +1,5 @@ +""" Core settings. """ + +SETTINGS = { + "auto_tidyup_atol": 1e-14 +} \ No newline at end of file diff --git a/tutorials/1-single-qubit-rabi-qarray.ipynb b/tutorials/1-single-qubit-rabi-qarray.ipynb index 415a1bc..ef32f73 100644 --- a/tutorials/1-single-qubit-rabi-qarray.ipynb +++ b/tutorials/1-single-qubit-rabi-qarray.ipynb @@ -2,9 +2,18 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -16,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -37,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -65,6 +74,33 @@ "# Schroedinger's Equation" ] }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:49: UserWarning: Complex dtype support is work in progress, please read https://github.com/patrick-kidger/diffrax/pull/197 and proceed carefully.\n", + " out = fun(*args, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.1 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "2.39 ms ± 4.79 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit -n1 -r1 jqt.sesolve(g_state, ts, Ht=Ht) \n", + "%timeit jqt.sesolve(g_state, ts, Ht=Ht) " + ] + }, { "cell_type": "code", "execution_count": 4, @@ -94,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -104,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -133,6 +169,33 @@ "# Master Equation in Lindbladian Form" ] }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:49: UserWarning: Complex dtype support is work in progress, please read https://github.com/patrick-kidger/diffrax/pull/197 and proceed carefully.\n", + " out = fun(*args, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.12 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "23.3 ms ± 2.64 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + ] + } + ], + "source": [ + "%timeit -n1 -r1 jqt.mesolve(g_state_dm, ts, c_ops=c_ops, Ht=Ht) \n", + "%timeit jqt.mesolve(g_state_dm, ts, c_ops=c_ops, Ht=Ht) " + ] + }, { "cell_type": "code", "execution_count": 8, @@ -162,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -172,7 +235,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -203,15 +266,15 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2.39 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", - "102 ms ± 883 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" + "3.5 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "109 ms ± 1.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n" ] } ], @@ -226,6 +289,13 @@ "%timeit -n1 -r1 grad(test_grad)(1.0)\n", "%timeit grad(test_grad)(1.0)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 06906ae4bdb17810da8b06dacc6ff73e170b64a3 Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 18:40:48 -0500 Subject: [PATCH 07/10] typing migration, ptrace fixed --- jaxquantum/core/operations.py | 4 +-- jaxquantum/core/operators.py | 30 ++++++++++------- jaxquantum/core/qarray.py | 62 ++++++++++++++++++++--------------- jaxquantum/core/solvers.py | 45 ++++++++++++------------- jaxquantum/utils/utils.py | 4 --- 5 files changed, 80 insertions(+), 65 deletions(-) diff --git a/jaxquantum/core/operations.py b/jaxquantum/core/operations.py index d7ea9b1..d4597fb 100644 --- a/jaxquantum/core/operations.py +++ b/jaxquantum/core/operations.py @@ -1,6 +1,6 @@ """ Relevant linear algebra operations. """ -from jax import config +from jax import config, Array import jax.numpy as jnp @@ -10,7 +10,7 @@ # Linear Algebra Operations ----------------------------------------------------- -def batch_dag(op: jnp.ndarray) -> jnp.ndarray: +def batch_dag_data(op: Array) -> Array: """Conjugate transpose. Args: diff --git a/jaxquantum/core/operators.py b/jaxquantum/core/operators.py index c5c39b3..a342b2f 100644 --- a/jaxquantum/core/operators.py +++ b/jaxquantum/core/operators.py @@ -11,7 +11,7 @@ -def sigmax() -> jnp.ndarray: +def sigmax() -> Qarray: """σx Returns: @@ -20,7 +20,7 @@ def sigmax() -> jnp.ndarray: return Qarray.create(jnp.array([[0.0, 1.0], [1.0, 0.0]])) -def sigmay() -> jnp.ndarray: +def sigmay() -> Qarray: """σy Returns: @@ -29,7 +29,7 @@ def sigmay() -> jnp.ndarray: return Qarray.create(jnp.array([[0.0, -1.0j], [1.0j, 0.0]])) -def sigmaz() -> jnp.ndarray: +def sigmaz() -> Qarray: """σz Returns: @@ -38,7 +38,15 @@ def sigmaz() -> jnp.ndarray: return Qarray.create(jnp.array([[1.0, 0.0], [0.0, -1.0]])) -def sigmam() -> jnp.ndarray: +def hadamard() -> Qarray: + """H + + Returns: + H: Hadamard gate + """ + return Qarray.create(jnp.array([[1, 1], [1, -1]]) / jnp.sqrt(2)) + +def sigmam() -> Qarray: """σ- Returns: @@ -47,7 +55,7 @@ def sigmam() -> jnp.ndarray: return Qarray.create(jnp.array([[0.0, 0.0], [1.0, 0.0]])) -def sigmap() -> jnp.ndarray: +def sigmap() -> Qarray: """σ+ Returns: @@ -56,7 +64,7 @@ def sigmap() -> jnp.ndarray: return Qarray.create(jnp.array([[0.0, 1.0], [0.0, 0.0]])) -def destroy(N) -> jnp.ndarray: +def destroy(N) -> Qarray: """annihilation operator Args: @@ -68,7 +76,7 @@ def destroy(N) -> jnp.ndarray: return Qarray.create(jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=1)) -def create(N) -> jnp.ndarray: +def create(N) -> Qarray: """creation operator Args: @@ -80,7 +88,7 @@ def create(N) -> jnp.ndarray: return Qarray.create(jnp.diag(jnp.sqrt(jnp.arange(1, N)), k=-1)) -def num(N) -> jnp.ndarray: +def num(N) -> Qarray: """Number operator Args: @@ -92,7 +100,7 @@ def num(N) -> jnp.ndarray: return Qarray.create(jnp.diag(jnp.arange(N))) -def identity(*args, **kwargs) -> jnp.ndarray: +def identity(*args, **kwargs) -> Qarray: """Identity matrix. Returns: @@ -101,7 +109,7 @@ def identity(*args, **kwargs) -> jnp.ndarray: return Qarray.create(jnp.eye(*args, **kwargs)) -def displace(N, α) -> jnp.ndarray: +def displace(N, α) -> Qarray: """Displacement operator Args: @@ -130,7 +138,7 @@ def basis(N, k): return Qarray.create(one_hot(k, N).reshape(N, 1)) -def coherent(N, α) -> jnp.ndarray: +def coherent(N, α) -> Qarray: """Coherent state. Args: diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index 9bef24a..1a56ccb 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -268,16 +268,7 @@ def copy(self): return Qarray.create(deepcopy(self.data), dims=self.dims) def unit(self): - data = self.data - - if self.qtype == Qtypes.oper: - evals, _ = jnp.linalg.eigh(data @ jnp.conj(data).T) - rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) - data = data / rho_norm - elif self.qtype in [Qtypes.ket, Qtypes.bra]: - data = data / jnp.linalg.norm(data) - - return Qarray.create(data, dims=self.dims) + return unit(self) def expm(self): return expm(self) @@ -285,14 +276,38 @@ def expm(self): def tr(self, **kwargs): return tr(self, **kwargs) - def ptrace(self, indx, dims): - return ptrace(self, indx, dims) + def ptrace(self, indx): + return ptrace(self, indx) + + def is_dm(self): + return self.qtype == Qtypes.oper # Qarray operations --------------------------------------------------------------------- -def tensor(*args, **kwargs) -> jnp.ndarray: +def unit(qarr: Qarray) -> Qarray: + """Normalize the quantum array. + + Args: + qarr (Qarray): quantum array + + Returns: + Normalized quantum array + """ + data = qarr.data + + if qarr.qtype == Qtypes.oper: + evals, _ = jnp.linalg.eigh(data @ jnp.conj(data).T) + rho_norm = jnp.sum(jnp.sqrt(jnp.abs(evals))) + data = data / rho_norm + elif qarr.qtype in [Qtypes.ket, Qtypes.bra]: + data = data / jnp.linalg.norm(data) + + return Qarray.create(data, dims=qarr.dims) + + +def tensor(*args, **kwargs) -> Qarray: """Tensor product. Args: @@ -310,7 +325,7 @@ def tensor(*args, **kwargs) -> jnp.ndarray: dims[1] += arg.dims[1] return Qarray.create(data, dims=dims) -def tr(qarr: Qarray, **kwargs) -> jnp.ndarray: +def tr(qarr: Qarray, **kwargs) -> Qarray: """Full trace. Args: @@ -321,7 +336,7 @@ def tr(qarr: Qarray, **kwargs) -> jnp.ndarray: """ return jnp.trace(qarr.data, **kwargs) -def expm(qarr: Qarray, **kwargs) -> jnp.ndarray: +def expm(qarr: Qarray, **kwargs) -> Qarray: """Matrix exponential wrapper. Returns: @@ -331,30 +346,25 @@ def expm(qarr: Qarray, **kwargs) -> jnp.ndarray: dims = deepcopy(qarr.dims) return Qarray.create(data, dims=dims) -def ptrace(qarr: Qarray, indx, dims): +def ptrace(qarr: Qarray, indx) -> Qarray: """Partial Trace. Args: rho: density matrix - indx: index to trace out - dims: list of dimensions of the tensored hilbert spaces + indx: index of quantum object to keep, rest will be partial traced out Returns: partial traced out density matrix TODO: Fix weird tracing errors that arise with reshape - TODO: return Qarray """ qarr = ket2dm(qarr) rho = qarr.data + dims = qarr.dims - Nq = len(dims) - - if isinstance(dims, jnp.ndarray): - dims2 = jnp.concatenate(jnp.array([dims, dims])) - else: - dims2 = dims + dims + Nq = len(dims[0]) + dims2 = jnp.concatenate(jnp.array(dims)) rho = rho.reshape(dims2) @@ -369,7 +379,7 @@ def ptrace(qarr: Qarray, indx, dims): for j in range(Nq - 1): rho = jnp.trace(rho, axis1=2, axis2=3) - return rho + return Qarray.create(rho) # Kets & Density Matrices ----------------------------------------------------- diff --git a/jaxquantum/core/solvers.py b/jaxquantum/core/solvers.py index 4144a32..9c2aa05 100644 --- a/jaxquantum/core/solvers.py +++ b/jaxquantum/core/solvers.py @@ -4,11 +4,10 @@ from typing import Callable, List, Optional from diffrax import diffeqsolve, Dopri5, ODETerm, SaveAt, PIDController -from jax import jit, vmap +from jax import jit, vmap, Array import jax.numpy as jnp from jaxquantum.utils.utils import ( - is_1d, real_to_complex_iso_matrix, real_to_complex_iso_vector, complex_to_real_iso_matrix, @@ -26,7 +25,7 @@ # ---- @jit -def calc_expect(op: Qarray, states: List[Qarray]) -> jnp.ndarray: +def calc_expect(op: Qarray, states: List[Qarray]) -> Array: """Calculate expectation value of an operator given a list of states. Args: @@ -38,24 +37,26 @@ def calc_expect(op: Qarray, states: List[Qarray]) -> jnp.ndarray: """ op = op.data + is_dm = states[0].is_dm() states = jqts2jnps(states) - def calc_expect_ket_single(state: jnp.ndarray): + def calc_expect_ket_single(state: Array): return (jnp.conj(state).T @ op @ state)[0][0] - def calc_expect_dm_single(state: jnp.ndarray): + def calc_expect_dm_single(state: Array): return jnp.trace(op @ state) - if is_1d(states[0]): - return vmap(calc_expect_ket_single)(states) - else: + if is_dm: return vmap(calc_expect_dm_single)(states) + else: + return vmap(calc_expect_ket_single)(states) + # ---- # ---- -def spre(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: +def spre(op: Array) -> Callable[[Array], Array]: """Superoperator generator. Args: @@ -75,7 +76,7 @@ def spre(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: ) def mesolve( ρ0: Qarray, - t_list: jnp.ndarray, + t_list: Array, c_ops: Optional[List[Qarray]] = None, H0: Optional[Qarray] = None, Ht: Optional[Callable[[float], Qarray]] = None, @@ -100,8 +101,8 @@ def mesolve( def f( t: float, - rho: jnp.ndarray, - args: jnp.ndarray, + rho: Array, + args: Array, ): H0_val = args[0] c_ops_val = args[1] @@ -145,7 +146,7 @@ def f( ) def sesolve( ψ: Qarray, - t_list: jnp.ndarray, + t_list: Array, H0: Optional[Qarray] = None, Ht: Optional[Callable[[float], Qarray]] = None, ): @@ -168,8 +169,8 @@ def sesolve( def f( t: float, - ψₜ: jnp.ndarray, - args: jnp.ndarray, + ψₜ: Array, + args: Array, ): H0_val = args[0] @@ -208,7 +209,7 @@ def f( # ---- -def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: +def spre_iso(op: Array) -> Callable[[Array], Array]: """Superoperator generator. Args: @@ -228,7 +229,7 @@ def spre_iso(op: jnp.ndarray) -> Callable[[jnp.ndarray], jnp.ndarray]: ) def mesolve_iso( ρ0: Qarray, - t_list: jnp.ndarray, + t_list: Array, c_ops: Optional[List[Qarray]] = None, H0: Optional[Qarray] = None, Ht: Optional[Callable[[float], Qarray]] = None, @@ -259,8 +260,8 @@ def mesolve_iso( def f( t: float, - rho: jnp.ndarray, - args: jnp.ndarray, + rho: Array, + args: Array, ): H0_val = args[0] c_ops_val = args[1] @@ -304,7 +305,7 @@ def f( ) def sesolve_iso( ψ: Qarray, - t_list: jnp.ndarray, + t_list: Array, H0: Optional[Qarray] = None, Ht: Optional[Callable[[float], Qarray]] = None, ): @@ -329,8 +330,8 @@ def sesolve_iso( def f( t: float, - ψₜ: jnp.ndarray, - args: jnp.ndarray, + ψₜ: Array, + args: Array, ): H0_val = args[0] diff --git a/jaxquantum/utils/utils.py b/jaxquantum/utils/utils.py index 9da056a..edf0ae0 100644 --- a/jaxquantum/utils/utils.py +++ b/jaxquantum/utils/utils.py @@ -49,10 +49,6 @@ def comb(N, k): ) -def is_1d(jax_obj) -> bool: - return len(jax_obj.shape) == 1 or jax_obj.shape[1] == 1 - - @jit def complex_to_real_iso_matrix(A): return jnp.block([[jnp.real(A), -jnp.imag(A)], [jnp.imag(A), jnp.real(A)]]) From 8a83a0f85f5377c10bae1a8d62aa83e91da823fa Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 19:33:11 -0500 Subject: [PATCH 08/10] added cosm sinm --- jaxquantum/core/__init__.py | 1 - jaxquantum/core/operations.py | 32 ------------------------------ jaxquantum/core/qarray.py | 37 ++++++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 jaxquantum/core/operations.py diff --git a/jaxquantum/core/__init__.py b/jaxquantum/core/__init__.py index 69823a0..ed617f5 100644 --- a/jaxquantum/core/__init__.py +++ b/jaxquantum/core/__init__.py @@ -1,7 +1,6 @@ """Quantum Tooling""" from .operators import * -from .operations import * from .conversions import * from .visualization import * from .solvers import * diff --git a/jaxquantum/core/operations.py b/jaxquantum/core/operations.py deleted file mode 100644 index d4597fb..0000000 --- a/jaxquantum/core/operations.py +++ /dev/null @@ -1,32 +0,0 @@ -""" Relevant linear algebra operations. """ - -from jax import config, Array - -import jax.numpy as jnp - - -config.update("jax_enable_x64", True) - - -# Linear Algebra Operations ----------------------------------------------------- - -def batch_dag_data(op: Array) -> Array: - """Conjugate transpose. - - Args: - op: operator - - Returns: - conjugate of op, and transposes last two axes - """ - return jnp.moveaxis( - jnp.conj(op), -1, -2 - ) # transposes last two axes, good for batching - - - - - - - - diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index 1a56ccb..d9f948b 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -273,6 +273,12 @@ def unit(self): def expm(self): return expm(self) + def cosm(self): + return cosm(self) + + def sinm(self): + return sinm(self) + def tr(self, **kwargs): return tr(self, **kwargs) @@ -346,6 +352,16 @@ def expm(qarr: Qarray, **kwargs) -> Qarray: dims = deepcopy(qarr.dims) return Qarray.create(data, dims=dims) + +def cosm(qarr: Qarray) -> Qarray: + return (expm(1j*qarr) + expm(-1j*qarr))/2 + +def sinm(qarr: Qarray) -> Qarray: + return (expm(1j*qarr) - expm(-1j*qarr))/(2j) + + +# More quantum specific ----------------------------------------------------- + def ptrace(qarr: Qarray, indx) -> Qarray: """Partial Trace. @@ -381,8 +397,6 @@ def ptrace(qarr: Qarray, indx) -> Qarray: return Qarray.create(rho) -# Kets & Density Matrices ----------------------------------------------------- - def dag(qarr: Qarray) -> Qarray: """Conjugate transpose. @@ -414,4 +428,21 @@ def ket2dm(qarr: Qarray) -> Qarray: if qarr.qtype == Qtypes.bra: qarr = qarr.dag() - return qarr @ qarr.dag() \ No newline at end of file + return qarr @ qarr.dag() + + +# Data level operations ---- + + +def batch_dag_data(op: Array) -> Array: + """Conjugate transpose. + + Args: + op: operator + + Returns: + conjugate of op, and transposes last two axes + """ + return jnp.moveaxis( + jnp.conj(op), -1, -2 + ) # transposes last two axes, good for batching \ No newline at end of file From a0c06edffcb8807fa5f5f46f136d7fbae13f685e Mon Sep 17 00:00:00 2001 From: Phionx Date: Sun, 3 Mar 2024 23:36:49 -0600 Subject: [PATCH 09/10] added diag only elements --- jaxquantum/core/qarray.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index d9f948b..94c6adc 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -287,6 +287,9 @@ def ptrace(self, indx): def is_dm(self): return self.qtype == Qtypes.oper + + def keep_only_diag_elements(self): + return keep_only_diag_elements(self) @@ -360,6 +363,11 @@ def sinm(qarr: Qarray) -> Qarray: return (expm(1j*qarr) - expm(-1j*qarr))/(2j) +def keep_only_diag_elements(qarr: Qarray) -> Qarray: + dims = qarr.dims + data = jnp.diag(jnp.diag(qarr.data)) + return Qarray.create(data, dims=dims) + # More quantum specific ----------------------------------------------------- def ptrace(qarr: Qarray, indx) -> Qarray: From aa4b2d91abb7ba403cd88ea9075e52f4245be4a2 Mon Sep 17 00:00:00 2001 From: Phionx Date: Mon, 4 Mar 2024 01:29:02 -0600 Subject: [PATCH 10/10] added dag for data in qarray --- jaxquantum/core/qarray.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index 94c6adc..a0f9dc0 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -453,4 +453,15 @@ def batch_dag_data(op: Array) -> Array: """ return jnp.moveaxis( jnp.conj(op), -1, -2 - ) # transposes last two axes, good for batching \ No newline at end of file + ) # transposes last two axes, good for batching + +def dag_data(op: Array) -> Array: + """Conjugate transpose. + + Args: + op: operator + + Returns: + conjugate of op, and transposes last two axes + """ + return jnp.conj(op.T) \ No newline at end of file