diff --git a/experiments/batch/2-norm.ipynb b/experiments/batch/2-norm.ipynb new file mode 100644 index 00000000..9fd5fefe --- /dev/null +++ b/experiments/batch/2-norm.ipynb @@ -0,0 +1,105 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9eeb9d3f", + "metadata": {}, + "outputs": [], + "source": [ + "import jaxquantum as jqt\n", + "import jax.numpy as jnp " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "1b2f2439", + "metadata": {}, + "outputs": [], + "source": [ + "def test_norm_unit():\n", + "\n", + " norm_test_array = jnp.array([[1.0, 2.0, 3.0],[4.0, 5.0, 6.0]])\n", + " ones_test_array = jnp.ones_like(norm_test_array)\n", + "\n", + " qarr = jqt.coherent(5, jnp.array([[1.0, 2.0, 3.0],[4.0, 5.0, 6.0]]))\n", + " qarr = qarr / norm_test_array\n", + " \n", + " qarr_dm = qarr.to_dm()\n", + "\n", + "\n", + " # NORM TESTS ----\n", + "\n", + " # Case 1: batched dm/operator\n", + " assert jnp.allclose(qarr_dm.norm(), norm_test_array**(-2), rtol=0, atol=1e-6).all()\n", + "\n", + " # Case 2: batched ket\n", + " assert jnp.allclose(qarr.norm(), norm_test_array**(-1), rtol=0, atol=1e-6).all()\n", + "\n", + " # Case 3: single dm/operator\n", + " assert jnp.isclose(qarr_dm[0,1].norm(), (1/norm_test_array[0,1])**2, rtol=0, atol=1e-6)\n", + "\n", + " # Case 4: single ket\n", + " assert jnp.isclose(qarr[0,1].norm(), 1/norm_test_array[0,1], rtol=0, atol=1e-6)\n", + "\n", + " # ----\n", + "\n", + "\n", + " # UNIT TESTS ----\n", + "\n", + " # Case 1: batched dm/operator\n", + " assert jnp.allclose(qarr_dm.unit().norm(), ones_test_array, rtol=0, atol=1e-6).all()\n", + "\n", + " # Case 2: batched ket\n", + " assert jnp.allclose(qarr.unit().norm(), ones_test_array, rtol=0, atol=1e-6).all()\n", + "\n", + " # Case 3: single dm/operator\n", + " assert jnp.isclose(qarr_dm[0,1].unit().norm(), 1.0, rtol=0, atol=1e-6)\n", + "\n", + " # Case 4: single ket\n", + " assert jnp.isclose(qarr[0,1].unit().norm(), 1.0, rtol=0, atol=1e-6)\n", + " # ----" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5bdba403", + "metadata": {}, + "outputs": [], + "source": [ + "test_norm_unit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ebdc1a85", + "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": 5 +} diff --git a/experiments/main/0-examples.ipynb b/experiments/main/0-examples.ipynb index 38ea5fee..f55ab36d 100644 --- a/experiments/main/0-examples.ipynb +++ b/experiments/main/0-examples.ipynb @@ -740,7 +740,7 @@ ], "metadata": { "kernelspec": { - "display_name": "jax-0.2.0-env", + "display_name": "jax-framework", "language": "python", "name": "python3" }, @@ -754,7 +754,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.5" + "version": "3.9.18" } }, "nbformat": 4, diff --git a/experiments/sparse/1-operations.ipynb b/experiments/sparse/1-operations.ipynb new file mode 100644 index 00000000..f94eaf26 --- /dev/null +++ b/experiments/sparse/1-operations.ipynb @@ -0,0 +1,929 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "7bf9569b", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "26438abd", + "metadata": {}, + "outputs": [], + "source": [ + "from jax.experimental import sparse\n", + "import jax.numpy as jnp\n", + "import jax.scipy as jsp\n", + "import jaxquantum as jqt\n", + "import jax" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "da878c57", + "metadata": {}, + "outputs": [], + "source": [ + "a = jqt.Qarray.create(jnp.array([[1, 2], [3, 4]]))\n", + "b = jqt.Qarray.create(jnp.array([[5, 6], [7, 8]]))\n", + "\n", + "# Basic operations\n", + "assert (a + b).is_dense\n", + "assert (a - b).is_dense\n", + "assert (a @ b).is_dense\n", + "assert (a * 2).is_dense\n", + "assert a.dag().is_dense\n", + "\n", + "# Sparse operations should stay sparse for supported operations\n", + "a_sparse = a.to_sparse()\n", + "b_sparse = b.to_sparse()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5c1b333b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = ((2,), (2,)), bdims = (), shape = (2, 2), type = oper, impl = sparse\n", + "Qarray data =\n", + "BCOO(complex128[2, 2], nse=4)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a_sparse.unit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec708f35", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "jax.experimental.sparse.bcoo.BCOO" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = jqt.destroy(100)\n", + "a_sp = a.to_sparse()\n", + "\n", + "import jax\n", + "\n", + "a_sp.data)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "49acdbf4", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "unsupported operand type(s) for 'in': 'str' and 'EnumMeta'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdense\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mQarrayImplType\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/enum.py:397\u001b[0m, in \u001b[0;36mEnumMeta.__contains__\u001b[0;34m(cls, member)\u001b[0m\n\u001b[1;32m 395\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m__contains__\u001b[39m(\u001b[38;5;28mcls\u001b[39m, member):\n\u001b[1;32m 396\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(member, Enum):\n\u001b[0;32m--> 397\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 398\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124munsupported operand type(s) for \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124min\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m: \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m and \u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m%\u001b[39m (\n\u001b[1;32m 399\u001b[0m \u001b[38;5;28mtype\u001b[39m(member)\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__qualname__\u001b[39m, \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__class__\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__qualname__\u001b[39m))\n\u001b[1;32m 400\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(member, \u001b[38;5;28mcls\u001b[39m) \u001b[38;5;129;01mand\u001b[39;00m member\u001b[38;5;241m.\u001b[39m_name_ \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m_member_map_\n", + "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for 'in': 'str' and 'EnumMeta'" + ] + } + ], + "source": [ + "\"dense\" in jqt.QarrayImplType" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50d3892c", + "metadata": {}, + "outputs": [], + "source": [ + "a = jqt.destroy(100).data\n", + "vac = jqt.basis(100, 1).data\n", + "\n", + "a_sp = sparse.BCOO.fromdense(a)\n", + "vac_sp = sparse.BCOO.fromdense(vac)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2ea57dc7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "14.6 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "724 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n", + "19.6 µs ± 3.42 µs per loop (mean ± std. dev. of 7 runs, 100,000 loops each)\n", + "7.08 ms ± 422 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "%timeit -n1 -r1 a @ vac\n", + "%timeit -n1 -r1 a_sp @ vac_sp\n", + "\n", + "%timeit a @ vac\n", + "%timeit a_sp @ vac_sp" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c0b3aa75", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([[0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " ...,\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sparse.sparsify(jnp.kron)(a_sp, a_sp).todense() - jnp.kron(a, a)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "86b6be9e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([[0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " ...,\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],\n", + " [0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j]], dtype=complex128)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sparse.sparsify(jnp.conj)(a_sp.T).todense() - jnp.conj(a.T)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "ec417930", + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "module 'jax.scipy.linalg' has no attribute 'exp'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[26], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m sparse\u001b[38;5;241m.\u001b[39msparsify(\u001b[43mjsp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinalg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexp\u001b[49m)(a_sp)\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'jax.scipy.linalg' has no attribute 'exp'" + ] + } + ], + "source": [ + "sparse.sparsify(jsp.linalg.exp)(a_sp)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "69991f4f", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "jnp.linalg.norm requires ndarray or scalar arguments, got at position 0.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[32], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43msparse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinalg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_svqb\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma_sp\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/linalg.py:333\u001b[0m, in \u001b[0;36m_svqb\u001b[0;34m(X)\u001b[0m\n\u001b[1;32m 308\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Derives a truncated orthonormal basis for `X`.\u001b[39;00m\n\u001b[1;32m 309\u001b[0m \n\u001b[1;32m 310\u001b[0m \u001b[38;5;124;03mSVQB [1] is an accelerator-friendly orthonormalization procedure, which\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 327\u001b[0m \u001b[38;5;124;03m columns possibly zeroed out if `X` is of low rank.\u001b[39;00m\n\u001b[1;32m 328\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 330\u001b[0m \u001b[38;5;66;03m# In [1] diagonal conditioning is explicit, but by normalizing first\u001b[39;00m\n\u001b[1;32m 331\u001b[0m \u001b[38;5;66;03m# we can simplify the formulas a bit, since then diagonal conditioning\u001b[39;00m\n\u001b[1;32m 332\u001b[0m \u001b[38;5;66;03m# becomes a no-op.\u001b[39;00m\n\u001b[0;32m--> 333\u001b[0m norms \u001b[38;5;241m=\u001b[39m \u001b[43mjnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlinalg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnorm\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mord\u001b[39;49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m2\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkeepdims\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 334\u001b[0m X \u001b[38;5;241m/\u001b[39m\u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mwhere(norms \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m1.0\u001b[39m, norms)\n\u001b[1;32m 336\u001b[0m inner \u001b[38;5;241m=\u001b[39m _mm(X\u001b[38;5;241m.\u001b[39mT, X)\n", + " \u001b[0;31m[... skipping hidden 11 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/linalg.py:1088\u001b[0m, in \u001b[0;36mnorm\u001b[0;34m(x, ord, axis, keepdims)\u001b[0m\n\u001b[1;32m 1020\u001b[0m \u001b[38;5;129m@partial\u001b[39m(jit, static_argnames\u001b[38;5;241m=\u001b[39m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mord\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124maxis\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mkeepdims\u001b[39m\u001b[38;5;124m'\u001b[39m))\n\u001b[1;32m 1021\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mnorm\u001b[39m(x: ArrayLike, \u001b[38;5;28mord\u001b[39m: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1022\u001b[0m axis: \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mtuple\u001b[39m[\u001b[38;5;28mint\u001b[39m, \u001b[38;5;241m.\u001b[39m\u001b[38;5;241m.\u001b[39m\u001b[38;5;241m.\u001b[39m] \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1023\u001b[0m keepdims: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Array:\n\u001b[1;32m 1024\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Compute the norm of a matrix or vector.\u001b[39;00m\n\u001b[1;32m 1025\u001b[0m \n\u001b[1;32m 1026\u001b[0m \u001b[38;5;124;03m JAX implementation of :func:`numpy.linalg.norm`.\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1086\u001b[0m \u001b[38;5;124;03m Array([3.7416575, 9.486833 ], dtype=float32)\u001b[39;00m\n\u001b[1;32m 1087\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m-> 1088\u001b[0m \u001b[43mcheck_arraylike\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mjnp.linalg.norm\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1089\u001b[0m x, \u001b[38;5;241m=\u001b[39m promote_dtypes_inexact(jnp\u001b[38;5;241m.\u001b[39masarray(x))\n\u001b[1;32m 1090\u001b[0m x_shape \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mshape(x)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/util.py:335\u001b[0m, in \u001b[0;36mcheck_arraylike\u001b[0;34m(fun_name, emit_warning, stacklevel, *args)\u001b[0m\n\u001b[1;32m 332\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(msg \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m In a future JAX release this will be an error.\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 333\u001b[0m category\u001b[38;5;241m=\u001b[39m\u001b[38;5;167;01mDeprecationWarning\u001b[39;00m, stacklevel\u001b[38;5;241m=\u001b[39mstacklevel)\n\u001b[1;32m 334\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 335\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(msg\u001b[38;5;241m.\u001b[39mformat(fun_name, \u001b[38;5;28mtype\u001b[39m(arg), pos))\n", + "\u001b[0;31mTypeError\u001b[0m: jnp.linalg.norm requires ndarray or scalar arguments, got at position 0." + ] + } + ], + "source": [ + "sparse.linalg._svqb(a_sp)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7e1d7bc3", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "kron requires ndarray or scalar arguments, got at position 0.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mjnp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkron\u001b[49m\u001b[43m(\u001b[49m\u001b[43ma_sp\u001b[49m\u001b[43m,\u001b[49m\u001b[43ma_sp\u001b[49m\u001b[43m)\u001b[49m\n", + " \u001b[0;31m[... skipping hidden 11 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/lax_numpy.py:4795\u001b[0m, in \u001b[0;36mkron\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m 4792\u001b[0m \u001b[38;5;129m@util\u001b[39m\u001b[38;5;241m.\u001b[39mimplements(np\u001b[38;5;241m.\u001b[39mkron)\n\u001b[1;32m 4793\u001b[0m \u001b[38;5;129m@jit\u001b[39m\n\u001b[1;32m 4794\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mkron\u001b[39m(a: ArrayLike, b: ArrayLike) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Array:\n\u001b[0;32m-> 4795\u001b[0m \u001b[43mutil\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcheck_arraylike\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mkron\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4796\u001b[0m a, b \u001b[38;5;241m=\u001b[39m util\u001b[38;5;241m.\u001b[39mpromote_dtypes(a, b)\n\u001b[1;32m 4797\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ndim(a) \u001b[38;5;241m<\u001b[39m ndim(b):\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/numpy/util.py:335\u001b[0m, in \u001b[0;36mcheck_arraylike\u001b[0;34m(fun_name, emit_warning, stacklevel, *args)\u001b[0m\n\u001b[1;32m 332\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(msg \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m In a future JAX release this will be an error.\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 333\u001b[0m category\u001b[38;5;241m=\u001b[39m\u001b[38;5;167;01mDeprecationWarning\u001b[39;00m, stacklevel\u001b[38;5;241m=\u001b[39mstacklevel)\n\u001b[1;32m 334\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 335\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(msg\u001b[38;5;241m.\u001b[39mformat(fun_name, \u001b[38;5;28mtype\u001b[39m(arg), pos))\n", + "\u001b[0;31mTypeError\u001b[0m: kron requires ndarray or scalar arguments, got at position 0." + ] + } + ], + "source": [ + "jnp.kron(a_sp,a_sp)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "bbf9132e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(100, 100)" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a_sp.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "9c2dceeb", + "metadata": {}, + "outputs": [], + "source": [ + "import flax.struct as struct\n", + "import jax.numpy as jnp\n", + "from abc import ABC, abstractmethod\n", + "from typing import Generic, TypeVar, Union\n", + "\n", + "# ------------------------------\n", + "# Abstract Implementation\n", + "# ------------------------------\n", + "class QarrayImpl(ABC):\n", + " @abstractmethod\n", + " def matmul(self, other: \"QarrayImpl\") -> \"QarrayImpl\":\n", + " ...\n", + "\n", + " @abstractmethod\n", + " def to_dense(self):\n", + " ...\n", + "\n", + "\n", + "# ------------------------------\n", + "# Concrete implementations\n", + "# ------------------------------\n", + "class DenseImpl(QarrayImpl):\n", + " def __init__(self, data):\n", + " self.data = jnp.asarray(data)\n", + "\n", + " def matmul(self, other: \"DenseImpl\") -> \"DenseImpl\":\n", + " return DenseImpl(self.data @ other.data)\n", + "\n", + " def to_dense(self):\n", + " return self.data\n", + "\n", + "\n", + "class SparseImpl(QarrayImpl):\n", + " def __init__(self, data):\n", + " self.data = data # e.g., jax.experimental.sparse.BCOO\n", + "\n", + " def matmul(self, other: \"SparseImpl\") -> \"SparseImpl\":\n", + " return SparseImpl(self.data @ other.data)\n", + "\n", + " def to_dense(self):\n", + " return self.data.todense()\n", + "\n", + "\n", + "# ------------------------------\n", + "# Type variable for typing clarity\n", + "# ------------------------------\n", + "ImplT = TypeVar(\"ImplT\", bound=QarrayImpl)\n", + "\n", + "\n", + "# ------------------------------\n", + "# Public Qarray class\n", + "# ------------------------------\n", + "@struct.dataclass\n", + "class Qarray(Generic[ImplT]):\n", + " impl: ImplT\n", + "\n", + " @classmethod\n", + " def from_dense(cls, data) -> \"Qarray[DenseImpl]\":\n", + " return cls(DenseImpl(data))\n", + "\n", + " @classmethod\n", + " def from_sparse(cls, data) -> \"Qarray[SparseImpl]\":\n", + " return cls(SparseImpl(data))\n", + "\n", + " def matmul(self: \"Qarray[ImplT]\", other: \"Qarray[ImplT]\") -> \"Qarray[ImplT]\":\n", + " \"\"\"For now: only allow matching implementations (Dense@Dense, Sparse@Sparse).\"\"\"\n", + " return Qarray(self.impl.matmul(other.impl)) # type: ignore\n", + "\n", + " def to_dense(self):\n", + " return self.impl.to_dense()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "661ae080", + "metadata": {}, + "outputs": [], + "source": [ + "A: Qarray[DenseImpl] = Qarray.from_dense(jnp.eye(2))\n", + "B: Qarray[DenseImpl] = Qarray.from_dense(jnp.array([[0, 1], [1, 0]]))\n", + "\n", + "C = A.matmul(B) # inferred as Qarray[DenseImpl]\n", + "# type(C) # -> Qarray[DenseImpl]\n", + "\n", + "from jax.experimental import sparse\n", + "sp = sparse.BCOO.fromdense(jnp.eye(2))\n", + "\n", + "S: Qarray[SparseImpl] = Qarray.from_sparse(sp)\n", + "T: Qarray[SparseImpl] = Qarray.from_sparse(sp)\n", + "\n", + "U = S.matmul(T) # inferred as Qarray[SparseImpl]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "937194b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from jax import Array\n", + "isinstance(U.impl.data, Array)" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "3128bf1b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "BCOO(float64[2, 2], nse=4)" + ] + }, + "execution_count": 55, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "U.impl.data" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8963aaef", + "metadata": {}, + "outputs": [], + "source": [ + "# filename: diffrax_jax_sparse_schrodinger.py\n", + "# Requirements: jax, diffrax\n", + "# (pip install \"jax[cpu]\" diffrax) -- or the GPU jax builds if you have one.\n", + "\n", + "import jax\n", + "import jax.numpy as jnp\n", + "from jax.experimental import sparse\n", + "import diffrax\n", + "\n", + "# Optional: enable 64-bit if you want double precision\n", + "# from jax.config import config\n", + "# config.update(\"jax_enable_x64\", True)\n", + "\n", + "def make_1d_tight_binding_hamiltonian(n_sites: int, hopping: float = -1.0):\n", + " \"\"\"Return a dense Hamiltonian for convenience; we'll convert to sparse BCOO below.\"\"\"\n", + " H = jnp.zeros((n_sites, n_sites), dtype=jnp.complex128)\n", + " # nearest-neighbor hopping (tridiagonal)\n", + " offsets = jnp.arange(n_sites - 1)\n", + " H = H.at[offsets, offsets + 1].set(hopping)\n", + " H = H.at[offsets + 1, offsets].set(hopping)\n", + " # optionally add on-site potential (zeros here)\n", + " return H\n", + "\n", + "def main():\n", + " n = 200 # number of sites (make larger to benefit from sparsity)\n", + " H_dense = make_1d_tight_binding_hamiltonian(n)\n", + "\n", + " # Convert to sparse BCOO (compresses zeros)\n", + " H_bcoo = sparse.BCOO.fromdense(H_dense) # experimental sparse type in JAX. :contentReference[oaicite:1]{index=1}\n", + " # H_bcoo = H_dense\n", + "\n", + " # Define the vector field for Schrödinger eqn:\n", + " # i dψ/dt = H ψ => dψ/dt = -i H ψ\n", + " def vector_field(t, y, args):\n", + " # y is complex vector shape (n,)\n", + " # Use sparse matvec to compute H @ y -> dense result\n", + " # For BCOO, the @ operator works as matmul-like; alternatively use sparse.matmul(...)\n", + " Hy = H_bcoo @ y # returns a dense vector\n", + " return -1j * Hy # RHS is complex\n", + "\n", + " term = diffrax.ODETerm(vector_field)\n", + "\n", + " # initial state: localized at center\n", + " y0 = jnp.zeros((n,), dtype=jnp.complex128)\n", + " y0 = y0.at[n // 2].set(1.0 + 0.0j)\n", + "\n", + " solver = diffrax.Tsit5() # explicit solver OK for this non-stiff unitary evolution\n", + " saveat = diffrax.SaveAt(t0=True, t1=True) # just save start and end\n", + " # set dt0 to a fixed initial step size to speed up compilation (optional)\n", + " sol = diffrax.diffeqsolve(\n", + " term,\n", + " solver,\n", + " t0=0.0,\n", + " t1=50.0,\n", + " dt0=0.1,\n", + " y0=y0,\n", + " saveat=saveat,\n", + " )\n", + "\n", + " psi_final = sol.ys[-1] # if you saved t0 and t1, ys will be [y(t0), y(t1)] (see docs). :contentReference[oaicite:2]{index=2}\n", + " # check unitarity (norm conservation)\n", + " norm0 = jnp.linalg.norm(y0)\n", + " norm_final = jnp.linalg.norm(psi_final)\n", + " print(\"||psi(t0)|| =\", norm0)\n", + " print(\"||psi(t1)|| =\", norm_final)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9fdea609", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "43b27079", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:55: UserWarning: Complex dtype support in Diffrax is a work in progress and may not yet produce correct results. Consider splitting your computation into real and imaginary parts instead.\n", + " out = fun(*args, **kwargs)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "||psi(t0)|| = 1.0\n", + "||psi(t1)|| = 0.9999996348072795\n" + ] + } + ], + "source": [ + "main()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3c8279a2", + "metadata": {}, + "outputs": [ + { + "ename": "ConcretizationTypeError", + "evalue": "Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function mesolve at /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:109 for jit. This concrete value was not available in Python because it depends on the value of the argument c_ops._data.\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mConcretizationTypeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[3], line 19\u001b[0m\n\u001b[1;32m 16\u001b[0m ts \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m4\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m\u001b[38;5;241m*\u001b[39mjnp\u001b[38;5;241m.\u001b[39mpi\u001b[38;5;241m/\u001b[39momega_a, \u001b[38;5;241m101\u001b[39m) \u001b[38;5;66;03m# Time points\u001b[39;00m\n\u001b[1;32m 18\u001b[0m solver_options \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39mSolverOptions\u001b[38;5;241m.\u001b[39mcreate(progress_meter\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \n\u001b[0;32m---> 19\u001b[0m states \u001b[38;5;241m=\u001b[39m \u001b[43mjit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmesolve\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstatic_argnums\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 20\u001b[0m \u001b[43m \u001b[49m\u001b[43mH0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minitial_state\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mts\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc_ops\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc_ops\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# solve\u001b[39;00m\n\u001b[1;32m 22\u001b[0m n_exp \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mreal(jqt\u001b[38;5;241m.\u001b[39moverlap(n, states)); a_exp \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39moverlap(a, states) \u001b[38;5;66;03m# expectation values\u001b[39;00m\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Plot\u001b[39;00m\n", + " \u001b[0;31m[... skipping hidden 11 frame]\u001b[0m\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:148\u001b[0m, in \u001b[0;36mmesolve\u001b[0;34m(H, rho0, tlist, saveat_tlist, c_ops, solver_options)\u001b[0m\n\u001b[1;32m 145\u001b[0m dims \u001b[38;5;241m=\u001b[39m ρ0\u001b[38;5;241m.\u001b[39mdims\n\u001b[1;32m 146\u001b[0m ρ0 \u001b[38;5;241m=\u001b[39m ρ0\u001b[38;5;241m.\u001b[39mdata\n\u001b[0;32m--> 148\u001b[0m c_ops \u001b[38;5;241m=\u001b[39m \u001b[43msparse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mBCOO\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromdense\u001b[49m\u001b[43m(\u001b[49m\u001b[43mc_ops\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 150\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(H, Qarray):\n\u001b[1;32m 151\u001b[0m Ht_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m t: sparse\u001b[38;5;241m.\u001b[39mBCOO\u001b[38;5;241m.\u001b[39mfromdense(H\u001b[38;5;241m.\u001b[39mdata)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:2514\u001b[0m, in \u001b[0;36mBCOO.fromdense\u001b[0;34m(cls, mat, nse, index_dtype, n_dense, n_batch)\u001b[0m\n\u001b[1;32m 2510\u001b[0m \u001b[38;5;129m@classmethod\u001b[39m\n\u001b[1;32m 2511\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mfromdense\u001b[39m(\u001b[38;5;28mcls\u001b[39m, mat: Array, \u001b[38;5;241m*\u001b[39m, nse: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m, index_dtype: DTypeLike \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mint32,\n\u001b[1;32m 2512\u001b[0m n_dense: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m, n_batch: \u001b[38;5;28mint\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m BCOO:\n\u001b[1;32m 2513\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Create a BCOO array from a (dense) :class:`~jax.Array`.\"\"\"\u001b[39;00m\n\u001b[0;32m-> 2514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mbcoo_fromdense\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2515\u001b[0m \u001b[43m \u001b[49m\u001b[43mmat\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnse\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex_dtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex_dtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_dense\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_dense\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_batch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_batch\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:268\u001b[0m, in \u001b[0;36mbcoo_fromdense\u001b[0;34m(mat, nse, n_batch, n_dense, index_dtype)\u001b[0m\n\u001b[1;32m 266\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m nse_arr \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 267\u001b[0m nse_arr \u001b[38;5;241m=\u001b[39m _count_stored_elements(mat, n_batch, n_dense)\n\u001b[0;32m--> 268\u001b[0m nse_int \u001b[38;5;241m=\u001b[39m \u001b[43mcore\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconcrete_or_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43moperator\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse_arr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_TRACED_NSE_ERROR\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 269\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m BCOO(_bcoo_fromdense(mat, nse\u001b[38;5;241m=\u001b[39mnse_int, n_batch\u001b[38;5;241m=\u001b[39mn_batch, n_dense\u001b[38;5;241m=\u001b[39mn_dense,\n\u001b[1;32m 270\u001b[0m index_dtype\u001b[38;5;241m=\u001b[39mindex_dtype),\n\u001b[1;32m 271\u001b[0m shape\u001b[38;5;241m=\u001b[39mmat\u001b[38;5;241m.\u001b[39mshape, indices_sorted\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, unique_indices\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/core.py:1492\u001b[0m, in \u001b[0;36mconcrete_or_error\u001b[0;34m(force, val, context)\u001b[0m\n\u001b[1;32m 1490\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m force(val\u001b[38;5;241m.\u001b[39maval\u001b[38;5;241m.\u001b[39mval)\n\u001b[1;32m 1491\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1492\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ConcretizationTypeError(val, context)\n\u001b[1;32m 1493\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1494\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m force(val)\n", + "\u001b[0;31mConcretizationTypeError\u001b[0m: Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function mesolve at /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:109 for jit. This concrete value was not available in Python because it depends on the value of the argument c_ops._data.\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError" + ] + } + ], + "source": [ + "from jax import jit\n", + "import jaxquantum as jqt \n", + "import jax.numpy as jnp\n", + "import matplotlib.pyplot as plt\n", + "\n", + "N = 100\n", + "a = jqt.destroy(N); n = a.dag() @ a\n", + "\n", + "omega_a = 2.0*jnp.pi*5.0; H0 = omega_a*n # Hamiltonian\n", + "\n", + "kappa = 2*jnp.pi*jnp.array([1,2]); batched_loss_op = jnp.sqrt(kappa)*a; \n", + "c_ops = jqt.Qarray.from_list([batched_loss_op]) # collapse operators\n", + "\n", + "initial_state = (jqt.displace(N, 0.1) @ jqt.basis(N,0)).to_dm() # initial state\n", + "\n", + "ts = jnp.linspace(0, 4*2*jnp.pi/omega_a, 101) # Time points\n", + "\n", + "solver_options = jqt.SolverOptions.create(progress_meter=True) \n", + "states = jit(jqt.mesolve, static_argnums=(5))(\n", + " H0, initial_state, ts, c_ops=c_ops, solver_options=solver_options) # solve\n", + "\n", + "n_exp = jnp.real(jqt.overlap(n, states)); a_exp = jqt.overlap(a, states) # expectation values\n", + "\n", + "# Plot\n", + "\n", + "fig, axs = plt.subplots(2,1, dpi=200, figsize=(6,5))\n", + "ax = axs[0]\n", + "ax.plot(ts, jnp.real(a_exp)[:,0], label=r\"$Re[\\langle a(t)\\rangle]$\", color=\"blue\") # Batch kappa value 0\n", + "ax.plot(ts, jnp.real(a_exp)[:,1], \"--\", label=r\"$Re[\\langle a(t)\\rangle]$\", color=\"blue\") # Batch kappa value 1\n", + "ax.plot(ts, jnp.imag(a_exp)[:,0], label=r\"$Im[\\langle a(t)\\rangle]$\", color=\"red\") # Batch kappa value 0\n", + "ax.plot(ts, jnp.imag(a_exp)[:,1], \"--\", label=r\"$Im[\\langle a(t)\\rangle]$\", color=\"red\") # Batch kappa value 1\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "\n", + "ax = axs[1]\n", + "ax.plot(ts, n_exp[:,0], label=r\"$Re[\\langle n(t)\\rangle]$\", color=\"green\") # Batch kappa value 0\n", + "ax.plot(ts, n_exp[:,1], \"--\", label=r\"$Re[\\langle n(t)\\rangle]$\", color=\"green\") # Batch kappa value 1\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6cd558b1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "8c5ca877", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Terms are not compatible with solver!", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mConcretizationTypeError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:168\u001b[0m, in \u001b[0;36m_assert_term_compatible.._check\u001b[0;34m(term_cls, term, term_contr_kwargs, yi)\u001b[0m\n\u001b[1;32m 167\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 168\u001b[0m vf_type \u001b[38;5;241m=\u001b[39m \u001b[43meqx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfilter_eval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[43mterm\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43myi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:38\u001b[0m, in \u001b[0;36mfilter_eval_shape\u001b[0;34m(fun, *args, **kwargs)\u001b[0m\n\u001b[1;32m 37\u001b[0m dynamic, static \u001b[38;5;241m=\u001b[39m partition((fun, args, kwargs), _filter)\n\u001b[0;32m---> 38\u001b[0m dynamic_out, static_out \u001b[38;5;241m=\u001b[39m \u001b[43mjax\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[43mft\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpartial\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_fn\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstatic\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdynamic\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m combine(dynamic_out, static_out\u001b[38;5;241m.\u001b[39mvalue)\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/api.py:2816\u001b[0m, in \u001b[0;36meval_shape\u001b[0;34m(fun, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2815\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m: fun \u001b[38;5;241m=\u001b[39m partial(fun)\n\u001b[0;32m-> 2816\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mjit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:478\u001b[0m, in \u001b[0;36m_make_jit_wrapper..eval_shape\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 476\u001b[0m \u001b[38;5;129m@api_boundary\u001b[39m\n\u001b[1;32m 477\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21meval_shape\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m--> 478\u001b[0m _, _, params, _, out_tree, _, _, _ \u001b[38;5;241m=\u001b[39m \u001b[43m_infer_params\u001b[49m\u001b[43m(\u001b[49m\u001b[43mjit_info\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 479\u001b[0m out_s \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01mif\u001b[39;00m is_unspecified(s) \u001b[38;5;28;01melse\u001b[39;00m s \u001b[38;5;28;01mfor\u001b[39;00m s \u001b[38;5;129;01min\u001b[39;00m params[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mout_shardings\u001b[39m\u001b[38;5;124m'\u001b[39m]]\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:598\u001b[0m, in \u001b[0;36m_infer_params\u001b[0;34m(***failed resolving arguments***)\u001b[0m\n\u001b[1;32m 593\u001b[0m in_shardings_flat, in_layouts_flat \u001b[38;5;241m=\u001b[39m _process_in_axis_resources(\n\u001b[1;32m 594\u001b[0m in_shardings_treedef, in_shardings_leaves,\n\u001b[1;32m 595\u001b[0m in_layouts_treedef, in_layouts_leaves,\n\u001b[1;32m 596\u001b[0m in_avals, in_tree, dbg, device_or_backend_set, have_kwargs)\n\u001b[0;32m--> 598\u001b[0m jaxpr, consts, out_shardings_flat, out_layouts_flat, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43m_pjit_jaxpr\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 599\u001b[0m \u001b[43m \u001b[49m\u001b[43mflat_fun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_shardings_treedef\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_shardings_leaves\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 600\u001b[0m \u001b[43m \u001b[49m\u001b[43mout_layouts_treedef\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_layouts_leaves\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdbg\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 601\u001b[0m \u001b[43m \u001b[49m\u001b[43mdevice_or_backend_set\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mHashableFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43mout_tree\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mclosure\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 602\u001b[0m \u001b[43m \u001b[49m\u001b[43mHashableFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43mres_paths\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mclosure\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minline\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 604\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(explicit_args) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlen\u001b[39m(in_shardings_flat) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlen\u001b[39m(in_layouts_flat)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:1206\u001b[0m, in \u001b[0;36m_pjit_jaxpr\u001b[0;34m(fun, out_shardings_treedef, out_shardings_leaves, out_layouts_treedef, out_layouts_leaves, in_type, debug_info, device_or_backend_set, out_tree, result_paths, inline)\u001b[0m\n\u001b[1;32m 1203\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_pjit_jaxpr\u001b[39m(fun, out_shardings_treedef, out_shardings_leaves,\n\u001b[1;32m 1204\u001b[0m out_layouts_treedef, out_layouts_leaves, in_type, debug_info,\n\u001b[1;32m 1205\u001b[0m device_or_backend_set, out_tree, result_paths, inline):\n\u001b[0;32m-> 1206\u001b[0m jaxpr, final_consts, out_type, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43m_create_pjit_jaxpr\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1207\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresult_paths\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mIgnoreKey\u001b[49m\u001b[43m(\u001b[49m\u001b[43minline\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1208\u001b[0m canonicalized_out_shardings_flat, out_layouts_flat \u001b[38;5;241m=\u001b[39m _check_and_canonicalize_out_shardings(\n\u001b[1;32m 1209\u001b[0m out_shardings_treedef, out_shardings_leaves, out_layouts_treedef,\n\u001b[1;32m 1210\u001b[0m out_layouts_leaves, out_tree, \u001b[38;5;28mtuple\u001b[39m(out_type),\n\u001b[1;32m 1211\u001b[0m jaxpr\u001b[38;5;241m.\u001b[39mjaxpr\u001b[38;5;241m.\u001b[39mdebug_info, device_or_backend_set)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/linear_util.py:350\u001b[0m, in \u001b[0;36mcache..memoized_fun\u001b[0;34m(fun, *args)\u001b[0m\n\u001b[1;32m 349\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 350\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[43mcall\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 351\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m explain \u001b[38;5;129;01mand\u001b[39;00m config\u001b[38;5;241m.\u001b[39mexplain_cache_misses\u001b[38;5;241m.\u001b[39mvalue:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:1154\u001b[0m, in \u001b[0;36m_create_pjit_jaxpr\u001b[0;34m(***failed resolving arguments***)\u001b[0m\n\u001b[1;32m 1153\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1154\u001b[0m jaxpr, global_out_avals, consts, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43mpe\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrace_to_jaxpr_dynamic\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1155\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpe_debug\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1157\u001b[0m \u001b[38;5;66;03m# TODO(dougalm,mattjj): enable debug info with attrs_tracked\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/profiler.py:335\u001b[0m, in \u001b[0;36mannotate_function..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 334\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m TraceAnnotation(name, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mdecorator_kwargs):\n\u001b[0;32m--> 335\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 336\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m wrapper\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/interpreters/partial_eval.py:2326\u001b[0m, in \u001b[0;36mtrace_to_jaxpr_dynamic\u001b[0;34m(fun, in_avals, debug_info, keep_inputs)\u001b[0m\n\u001b[1;32m 2325\u001b[0m main\u001b[38;5;241m.\u001b[39mjaxpr_stack \u001b[38;5;241m=\u001b[39m () \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[0;32m-> 2326\u001b[0m jaxpr, out_avals, consts, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43mtrace_to_subjaxpr_dynamic\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2327\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmain\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_avals\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkeep_inputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mkeep_inputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdebug_info\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2328\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m main, fun\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/interpreters/partial_eval.py:2348\u001b[0m, in \u001b[0;36mtrace_to_subjaxpr_dynamic\u001b[0;34m(fun, main, in_avals, keep_inputs, debug_info)\u001b[0m\n\u001b[1;32m 2347\u001b[0m in_tracers_ \u001b[38;5;241m=\u001b[39m [t \u001b[38;5;28;01mfor\u001b[39;00m t, keep \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(in_tracers, keep_inputs) \u001b[38;5;28;01mif\u001b[39;00m keep]\n\u001b[0;32m-> 2348\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[43mfun\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcall_wrapped\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43min_tracers_\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2349\u001b[0m out_tracers \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmap\u001b[39m(trace\u001b[38;5;241m.\u001b[39mfull_raise, ans)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/linear_util.py:192\u001b[0m, in \u001b[0;36mWrappedFun.call_wrapped\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 192\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mdict\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparams\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[1;32m 194\u001b[0m \u001b[38;5;66;03m# Some transformations yield from inside context managers, so we have to\u001b[39;00m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;66;03m# interrupt them before reraising the exception. Otherwise they will only\u001b[39;00m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# get garbage-collected at some later time, running their cleanup tasks\u001b[39;00m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;66;03m# only after this exception is handled, which can corrupt the global\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;66;03m# state.\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:33\u001b[0m, in \u001b[0;36mfilter_eval_shape.._fn\u001b[0;34m(_static, _dynamic)\u001b[0m\n\u001b[1;32m 32\u001b[0m _fun, _args, _kwargs \u001b[38;5;241m=\u001b[39m combine(_static, _dynamic)\n\u001b[0;32m---> 33\u001b[0m _out \u001b[38;5;241m=\u001b[39m \u001b[43m_fun\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 34\u001b[0m _dynamic_out, _static_out \u001b[38;5;241m=\u001b[39m partition(_out, _filter)\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_term.py:194\u001b[0m, in \u001b[0;36mODETerm.vf\u001b[0;34m(self, t, y, args)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mvf\u001b[39m(\u001b[38;5;28mself\u001b[39m, t: RealScalarLike, y: Y, args: Args) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m _VF:\n\u001b[0;32m--> 194\u001b[0m out \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvector_field\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m jtu\u001b[38;5;241m.\u001b[39mtree_structure(out) \u001b[38;5;241m!=\u001b[39m jtu\u001b[38;5;241m.\u001b[39mtree_structure(y):\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:208\u001b[0m, in \u001b[0;36m_mesolve_data..f\u001b[0;34m(t, rho, c_ops_val)\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mf\u001b[39m(\n\u001b[1;32m 204\u001b[0m t: \u001b[38;5;28mfloat\u001b[39m,\n\u001b[1;32m 205\u001b[0m rho: Array,\n\u001b[1;32m 206\u001b[0m c_ops_val: Array,\n\u001b[1;32m 207\u001b[0m ):\n\u001b[0;32m--> 208\u001b[0m H_val \u001b[38;5;241m=\u001b[39m \u001b[43mH\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 210\u001b[0m rho_dot \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39mj \u001b[38;5;241m*\u001b[39m (H_val \u001b[38;5;241m@\u001b[39m rho \u001b[38;5;241m-\u001b[39m rho \u001b[38;5;241m@\u001b[39m H_val)\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151\u001b[0m, in \u001b[0;36mmesolve..\u001b[0;34m(t)\u001b[0m\n\u001b[1;32m 150\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(H, Qarray):\n\u001b[0;32m--> 151\u001b[0m Ht_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m t: \u001b[43msparse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mBCOO\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromdense\u001b[49m\u001b[43m(\u001b[49m\u001b[43mH\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 152\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:2514\u001b[0m, in \u001b[0;36mBCOO.fromdense\u001b[0;34m(cls, mat, nse, index_dtype, n_dense, n_batch)\u001b[0m\n\u001b[1;32m 2513\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Create a BCOO array from a (dense) :class:`~jax.Array`.\"\"\"\u001b[39;00m\n\u001b[0;32m-> 2514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mbcoo_fromdense\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2515\u001b[0m \u001b[43m \u001b[49m\u001b[43mmat\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnse\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex_dtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex_dtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_dense\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_dense\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_batch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_batch\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:268\u001b[0m, in \u001b[0;36mbcoo_fromdense\u001b[0;34m(mat, nse, n_batch, n_dense, index_dtype)\u001b[0m\n\u001b[1;32m 267\u001b[0m nse_arr \u001b[38;5;241m=\u001b[39m _count_stored_elements(mat, n_batch, n_dense)\n\u001b[0;32m--> 268\u001b[0m nse_int \u001b[38;5;241m=\u001b[39m \u001b[43mcore\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconcrete_or_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43moperator\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse_arr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_TRACED_NSE_ERROR\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 269\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m BCOO(_bcoo_fromdense(mat, nse\u001b[38;5;241m=\u001b[39mnse_int, n_batch\u001b[38;5;241m=\u001b[39mn_batch, n_dense\u001b[38;5;241m=\u001b[39mn_dense,\n\u001b[1;32m 270\u001b[0m index_dtype\u001b[38;5;241m=\u001b[39mindex_dtype),\n\u001b[1;32m 271\u001b[0m shape\u001b[38;5;241m=\u001b[39mmat\u001b[38;5;241m.\u001b[39mshape, indices_sorted\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, unique_indices\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/core.py:1492\u001b[0m, in \u001b[0;36mconcrete_or_error\u001b[0;34m(force, val, context)\u001b[0m\n\u001b[1;32m 1491\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1492\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ConcretizationTypeError(val, context)\n\u001b[1;32m 1493\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "\u001b[0;31mConcretizationTypeError\u001b[0m: Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function _fn at /opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:31 for jit. This value became a tracer due to JAX operations on these lines:\n\n operation a\u001b[35m:c128[]\u001b[39m = convert_element_type[new_dtype=complex128 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\n operation a\u001b[35m:bool[100,100]\u001b[39m = ne b c\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\n operation a\u001b[35m:i64[]\u001b[39m = convert_element_type[new_dtype=int64 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:194\u001b[0m, in \u001b[0;36m_assert_term_compatible\u001b[0;34m(y, args, terms, term_structure, contr_kwargs)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m jax\u001b[38;5;241m.\u001b[39mnumpy_dtype_promotion(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstandard\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m--> 194\u001b[0m \u001b[43mjtu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtree_map\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_check\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mterm_structure\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mterms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcontr_kwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# ValueError may also arise from mismatched tree structures\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/tree_util.py:320\u001b[0m, in \u001b[0;36mtree_map\u001b[0;34m(f, tree, is_leaf, *rest)\u001b[0m\n\u001b[1;32m 319\u001b[0m all_leaves \u001b[38;5;241m=\u001b[39m [leaves] \u001b[38;5;241m+\u001b[39m [treedef\u001b[38;5;241m.\u001b[39mflatten_up_to(r) \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m rest]\n\u001b[0;32m--> 320\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtreedef\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43munflatten\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mxs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mxs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mzip\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mall_leaves\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/tree_util.py:320\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 319\u001b[0m all_leaves \u001b[38;5;241m=\u001b[39m [leaves] \u001b[38;5;241m+\u001b[39m [treedef\u001b[38;5;241m.\u001b[39mflatten_up_to(r) \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m rest]\n\u001b[0;32m--> 320\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m treedef\u001b[38;5;241m.\u001b[39munflatten(\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mxs\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m xs \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(\u001b[38;5;241m*\u001b[39mall_leaves))\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:170\u001b[0m, in \u001b[0;36m_assert_term_compatible.._check\u001b[0;34m(term_cls, term, term_contr_kwargs, yi)\u001b[0m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m--> 170\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mError while tracing \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mterm\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.vf: \u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mstr\u001b[39m(e))\n\u001b[1;32m 171\u001b[0m vf_type_compatible \u001b[38;5;241m=\u001b[39m eqx\u001b[38;5;241m.\u001b[39mfilter_eval_shape(\n\u001b[1;32m 172\u001b[0m better_isinstance, vf_type, vf_type_expected\n\u001b[1;32m 173\u001b[0m )\n", + "\u001b[0;31mValueError\u001b[0m: Error while tracing ODETerm(vector_field=).vf: Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function _fn at /opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:31 for jit. This value became a tracer due to JAX operations on these lines:\n\n operation a\u001b[35m:c128[]\u001b[39m = convert_element_type[new_dtype=complex128 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\n operation a\u001b[35m:bool[100,100]\u001b[39m = ne b c\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\n operation a\u001b[35m:i64[]\u001b[39m = convert_element_type[new_dtype=int64 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:151 ()\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[2], line 19\u001b[0m\n\u001b[1;32m 16\u001b[0m ts \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m4\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m\u001b[38;5;241m*\u001b[39mjnp\u001b[38;5;241m.\u001b[39mpi\u001b[38;5;241m/\u001b[39momega_a, \u001b[38;5;241m101\u001b[39m) \u001b[38;5;66;03m# Time points\u001b[39;00m\n\u001b[1;32m 18\u001b[0m solver_options \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39mSolverOptions\u001b[38;5;241m.\u001b[39mcreate(progress_meter\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \n\u001b[0;32m---> 19\u001b[0m states \u001b[38;5;241m=\u001b[39m \u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmesolve\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 20\u001b[0m \u001b[43m \u001b[49m\u001b[43mH0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minitial_state\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mts\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc_ops\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mc_ops\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# solve\u001b[39;00m\n\u001b[1;32m 22\u001b[0m n_exp \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mreal(jqt\u001b[38;5;241m.\u001b[39moverlap(n, states)); a_exp \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39moverlap(a, states) \u001b[38;5;66;03m# expectation values\u001b[39;00m\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Plot\u001b[39;00m\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:155\u001b[0m, in \u001b[0;36mmesolve\u001b[0;34m(H, rho0, tlist, saveat_tlist, c_ops, solver_options)\u001b[0m\n\u001b[1;32m 152\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 153\u001b[0m Ht_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m t: H(t)\u001b[38;5;241m.\u001b[39mdata\n\u001b[0;32m--> 155\u001b[0m ys \u001b[38;5;241m=\u001b[39m \u001b[43m_mesolve_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43mHt_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mρ0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msaveat_tlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc_ops\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 156\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 158\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m jnp2jqt(ys, dims\u001b[38;5;241m=\u001b[39mdims)\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:229\u001b[0m, in \u001b[0;36m_mesolve_data\u001b[0;34m(H, rho0, tlist, saveat_tlist, c_ops, solver_options)\u001b[0m\n\u001b[1;32m 225\u001b[0m rho_dot \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m rho_dot_delta\n\u001b[1;32m 227\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m rho_dot\n\u001b[0;32m--> 229\u001b[0m sol \u001b[38;5;241m=\u001b[39m \u001b[43msolve\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mρ0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msaveat_tlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mc_ops\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 230\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 232\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sol\u001b[38;5;241m.\u001b[39mys\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:90\u001b[0m, in \u001b[0;36msolve\u001b[0;34m(f, ρ0, tlist, saveat_tlist, args, solver_options)\u001b[0m\n\u001b[1;32m 86\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m warnings\u001b[38;5;241m.\u001b[39mcatch_warnings():\n\u001b[1;32m 87\u001b[0m warnings\u001b[38;5;241m.\u001b[39mfilterwarnings(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mignore\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 88\u001b[0m message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mComplex dtype support in Diffrax\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 89\u001b[0m category\u001b[38;5;241m=\u001b[39m\u001b[38;5;167;01mUserWarning\u001b[39;00m) \u001b[38;5;66;03m# NOTE: suppresses complex dtype warning in diffrax\u001b[39;00m\n\u001b[0;32m---> 90\u001b[0m sol \u001b[38;5;241m=\u001b[39m \u001b[43mdiffeqsolve\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 91\u001b[0m \u001b[43m \u001b[49m\u001b[43mterm\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 92\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 93\u001b[0m \u001b[43m \u001b[49m\u001b[43mt0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 94\u001b[0m \u001b[43m \u001b[49m\u001b[43mt1\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 95\u001b[0m \u001b[43m \u001b[49m\u001b[43mdt0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 96\u001b[0m \u001b[43m \u001b[49m\u001b[43my0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mρ0\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 97\u001b[0m \u001b[43m \u001b[49m\u001b[43msaveat\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msaveat\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 98\u001b[0m \u001b[43m \u001b[49m\u001b[43mstepsize_controller\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstepsize_controller\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 99\u001b[0m \u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 100\u001b[0m \u001b[43m \u001b[49m\u001b[43mmax_steps\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmax_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 101\u001b[0m \u001b[43m \u001b[49m\u001b[43mprogress_meter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mCustomProgressMeter\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 102\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mprogress_meter\u001b[49m\n\u001b[1;32m 103\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mNoProgressMeter\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 104\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 106\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sol\n", + " \u001b[0;31m[... skipping hidden 15 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:1089\u001b[0m, in \u001b[0;36mdiffeqsolve\u001b[0;34m(terms, solver, t0, t1, dt0, y0, args, saveat, stepsize_controller, adjoint, event, max_steps, throw, progress_meter, solver_state, controller_state, made_jump, discrete_terminating_event)\u001b[0m\n\u001b[1;32m 1086\u001b[0m terms \u001b[38;5;241m=\u001b[39m MultiTerm(\u001b[38;5;241m*\u001b[39mterms)\n\u001b[1;32m 1088\u001b[0m \u001b[38;5;66;03m# Error checking for term compatibility\u001b[39;00m\n\u001b[0;32m-> 1089\u001b[0m \u001b[43m_assert_term_compatible\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1090\u001b[0m \u001b[43m \u001b[49m\u001b[43my0\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1091\u001b[0m \u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1092\u001b[0m \u001b[43m \u001b[49m\u001b[43mterms\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1093\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mterm_structure\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1094\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mterm_compatible_contr_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1095\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1097\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_sde(terms):\n\u001b[1;32m 1098\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(solver, (AbstractItoSolver, AbstractStratonovichSolver)):\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:197\u001b[0m, in \u001b[0;36m_assert_term_compatible\u001b[0;34m(y, args, terms, term_structure, contr_kwargs)\u001b[0m\n\u001b[1;32m 194\u001b[0m jtu\u001b[38;5;241m.\u001b[39mtree_map(_check, term_structure, terms, contr_kwargs, y)\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# ValueError may also arise from mismatched tree structures\u001b[39;00m\n\u001b[0;32m--> 197\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTerms are not compatible with solver!\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n", + "\u001b[0;31mValueError\u001b[0m: Terms are not compatible with solver!" + ] + } + ], + "source": [ + "from jax import jit\n", + "import jaxquantum as jqt \n", + "import jax.numpy as jnp\n", + "import matplotlib.pyplot as plt\n", + "\n", + "N = 100\n", + "a = jqt.destroy(N); n = a.dag() @ a\n", + "\n", + "omega_a = 2.0*jnp.pi*5.0; H0 = omega_a*n # Hamiltonian\n", + "\n", + "kappa = 2*jnp.pi*1.0; batched_loss_op = jnp.sqrt(kappa)*a; \n", + "c_ops = jqt.Qarray.from_list([batched_loss_op]) # collapse operators\n", + "\n", + "initial_state = (jqt.displace(N, 0.1) @ jqt.basis(N,0)).to_dm() # initial state\n", + "\n", + "ts = jnp.linspace(0, 4*2*jnp.pi/omega_a, 101) # Time points\n", + "\n", + "solver_options = jqt.SolverOptions.create(progress_meter=True) \n", + "states = jqt.mesolve(\n", + " H0, initial_state, ts, c_ops=c_ops, solver_options=solver_options) # solve\n", + "\n", + "n_exp = jnp.real(jqt.overlap(n, states)); a_exp = jqt.overlap(a, states) # expectation values\n", + "\n", + "# Plot\n", + "\n", + "fig, axs = plt.subplots(2,1, dpi=200, figsize=(6,5))\n", + "ax = axs[0]\n", + "ax.plot(ts, jnp.real(a_exp)[:], label=r\"$Re[\\langle a(t)\\rangle]$\", color=\"blue\") # Batch kappa value 0\n", + "ax.plot(ts, jnp.imag(a_exp)[:], label=r\"$Im[\\langle a(t)\\rangle]$\", color=\"red\") # Batch kappa value 0\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "\n", + "ax = axs[1]\n", + "ax.plot(ts, n_exp[:], label=r\"$Re[\\langle n(t)\\rangle]$\", color=\"green\") # Batch kappa value 0\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "87c4765e", + "metadata": {}, + "outputs": [ + { + "ename": "ValueError", + "evalue": "Terms are not compatible with solver!", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mConcretizationTypeError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:168\u001b[0m, in \u001b[0;36m_assert_term_compatible.._check\u001b[0;34m(term_cls, term, term_contr_kwargs, yi)\u001b[0m\n\u001b[1;32m 167\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 168\u001b[0m vf_type \u001b[38;5;241m=\u001b[39m \u001b[43meqx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfilter_eval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[43mterm\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43myi\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:38\u001b[0m, in \u001b[0;36mfilter_eval_shape\u001b[0;34m(fun, *args, **kwargs)\u001b[0m\n\u001b[1;32m 37\u001b[0m dynamic, static \u001b[38;5;241m=\u001b[39m partition((fun, args, kwargs), _filter)\n\u001b[0;32m---> 38\u001b[0m dynamic_out, static_out \u001b[38;5;241m=\u001b[39m \u001b[43mjax\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[43mft\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpartial\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_fn\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstatic\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdynamic\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 39\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m combine(dynamic_out, static_out\u001b[38;5;241m.\u001b[39mvalue)\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/api.py:2816\u001b[0m, in \u001b[0;36meval_shape\u001b[0;34m(fun, *args, **kwargs)\u001b[0m\n\u001b[1;32m 2815\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m: fun \u001b[38;5;241m=\u001b[39m partial(fun)\n\u001b[0;32m-> 2816\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mjit\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43meval_shape\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:478\u001b[0m, in \u001b[0;36m_make_jit_wrapper..eval_shape\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 476\u001b[0m \u001b[38;5;129m@api_boundary\u001b[39m\n\u001b[1;32m 477\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21meval_shape\u001b[39m(\u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[0;32m--> 478\u001b[0m _, _, params, _, out_tree, _, _, _ \u001b[38;5;241m=\u001b[39m \u001b[43m_infer_params\u001b[49m\u001b[43m(\u001b[49m\u001b[43mjit_info\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 479\u001b[0m out_s \u001b[38;5;241m=\u001b[39m [\u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01mif\u001b[39;00m is_unspecified(s) \u001b[38;5;28;01melse\u001b[39;00m s \u001b[38;5;28;01mfor\u001b[39;00m s \u001b[38;5;129;01min\u001b[39;00m params[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mout_shardings\u001b[39m\u001b[38;5;124m'\u001b[39m]]\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:598\u001b[0m, in \u001b[0;36m_infer_params\u001b[0;34m(***failed resolving arguments***)\u001b[0m\n\u001b[1;32m 593\u001b[0m in_shardings_flat, in_layouts_flat \u001b[38;5;241m=\u001b[39m _process_in_axis_resources(\n\u001b[1;32m 594\u001b[0m in_shardings_treedef, in_shardings_leaves,\n\u001b[1;32m 595\u001b[0m in_layouts_treedef, in_layouts_leaves,\n\u001b[1;32m 596\u001b[0m in_avals, in_tree, dbg, device_or_backend_set, have_kwargs)\n\u001b[0;32m--> 598\u001b[0m jaxpr, consts, out_shardings_flat, out_layouts_flat, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43m_pjit_jaxpr\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 599\u001b[0m \u001b[43m \u001b[49m\u001b[43mflat_fun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_shardings_treedef\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_shardings_leaves\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 600\u001b[0m \u001b[43m \u001b[49m\u001b[43mout_layouts_treedef\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mout_layouts_leaves\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdbg\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 601\u001b[0m \u001b[43m \u001b[49m\u001b[43mdevice_or_backend_set\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mHashableFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43mout_tree\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mclosure\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 602\u001b[0m \u001b[43m \u001b[49m\u001b[43mHashableFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43mres_paths\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mclosure\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minline\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 604\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(explicit_args) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlen\u001b[39m(in_shardings_flat) \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mlen\u001b[39m(in_layouts_flat)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:1206\u001b[0m, in \u001b[0;36m_pjit_jaxpr\u001b[0;34m(fun, out_shardings_treedef, out_shardings_leaves, out_layouts_treedef, out_layouts_leaves, in_type, debug_info, device_or_backend_set, out_tree, result_paths, inline)\u001b[0m\n\u001b[1;32m 1203\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_pjit_jaxpr\u001b[39m(fun, out_shardings_treedef, out_shardings_leaves,\n\u001b[1;32m 1204\u001b[0m out_layouts_treedef, out_layouts_leaves, in_type, debug_info,\n\u001b[1;32m 1205\u001b[0m device_or_backend_set, out_tree, result_paths, inline):\n\u001b[0;32m-> 1206\u001b[0m jaxpr, final_consts, out_type, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43m_create_pjit_jaxpr\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1207\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mresult_paths\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mIgnoreKey\u001b[49m\u001b[43m(\u001b[49m\u001b[43minline\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1208\u001b[0m canonicalized_out_shardings_flat, out_layouts_flat \u001b[38;5;241m=\u001b[39m _check_and_canonicalize_out_shardings(\n\u001b[1;32m 1209\u001b[0m out_shardings_treedef, out_shardings_leaves, out_layouts_treedef,\n\u001b[1;32m 1210\u001b[0m out_layouts_leaves, out_tree, \u001b[38;5;28mtuple\u001b[39m(out_type),\n\u001b[1;32m 1211\u001b[0m jaxpr\u001b[38;5;241m.\u001b[39mjaxpr\u001b[38;5;241m.\u001b[39mdebug_info, device_or_backend_set)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/linear_util.py:350\u001b[0m, in \u001b[0;36mcache..memoized_fun\u001b[0;34m(fun, *args)\u001b[0m\n\u001b[1;32m 349\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 350\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[43mcall\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 351\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m explain \u001b[38;5;129;01mand\u001b[39;00m config\u001b[38;5;241m.\u001b[39mexplain_cache_misses\u001b[38;5;241m.\u001b[39mvalue:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/pjit.py:1154\u001b[0m, in \u001b[0;36m_create_pjit_jaxpr\u001b[0;34m(***failed resolving arguments***)\u001b[0m\n\u001b[1;32m 1153\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1154\u001b[0m jaxpr, global_out_avals, consts, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43mpe\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtrace_to_jaxpr_dynamic\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1155\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_type\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpe_debug\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1157\u001b[0m \u001b[38;5;66;03m# TODO(dougalm,mattjj): enable debug info with attrs_tracked\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/profiler.py:335\u001b[0m, in \u001b[0;36mannotate_function..wrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 334\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m TraceAnnotation(name, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mdecorator_kwargs):\n\u001b[0;32m--> 335\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 336\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m wrapper\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/interpreters/partial_eval.py:2326\u001b[0m, in \u001b[0;36mtrace_to_jaxpr_dynamic\u001b[0;34m(fun, in_avals, debug_info, keep_inputs)\u001b[0m\n\u001b[1;32m 2325\u001b[0m main\u001b[38;5;241m.\u001b[39mjaxpr_stack \u001b[38;5;241m=\u001b[39m () \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[0;32m-> 2326\u001b[0m jaxpr, out_avals, consts, attrs_tracked \u001b[38;5;241m=\u001b[39m \u001b[43mtrace_to_subjaxpr_dynamic\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2327\u001b[0m \u001b[43m \u001b[49m\u001b[43mfun\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmain\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43min_avals\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkeep_inputs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mkeep_inputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdebug_info\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdebug_info\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2328\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m main, fun\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/interpreters/partial_eval.py:2348\u001b[0m, in \u001b[0;36mtrace_to_subjaxpr_dynamic\u001b[0;34m(fun, main, in_avals, keep_inputs, debug_info)\u001b[0m\n\u001b[1;32m 2347\u001b[0m in_tracers_ \u001b[38;5;241m=\u001b[39m [t \u001b[38;5;28;01mfor\u001b[39;00m t, keep \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(in_tracers, keep_inputs) \u001b[38;5;28;01mif\u001b[39;00m keep]\n\u001b[0;32m-> 2348\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[43mfun\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcall_wrapped\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43min_tracers_\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2349\u001b[0m out_tracers \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmap\u001b[39m(trace\u001b[38;5;241m.\u001b[39mfull_raise, ans)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/linear_util.py:192\u001b[0m, in \u001b[0;36mWrappedFun.call_wrapped\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 192\u001b[0m ans \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mdict\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparams\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m:\n\u001b[1;32m 194\u001b[0m \u001b[38;5;66;03m# Some transformations yield from inside context managers, so we have to\u001b[39;00m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;66;03m# interrupt them before reraising the exception. Otherwise they will only\u001b[39;00m\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# get garbage-collected at some later time, running their cleanup tasks\u001b[39;00m\n\u001b[1;32m 197\u001b[0m \u001b[38;5;66;03m# only after this exception is handled, which can corrupt the global\u001b[39;00m\n\u001b[1;32m 198\u001b[0m \u001b[38;5;66;03m# state.\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:33\u001b[0m, in \u001b[0;36mfilter_eval_shape.._fn\u001b[0;34m(_static, _dynamic)\u001b[0m\n\u001b[1;32m 32\u001b[0m _fun, _args, _kwargs \u001b[38;5;241m=\u001b[39m combine(_static, _dynamic)\n\u001b[0;32m---> 33\u001b[0m _out \u001b[38;5;241m=\u001b[39m \u001b[43m_fun\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 34\u001b[0m _dynamic_out, _static_out \u001b[38;5;241m=\u001b[39m partition(_out, _filter)\n", + " \u001b[0;31m[... skipping hidden 1 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_term.py:194\u001b[0m, in \u001b[0;36mODETerm.vf\u001b[0;34m(self, t, y, args)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mvf\u001b[39m(\u001b[38;5;28mself\u001b[39m, t: RealScalarLike, y: Y, args: Args) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m _VF:\n\u001b[0;32m--> 194\u001b[0m out \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvector_field\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m jtu\u001b[38;5;241m.\u001b[39mtree_structure(out) \u001b[38;5;241m!=\u001b[39m jtu\u001b[38;5;241m.\u001b[39mtree_structure(y):\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:303\u001b[0m, in \u001b[0;36m_sesolve_data..f\u001b[0;34m(t, ψt, _)\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mf\u001b[39m(t: \u001b[38;5;28mfloat\u001b[39m, ψₜ: Array, _):\n\u001b[0;32m--> 303\u001b[0m H_val \u001b[38;5;241m=\u001b[39m \u001b[43mH\u001b[49m\u001b[43m(\u001b[49m\u001b[43mt\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 305\u001b[0m ψₜ_dot \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39mj \u001b[38;5;241m*\u001b[39m (H_val \u001b[38;5;241m@\u001b[39m ψₜ)\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271\u001b[0m, in \u001b[0;36msesolve..\u001b[0;34m(t)\u001b[0m\n\u001b[1;32m 270\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(H, Qarray):\n\u001b[0;32m--> 271\u001b[0m Ht_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m t: \u001b[43msparse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mBCOO\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfromdense\u001b[49m\u001b[43m(\u001b[49m\u001b[43mH\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:2514\u001b[0m, in \u001b[0;36mBCOO.fromdense\u001b[0;34m(cls, mat, nse, index_dtype, n_dense, n_batch)\u001b[0m\n\u001b[1;32m 2513\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Create a BCOO array from a (dense) :class:`~jax.Array`.\"\"\"\u001b[39;00m\n\u001b[0;32m-> 2514\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mbcoo_fromdense\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2515\u001b[0m \u001b[43m \u001b[49m\u001b[43mmat\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnse\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mindex_dtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mindex_dtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_dense\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_dense\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_batch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mn_batch\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/experimental/sparse/bcoo.py:268\u001b[0m, in \u001b[0;36mbcoo_fromdense\u001b[0;34m(mat, nse, n_batch, n_dense, index_dtype)\u001b[0m\n\u001b[1;32m 267\u001b[0m nse_arr \u001b[38;5;241m=\u001b[39m _count_stored_elements(mat, n_batch, n_dense)\n\u001b[0;32m--> 268\u001b[0m nse_int \u001b[38;5;241m=\u001b[39m \u001b[43mcore\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mconcrete_or_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43moperator\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnse_arr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_TRACED_NSE_ERROR\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 269\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m BCOO(_bcoo_fromdense(mat, nse\u001b[38;5;241m=\u001b[39mnse_int, n_batch\u001b[38;5;241m=\u001b[39mn_batch, n_dense\u001b[38;5;241m=\u001b[39mn_dense,\n\u001b[1;32m 270\u001b[0m index_dtype\u001b[38;5;241m=\u001b[39mindex_dtype),\n\u001b[1;32m 271\u001b[0m shape\u001b[38;5;241m=\u001b[39mmat\u001b[38;5;241m.\u001b[39mshape, indices_sorted\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, unique_indices\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/core.py:1492\u001b[0m, in \u001b[0;36mconcrete_or_error\u001b[0;34m(force, val, context)\u001b[0m\n\u001b[1;32m 1491\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 1492\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ConcretizationTypeError(val, context)\n\u001b[1;32m 1493\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n", + "\u001b[0;31mConcretizationTypeError\u001b[0m: Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function _fn at /opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:31 for jit. This value became a tracer due to JAX operations on these lines:\n\n operation a\u001b[35m:c128[]\u001b[39m = convert_element_type[new_dtype=complex128 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\n operation a\u001b[35m:bool[100,100]\u001b[39m = ne b c\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\n operation a\u001b[35m:i64[]\u001b[39m = convert_element_type[new_dtype=int64 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:194\u001b[0m, in \u001b[0;36m_assert_term_compatible\u001b[0;34m(y, args, terms, term_structure, contr_kwargs)\u001b[0m\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m jax\u001b[38;5;241m.\u001b[39mnumpy_dtype_promotion(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mstandard\u001b[39m\u001b[38;5;124m\"\u001b[39m):\n\u001b[0;32m--> 194\u001b[0m \u001b[43mjtu\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtree_map\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_check\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mterm_structure\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mterms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcontr_kwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43my\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# ValueError may also arise from mismatched tree structures\u001b[39;00m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/tree_util.py:320\u001b[0m, in \u001b[0;36mtree_map\u001b[0;34m(f, tree, is_leaf, *rest)\u001b[0m\n\u001b[1;32m 319\u001b[0m all_leaves \u001b[38;5;241m=\u001b[39m [leaves] \u001b[38;5;241m+\u001b[39m [treedef\u001b[38;5;241m.\u001b[39mflatten_up_to(r) \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m rest]\n\u001b[0;32m--> 320\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mtreedef\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43munflatten\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mxs\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mxs\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mzip\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mall_leaves\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/jax/_src/tree_util.py:320\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 319\u001b[0m all_leaves \u001b[38;5;241m=\u001b[39m [leaves] \u001b[38;5;241m+\u001b[39m [treedef\u001b[38;5;241m.\u001b[39mflatten_up_to(r) \u001b[38;5;28;01mfor\u001b[39;00m r \u001b[38;5;129;01min\u001b[39;00m rest]\n\u001b[0;32m--> 320\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m treedef\u001b[38;5;241m.\u001b[39munflatten(\u001b[43mf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mxs\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m xs \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(\u001b[38;5;241m*\u001b[39mall_leaves))\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:170\u001b[0m, in \u001b[0;36m_assert_term_compatible.._check\u001b[0;34m(term_cls, term, term_contr_kwargs, yi)\u001b[0m\n\u001b[1;32m 169\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m--> 170\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mError while tracing \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mterm\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.vf: \u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m \u001b[38;5;28mstr\u001b[39m(e))\n\u001b[1;32m 171\u001b[0m vf_type_compatible \u001b[38;5;241m=\u001b[39m eqx\u001b[38;5;241m.\u001b[39mfilter_eval_shape(\n\u001b[1;32m 172\u001b[0m better_isinstance, vf_type, vf_type_expected\n\u001b[1;32m 173\u001b[0m )\n", + "\u001b[0;31mValueError\u001b[0m: Error while tracing ODETerm(vector_field=).vf: Abstract tracer value encountered where concrete value is expected: traced array with shape int64[].\n\nThe error arose for the nse argument of bcoo_fromdense. In order for\nBCOO.fromdense() to be used in traced/compiled code, you must pass a concrete\nvalue to the nse (number of stored elements) argument.\n\nThe error occurred while tracing the function _fn at /opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_eval_shape.py:31 for jit. This value became a tracer due to JAX operations on these lines:\n\n operation a\u001b[35m:c128[]\u001b[39m = convert_element_type[new_dtype=complex128 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\n operation a\u001b[35m:bool[100,100]\u001b[39m = ne b c\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\n operation a\u001b[35m:i64[]\u001b[39m = convert_element_type[new_dtype=int64 weak_type=False] b\n from line /Users/phionx/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:271 ()\n\nSee https://jax.readthedocs.io/en/latest/errors.html#jax.errors.ConcretizationTypeError", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 22\u001b[0m\n\u001b[1;32m 19\u001b[0m ts \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m4\u001b[39m\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m2\u001b[39m\u001b[38;5;241m*\u001b[39mjnp\u001b[38;5;241m.\u001b[39mpi\u001b[38;5;241m/\u001b[39momega_a, \u001b[38;5;241m101\u001b[39m) \u001b[38;5;66;03m# Time points\u001b[39;00m\n\u001b[1;32m 21\u001b[0m solver_options \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39mSolverOptions\u001b[38;5;241m.\u001b[39mcreate(progress_meter\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, solver\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDopri5\u001b[39m\u001b[38;5;124m\"\u001b[39m) \n\u001b[0;32m---> 22\u001b[0m states \u001b[38;5;241m=\u001b[39m \u001b[43mjqt\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msesolve\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 23\u001b[0m \u001b[43m \u001b[49m\u001b[43mH0\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minitial_state\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mts\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# solve\u001b[39;00m\n\u001b[1;32m 25\u001b[0m n_exp \u001b[38;5;241m=\u001b[39m jnp\u001b[38;5;241m.\u001b[39mreal(jqt\u001b[38;5;241m.\u001b[39moverlap(n, states)); a_exp \u001b[38;5;241m=\u001b[39m jqt\u001b[38;5;241m.\u001b[39moverlap(a, states) \u001b[38;5;66;03m# expectation values\u001b[39;00m\n\u001b[1;32m 27\u001b[0m \u001b[38;5;66;03m# Plot\u001b[39;00m\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:275\u001b[0m, in \u001b[0;36msesolve\u001b[0;34m(H, rho0, tlist, saveat_tlist, solver_options)\u001b[0m\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 273\u001b[0m Ht_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mlambda\u001b[39;00m t: H(t)\u001b[38;5;241m.\u001b[39mdata\n\u001b[0;32m--> 275\u001b[0m ys \u001b[38;5;241m=\u001b[39m \u001b[43m_sesolve_data\u001b[49m\u001b[43m(\u001b[49m\u001b[43mHt_data\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mψ\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msaveat_tlist\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 276\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 278\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m jnp2jqt(ys, dims\u001b[38;5;241m=\u001b[39mdims)\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:312\u001b[0m, in \u001b[0;36m_sesolve_data\u001b[0;34m(H, rho0, tlist, saveat_tlist, solver_options)\u001b[0m\n\u001b[1;32m 309\u001b[0m ψ_test \u001b[38;5;241m=\u001b[39m f(\u001b[38;5;241m0\u001b[39m, ψ, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 310\u001b[0m \u001b[38;5;66;03m# ψ = jnp.resize(ψ, ψ_test.shape) # ensure correct shape\u001b[39;00m\n\u001b[0;32m--> 312\u001b[0m sol \u001b[38;5;241m=\u001b[39m \u001b[43msolve\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mψ\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msaveat_tlist\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msolver_options\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 313\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sol\u001b[38;5;241m.\u001b[39mys\n", + "File \u001b[0;32m~/Github/qc/EQuS/bosonic/jax/jaxquantum/jaxquantum/core/solvers.py:90\u001b[0m, in \u001b[0;36msolve\u001b[0;34m(f, ρ0, tlist, saveat_tlist, args, solver_options)\u001b[0m\n\u001b[1;32m 86\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m warnings\u001b[38;5;241m.\u001b[39mcatch_warnings():\n\u001b[1;32m 87\u001b[0m warnings\u001b[38;5;241m.\u001b[39mfilterwarnings(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mignore\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 88\u001b[0m message\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mComplex dtype support in Diffrax\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 89\u001b[0m category\u001b[38;5;241m=\u001b[39m\u001b[38;5;167;01mUserWarning\u001b[39;00m) \u001b[38;5;66;03m# NOTE: suppresses complex dtype warning in diffrax\u001b[39;00m\n\u001b[0;32m---> 90\u001b[0m sol \u001b[38;5;241m=\u001b[39m \u001b[43mdiffeqsolve\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 91\u001b[0m \u001b[43m \u001b[49m\u001b[43mterm\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 92\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 93\u001b[0m \u001b[43m \u001b[49m\u001b[43mt0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 94\u001b[0m \u001b[43m \u001b[49m\u001b[43mt1\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 95\u001b[0m \u001b[43m \u001b[49m\u001b[43mdt0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mtlist\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 96\u001b[0m \u001b[43m \u001b[49m\u001b[43my0\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mρ0\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 97\u001b[0m \u001b[43m \u001b[49m\u001b[43msaveat\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msaveat\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 98\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# stepsize_controller=stepsize_controller,\u001b[39;49;00m\n\u001b[1;32m 99\u001b[0m \u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 100\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# max_steps=solver_options.max_steps,\u001b[39;49;00m\n\u001b[1;32m 101\u001b[0m \u001b[43m \u001b[49m\u001b[43mprogress_meter\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mCustomProgressMeter\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 102\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43msolver_options\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mprogress_meter\u001b[49m\n\u001b[1;32m 103\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mNoProgressMeter\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 104\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 106\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m sol\n", + " \u001b[0;31m[... skipping hidden 15 frame]\u001b[0m\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:1089\u001b[0m, in \u001b[0;36mdiffeqsolve\u001b[0;34m(terms, solver, t0, t1, dt0, y0, args, saveat, stepsize_controller, adjoint, event, max_steps, throw, progress_meter, solver_state, controller_state, made_jump, discrete_terminating_event)\u001b[0m\n\u001b[1;32m 1086\u001b[0m terms \u001b[38;5;241m=\u001b[39m MultiTerm(\u001b[38;5;241m*\u001b[39mterms)\n\u001b[1;32m 1088\u001b[0m \u001b[38;5;66;03m# Error checking for term compatibility\u001b[39;00m\n\u001b[0;32m-> 1089\u001b[0m \u001b[43m_assert_term_compatible\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1090\u001b[0m \u001b[43m \u001b[49m\u001b[43my0\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1091\u001b[0m \u001b[43m \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1092\u001b[0m \u001b[43m \u001b[49m\u001b[43mterms\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1093\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mterm_structure\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1094\u001b[0m \u001b[43m \u001b[49m\u001b[43msolver\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mterm_compatible_contr_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1095\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1097\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_sde(terms):\n\u001b[1;32m 1098\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(solver, (AbstractItoSolver, AbstractStratonovichSolver)):\n", + "File \u001b[0;32m/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/diffrax/_integrate.py:197\u001b[0m, in \u001b[0;36m_assert_term_compatible\u001b[0;34m(y, args, terms, term_structure, contr_kwargs)\u001b[0m\n\u001b[1;32m 194\u001b[0m jtu\u001b[38;5;241m.\u001b[39mtree_map(_check, term_structure, terms, contr_kwargs, y)\n\u001b[1;32m 195\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 196\u001b[0m \u001b[38;5;66;03m# ValueError may also arise from mismatched tree structures\u001b[39;00m\n\u001b[0;32m--> 197\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTerms are not compatible with solver!\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01me\u001b[39;00m\n", + "\u001b[0;31mValueError\u001b[0m: Terms are not compatible with solver!" + ] + } + ], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "from jax import jit\n", + "import jaxquantum as jqt \n", + "import jax.numpy as jnp\n", + "import matplotlib.pyplot as plt\n", + "\n", + "N = 100\n", + "a = jqt.destroy(N); n = a.dag() @ a\n", + "\n", + "omega_a = 2.0*jnp.pi*5.0; H0 = omega_a*n # Hamiltonian\n", + "\n", + "kappa = 2*jnp.pi*1.0; batched_loss_op = jnp.sqrt(kappa)*a; \n", + "c_ops = jqt.Qarray.from_list([batched_loss_op]) # collapse operators\n", + "\n", + "initial_state = (jqt.displace(N, 0.1) @ jqt.basis(N,0)) # initial state\n", + "\n", + "ts = jnp.linspace(0, 4*2*jnp.pi/omega_a, 101) # Time points\n", + "\n", + "solver_options = jqt.SolverOptions.create(progress_meter=True, solver=\"Dopri5\") \n", + "states = jqt.sesolve(\n", + " H0, initial_state, ts, solver_options=solver_options) # solve\n", + "\n", + "n_exp = jnp.real(jqt.overlap(n, states)); a_exp = jqt.overlap(a, states) # expectation values\n", + "\n", + "# Plot\n", + "\n", + "fig, axs = plt.subplots(2,1, dpi=200, figsize=(6,5))\n", + "ax = axs[0]\n", + "ax.plot(ts, jnp.real(a_exp)[:], label=r\"$Re[\\langle a(t)\\rangle]$\", color=\"blue\") # Batch kappa value 0\n", + "ax.plot(ts, jnp.imag(a_exp)[:], label=r\"$Im[\\langle a(t)\\rangle]$\", color=\"red\") # Batch kappa value 0\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "\n", + "ax = axs[1]\n", + "ax.plot(ts, n_exp[:], label=r\"$Re[\\langle n(t)\\rangle]$\", color=\"green\") # Batch kappa value 0\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"Expectations\")\n", + "ax.legend()\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3bd67985", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/miniconda3/envs/jax-framework/lib/python3.9/site-packages/equinox/_jit.py:55: UserWarning: Complex dtype support in Diffrax is a work in progress and may not yet produce correct results. Consider splitting your computation into real and imaginary parts instead.\n", + " out = fun(*args, **kwargs)\n" + ] + } + ], + "source": [ + "import jax\n", + "import jax.numpy as jnp\n", + "import jax.experimental.sparse as jsparse\n", + "import diffrax\n", + "\n", + "# Build sparse Hamiltonian (1D Laplacian with Dirichlet BCs)\n", + "def make_hamiltonian(N, L=1.0):\n", + " dx = L / (N+1)\n", + " diag = -2.0 * jnp.ones(N)\n", + " offdiag = jnp.ones(N-1)\n", + " H_dense = (1.0 / (2.0 * dx * dx)) * (\n", + " jnp.diag(diag) + jnp.diag(offdiag, 1) + jnp.diag(offdiag, -1)\n", + " )\n", + " return jsparse.BCOO.fromdense(H_dense)\n", + "\n", + "# RHS of the TDSE: dψ/dt = -i H ψ\n", + "def schrodinger_rhs(t, psi, H):\n", + " return -1j * (H @ psi)\n", + "\n", + "# Parameters\n", + "N = 100\n", + "L = 1.0\n", + "H_sparse = make_hamiltonian(N, L)\n", + "\n", + "# Initial wavefunction: Gaussian packet centered in the box\n", + "x = jnp.linspace(0, L, N+2)[1:-1] # exclude boundaries\n", + "psi0 = jnp.exp(-200 * (x - 0.5*L)**2) + 0j\n", + "\n", + "# Setup ODE solver\n", + "term = diffrax.ODETerm(schrodinger_rhs)\n", + "solver = diffrax.Dopri5()\n", + "\n", + "sol = diffrax.diffeqsolve(\n", + " term,\n", + " solver,\n", + " t0=0.0,\n", + " t1=0.05,\n", + " dt0=1e-4,\n", + " y0=psi0,\n", + " args=H_sparse,\n", + " saveat=diffrax.SaveAt(ts=jnp.linspace(0, 0.05, 20)),\n", + ")\n", + "\n", + "# sol.ys is an array of shape (20, N) with the wavefunction at each time\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57f62dc8", + "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": 5 +} diff --git a/experiments/sparse/2-sparse-tutorial.ipynb b/experiments/sparse/2-sparse-tutorial.ipynb new file mode 100644 index 00000000..125ab1cc --- /dev/null +++ b/experiments/sparse/2-sparse-tutorial.ipynb @@ -0,0 +1,388 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sparse Qarray Tutorial\n", + "\n", + "This tutorial demonstrates the new sparse support in jaxquantum's Qarray implementation. The new implementation uses an architecture based on different implementations (dense and sparse) while maintaining full backward compatibility.\n", + "\n", + "## What is Sparse Support?\n", + "\n", + "Sparse matrices are matrices where most elements are zero. For quantum systems, many operators (like Hamiltonians) have sparse structure, especially for large systems. Sparse representations can offer significant memory and computational advantages when the sparsity is high enough.\n", + "\n", + "## When to Use Sparse vs Dense\n", + "\n", + "- **Use Sparse**: When your matrices have many zeros (sparsity > 70-80%) and you're doing operations like matrix multiplication, addition, or scalar multiplication\n", + "- **Use Dense**: For small matrices, when doing operations like matrix exponentials, or when sparsity is low\n", + "\n", + "The new implementation automatically handles fallback from sparse to dense for unsupported operations.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "import jaxquantum as jqt\n", + "import jax.numpy as jnp\n", + "import jax.experimental.sparse as sparse\n", + "import numpy as np\n", + "import time\n", + "import matplotlib.pyplot as plt\n", + "from jax import jit, grad, vmap\n", + "\n", + "# Set up plotting\n", + "plt.style.use('default')\n", + "plt.rcParams['figure.figsize'] = (10, 6)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Creating Sparse Qarrays\n", + "\n", + "There are several ways to create sparse Qarrays:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dense Qarray:\n", + "Quantum array: dims = ((3,), (3,)), bdims = (), shape = (3, 3), type = oper, impl = dense\n", + "Qarray data =\n", + "[[1.+0.j 0.+0.j 0.+0.j]\n", + " [0.+0.j 2.+0.j 0.+0.j]\n", + " [0.+0.j 0.+0.j 3.+0.j]]\n", + "Is dense: True\n", + "Is sparse: False\n", + "\n", + "Sparse Qarray:\n", + "Quantum array: dims = ((3,), (3,)), bdims = (), shape = (3, 3), type = oper, impl = sparse\n", + "Qarray data =\n", + "BCOO(complex128[3, 3], nse=3)\n", + "Is dense: False\n", + "Is sparse: True\n" + ] + } + ], + "source": [ + "# Method 1: Create dense Qarray and convert to sparse\n", + "dense_data = jnp.array([[1, 0, 0], [0, 2, 0], [0, 0, 3]])\n", + "qarr_dense = jqt.Qarray.create(dense_data)\n", + "qarr_sparse = qarr_dense.to_sparse()\n", + "\n", + "print(\"Dense Qarray:\")\n", + "print(qarr_dense)\n", + "print(f\"Is dense: {qarr_dense.is_dense}\")\n", + "print(f\"Is sparse: {qarr_dense.is_sparse}\")\n", + "\n", + "print(\"\\nSparse Qarray:\")\n", + "print(qarr_sparse)\n", + "print(f\"Is dense: {qarr_sparse.is_dense}\")\n", + "print(f\"Is sparse: {qarr_sparse.is_sparse}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "From sparse data:\n", + "Quantum array: dims = ((3,), (3,)), bdims = (), shape = (3, 3), type = oper, impl = sparse\n", + "Qarray data =\n", + "BCOO(complex128[3, 3], nse=4)\n", + "Is sparse: True\n", + "\n", + "Direct sparse creation:\n", + "Quantum array: dims = ((3,), (3,)), bdims = (), shape = (3, 3), type = oper, impl = dense\n", + "Qarray data =\n", + "[[1.+0.j 0.+0.j 0.+0.j]\n", + " [0.+0.j 2.+0.j 0.+0.j]\n", + " [0.+0.j 0.+0.j 3.+0.j]]\n", + "Is sparse: False\n" + ] + } + ], + "source": [ + "# Method 2: Create sparse Qarray directly\n", + "sparse_data = sparse.BCOO.fromdense(jnp.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]))\n", + "qarr_from_sparse = jqt.Qarray.from_sparse(sparse_data)\n", + "\n", + "print(\"From sparse data:\")\n", + "print(qarr_from_sparse)\n", + "print(f\"Is sparse: {qarr_from_sparse.is_sparse}\")\n", + "\n", + "# Method 3: Create with implementation parameter\n", + "qarr_sparse_direct = jqt.Qarray.create(dense_data, implementation=\"sparse\")\n", + "\n", + "print(\"\\nDirect sparse creation:\")\n", + "print(qarr_sparse_direct)\n", + "print(f\"Is sparse: {qarr_sparse_direct.is_sparse}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = ((2, 2), (2, 2)), bdims = (), shape = (4, 4), type = oper, impl = dense\n", + "Qarray data =\n", + "[[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n", + " [ 0.+0.j -1.+0.j 0.+0.j 0.+0.j]\n", + " [ 0.+0.j 0.+0.j -1.+0.j 0.+0.j]\n", + " [ 0.+0.j 0.+0.j 0.+0.j 1.+0.j]]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jqt.sigmaz().to_sparse() ^ jqt.sigmaz().to_sparse()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = ((2,), (1,)), bdims = (), shape = (2, 1), type = ket, impl = dense\n", + "Qarray data =\n", + "[[1.+0.j]\n", + " [0.+0.j]]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(jqt.sigmaz().to_sparse() @ jqt.basis(2,0)).to_dense()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100% |\u001b[35m██████████\u001b[0m| [00:00<00:00, 306.97%/s]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwwAAAJECAYAAAC7A6POAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAB7CAAAewgFu0HU+AADIjUlEQVR4nOzdd5icVd0+8PuZur33ne01m94TQgkgIAGkKSBFwIL4ogKi+OqrKP5sIFVsNClSBRXpPZSE9LrJZluydbb3Pjvl+f2xZHbPJLMtM3um3J/r4rr2nH1m5o4mu/Od55zvUVRVVUFERERERHQcGtkBiIiIiIjId7FgICIiIiIit1gwEBERERGRWywYiIiIiIjILRYMRERERETkFgsGIiIiIiJyiwUDERERERG5xYKBiIiIiIjcYsFARERERERusWAgIiIiIiK3WDAQEREREZFbLBiIiIiIiMgtFgxEREREROQWCwYiIiIiInKLBQMREREREbnFgoGIiIiIiNxiwUBERERERG7pZAcg/zYyMoLS0lIAQGJiInQ6/pUiIiIiksFms6G9vR0AsHDhQoSEhHjkefnujk5IaWkpVq1aJTsGEREREU2wfft2rFy50iPPxSVJRERERETkFu8w0AlJTEx0fr19+3akpqZKTENEREQUvJqbm50rPya+RztRLBjohEzcs5CamgqTySQxDREREREB8Oi+Ui5JIiIiIiIit1gwEBERERGRWywYiIiIiIjILRYMRERERETkFgsGIiIiIiJyiwUDERERERG5xYKBiIiIiIjcYsFARERERERusWDwgra2Nrz++uu44447cO655yIhIQGKokBRFFx33XVeec3nn38eZ599NlJSUhASEoKsrCxcffXV2LJli1dej4iIiIiCA0969oLk5OQ5e63h4WF8+ctfxptvvinM19fX49lnn8Xzzz+PO+64A7/4xS/mLBMRERERBQ7eYfCyzMxMnH322V57/q9//evOYuH000/HK6+8gu3bt+Pxxx9HXl4eHA4HfvnLX+KRRx7xWgYiIiIiCly8w+AFd9xxB1auXImVK1ciOTkZtbW1yMnJ8fjrfPjhh3jhhRcAABdccAH+85//QKvVAgBWrlyJL33pS1i+fDnq6+vx4x//GF/5ylcQGxvr8RxEREREFLh4h8EL7rzzTpx//vleX5p0zz33AAB0Oh3+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JCRg79692LJlC+Li4pCamgqNRoP4+HjEx8fjvffew44dO5p9QlMXb/0nNUREDTFhICKiTsnT09Pw2dvbu12JQGvVJQymWACuLcaPH4/x48cDAEpKSvDrr7/iiy++wG+//YYLFy5g/vz5SElJMbp/XbyBgYEdEi8RWSe+kkRERJ3SgAEDDJ8PHTpk1nPFxMQAAEpLS9v9KtSd8vT0xPz587F3717MmDEDAHDixAlkZGQ02V6v1xsGVEdHR3dYnERkfZgwEBFRpzRhwgTDOIGPPvoIoiia7Vx1g5ABICkpyWznaa26pw7ArWlpm5Kamory8nIAwLBhwzokLiKyTkwYiIioU3J3d8cf//hHAEBCQgKWLVsGvV5vtH1RURHWrFnTrnMNHToUDg4OAIDExMR2HaO1Tpw4gRMnThjdLooifv31VwC31mIIDg5usl39OCdNmmTKEImok2HCQEREndabb75p+PV81apVGDhwID799FMcOnQIJ06cwL59+/DJJ59g1qxZCAwMxOeff96u8zg4OBjWlNi7d6/J4m/KiRMnMGDAAAwdOhQrVqzA9u3bcezYMRw5cgSbNm3C5MmTsW3bNgDAjBkzJOs21FcXZ79+/RASEmLWmInIunHQMxERdVoODg7Ys2cPHnroIWzZsgUnT540PHVoipubW7vP9cgjj+Cnn35CQkICcnNzERQU1O5jtUZSUlKzrz/dddddWLt2bZPbKisr8eOPPwIA/vCHP5glPiLqPJgwEBFRp+bq6ooffvgBBw8exFdffYX4+HgUFBSgqqoKbm5uCAsLw9ChQzFt2rQ7ejVnypQp8Pf3R15eHjZt2oSXX37ZhN/itgceeADdunXDnj17kJSUhPz8fBQVFUGr1cLHxwcDBw7E/Pnzcf/990vWqajvxx9/REVFBdRqNRYvXmyWOImo8xBEc44CIyIisiErV67ESy+9hMjISKSlpRm9YZfbhAkTsHfvXjz22GPtfg2LiGwHEwYiIiITqaqqQkREBPLz87Fp0ybcf//9cofUyJEjRxAbGwuVSoWMjAyuwUBELbLMnz6IiIiskKOjI5YvXw4A+Nvf/mbWqVzbqy6+Z599lskCEbUKxzAQERGZ0EMPPYSioiLU1taisLAQPXr0kDskg8rKSgwfPhzDhw/HsmXL5A6HiKwEX0kiIiIiIiKj+EoSEREREREZxYSBiIiIiIiMYsJARERERERGMWEgIiIiIiKjmDAQEREREZFRTBiIiIiIiMgoJgxERERERGQUEwYiIiIiIjKKCQMRERERERnFhIGIiIiIiIxiwkBEREREREYxYSAiIiIiIqOYMBARERERkVFMGIiIiIiIyCgmDEREREREZBQTBiIiIiIiMooJAxERERERGcWEgYiIiIiIjGLCQERERERERjFhICIiIiIio/4fF2WEi0PAqdYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "omega_q = 2*jnp.pi*5.0 #GHzz\n", + "Omega = 2*jnp.pi*.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", + "\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\n", + "\n", + "states = jqt.sesolve(Ht, g_state, ts) \n", + "szt = jnp.real(jqt.overlap(states, sz0))\n", + "\n", + "fig, ax = plt.subplots(1, dpi=200, figsize=(4,3))\n", + "ax.plot(ts, szt)\n", + "ax.plot(ts, jnp.cos(Omega*ts), \"--\", label=r\"$\\cos(\\Omega t)$\")\n", + "ax.set_xlabel(\"Time (ns)\")\n", + "ax.set_ylabel(\"<σz(t)>\")\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100% |\u001b[35m██████████\u001b[0m| [00:00<00:00, 15673.78%/s]\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwwAAAJECAYAAAC7A6POAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAB7CAAAewgFu0HU+AACfnklEQVR4nOzdd3hb1f0/8PeVZFneezvOsh1nQsggIYSEFUoYYZQVRlilUKCFUuim0JZvgUIp5dcBJRAos6GUFVICNITsQQLZw4nteO89ZEu6vz9SK75Hkqeke6/u+/U8fh77+Eo6tnSk8znnc86RZFmWQURERERE5IVJ7QoQEREREZF2MWAgIiIiIiKfGDAQEREREZFPDBiIiIiIiMgnBgxEREREROQTAwYiIiIiIvKJAQMREREREfnEgIGIiIiIiHxiwEBERERERD4xYCAiIiIiIp8YMBARERERkU8MGIiIiIiIyCcGDERERERE5BMDBiIiIiIi8okBAxERERER+cSAgYiIiIiIfGLAQEREREREPlnUrgDpW1dXF/bs2QMASElJgcXClxQRERGRGhwOB2prawEAU6dOhc1m88v9sndHI7Jnzx7Mnj1b7WoQERERUR/btm3DrFmz/HJfTEkiIiIiIiKfOMNAI5KSkuL+ftu2bcjIyFCxNkRERETGVVlZ6c786NtHGykGDDQifdcsZGRkIDs7W8XaEBEREREAv64rZUoSERERERH5xICBiIiIiIh8YsBAREREREQ+MWAgIiIiIiKfGDAQEREREZFPDBiIiIiIiMgnBgxEREREROQTAwYiIiIiIvKJAUMA1NTU4KOPPsLDDz+MCy+8EMnJyZAkCZIk4eabbw7IY7755ptYtGgR0tPTYbPZMHr0aNxwww3YvHlzQB6PiIiIiIyBJz0HQFpaWtAeq7OzE9/+9rfx8ccfK8qPHz+O119/HW+++SYefvhh/OpXvwpanYiIiIgodHCGIcBycnKwaNGigN3/rbfe6g4Wzj77bLz33nvYtm0bli9fjvHjx8PlcuGRRx7BCy+8ELA6EBEREVHo4gxDADz88MOYNWsWZs2ahbS0NBQXF2Ps2LF+f5z//ve/eOuttwAAl1xyCf7973/DbDYDAGbNmoVLL70UM2bMwPHjx/HjH/8YV111FRISEvxeDyIiIiIKXZxhCIBHH30UF198ccBTk556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jtTx0Ucf4eqrr4bZbPb6++TkZDz99NPun9955x2v17HtBZ+/nju9YsBAmvbee++5v7/lllu8XmMymXDTTTcBAJqamrB27dpgVI3I0GRZxvvvvw8AKCgowJw5c7xeN2fOHEyYMAEA8P7770OW5aDVkUiPzj77bPf3R48e9fg92552DfTc6RkDBtK03h0HoqKiMGPGDJ/XLViwwP39xo0bA14vIqMrKipy5/T2bX/e9P6+vLwcxcXFga4aka7Z7Xb3995Gs9n2tGug507PGDCQph04cAAAkJubC4vF9xr9goICj9uQdqxcuRKTJk1CZGQkYmJikJeXh2XLlnE2SMf279/v/r5v+/OG7VPbbrnlFmRmZsJqtSI5ORlz5szBL37xC5SXl6tdNUNat26d+/vedNu+2Pa0a6DnTqSntseAgTSrq6sLdXV1AAbeKSAhIcG9005paWnA60ZDs3//fhw4cACdnZ1oa2tDYWEhXn31VZxzzjm4/PLL0dzcrHYVaYjKysrc3w/UPkeNGuX+nu1Te7744gtUVlaip6cH9fX12Lp1Kx577DHk5ubi+eefV7t6huJyufD444+7f7766qs9rmHb06bBPHciPbU9bqtKmtXa2ur+Pjo6esDro6Ki0N7ejra2tkBWi4YgMjISl156Kc4991wUFBQgOjoatbW1WLduHf72t7+hvr4e7733HpYsWYJPP/0UYWFhaleZBmko7bPvtrlsn9oxbtw4XHHFFZg7d667Y3ns2DH861//wjvvvIOuri7ceeedkCQJd9xxh8q1NYZnnnkG27ZtAwBcccUVXlNx2fa0aTDPXS89tj0GDKRZXV1d7u+tVuuA14eHhwMAOjs7A1YnGpry8nLEx8d7lJ9//vm49957ceGFF2LXrl1Yt24d/vrXv+L73/9+8CtJwzKU9tnbNgG2T624/PLLsWzZMkiSpCifNWsWrrnmGnz00Ue44oor0NPTg/vvvx+XXnop0tPTVaqtMaxbtw4/+clPAACpqan461//6vU6tj3tGexzB+i37TEliTTLZrO5v+/u7h7w+t7FRuL2cqQeb8FCr7S0NLzzzjvuWYXeLQBJH4bSPvsuBGT71Ia4uDiPDktfF198MR5++GEAQEdHh/uAMAqMffv24fLLL4fD4YDNZsPKlSuRmprq9Vq2PW0ZynMH6LftMWAgzep7YvNgplLb29sBDC59ibRh3LhxOP/88wEAhYWF7p0/SPuG0j572ybA9qknd9xxh7tj03cxJ/lXUVERFi1ahMbGRpjNZrz11ls466yzfF7PtqcdQ33uBkuLbY8BA2mWzWZDUlISAOUiL28aGxvdb4x9F3mR9k2aNMn9vRZ3hiDv+i62HKh99l1syfapH6mpqe73YLbNwKioqMB5552HiooKSJKEl156CUuWLOn3Nmx72jCc526wtNj2GDCQpvV2JgsLC92nWnpz8OBB9/eD2cqMtKO/qVnSrr6BXt/25w3bp36xfQZOXV0dzj//fBw7dgzAibTM3kNI+8O2p77hPndDobW2x4CBNO3MM88EcGJa9auvvvJ5Xd8pu3nz5gW8XuQ/ffcUz8zMVLEmNBRjx451P18DTZl/+eWXAICsrCyMGTMm0FUjP6mtrXVvbc226V/Nzc244IIL3O9/jz/+OO6+++5B3ZZtT10jee4GS4ttjwEDadpll13m/v7ll1/2eo3L5cKrr74K4MQi275Hs5O2FRUV4dNPPwUAjB8/HllZWSrXiAZLkiT39PvBgwexZcsWr9dt2bLFPcq5ZMkSzY2akW8vvPACZFkGMPCJwjR4HR0duOiii7Bz504AwM9//nP8+Mc/HvTt2fbUM9LnbrA02fZkIo2bP3++DEC2WCzypk2bPH7/5JNPygBkAPKvfvWr4FeQvPrggw/knp4en7+vqqqSp0+f7n7unn766SDWjkRFRUXu52LZsmWDus2hQ4dks9ksA5Bnzpwpd3R0KH7f0dEhz5w5091+Dx8+HICa01Cfu6KiInnnzp39XvPhhx/KVqtVBiBHRETIZWVlfqqtsdntdnnRokXu5+sHP/jBsO6HbS/4/PHc6bnt8RwG0rxnn30W8+bNQ2dnJxYtWoSf/exnOPvss9HZ2Ym33noLL7zwAgAgPz8fDzzwgMq1pV733nsvenp6cOWVV2Lu3LkYM2YMIiIiUFdXhy+++ALPP/+8e8r1zDPP9PuULvVvw4YNKCwsdP/c+1wAJ9YMrVixQnH9zTff7HEf+fn5ePDBB/H4449jx44dmDdvHn784x9j/PjxOHr0KJ544gns2rULAPDggw8iLy8vIH+L0Yz0uSsuLsbZZ5+NuXPn4pJLLsEpp5zi3gby2LFjeOedd/DOO++4Rzifeuopzv75yXXXXYc1a9YAAM455xzcdttt2Lt3r8/rrVYr8vPzPcrZ9oLPH8+drtue2hEL0WB88MEHcmxsrDuyF7/y8/PlI0eOqF1N6mP06NE+n6++X1deeaXc2NiodnUNZ9myZYN6fnq/fHE6nfKtt97a721vu+022el0BvGvC20jfe7Wrl07qNtFRkbKzz//vAp/YegayvMGQB49erTP+2LbCy5/PHd6bnucYSBduOSSS7B79248++yzWLVqFcrKymC1WpGbm4urrroK99xzDyIjI9WuJvXxyiuvYN26ddi8eTOOHTuGuro6tLS0IDo6GqNGjcIZZ5yBZcuWYe7cuWpXlUbAZDJh+fLluPLKK/HCCy9g+/btqKurQ3JyMmbNmoXvfve7uPDCC9WuJvUxY8YMvPbaa9i8eTN27NiByspK1NXVweFwICEhAZMnT8a5556L22+/vd8DqEhdbHv6o+e2J8ny/+Y9iIiIiIiIBNwliYiIiIiIfGLAQEREREREPjFgICIiIiIinxgwEBERERGRTwwYiIiIiIjIJwYMRERERETkEwMGIiIiIiLyiQEDERERERH5xICBiIiIiIh8YsBAREREREQ+MWAgIiIiIiKfGDAQEREREZFPDBiIiIiIiMgnBgxEREREROQTAwYiIiIiIvKJAQMREREREfnEgIGIiIiIiHxiwEBERH61YsUKSJIESZJQXFysdnWC7tChQ7BarbDZbCgvL1e7OgodHR1ITU2FJEn44osv1K4OEekEAwYiIgIAFBcXuzv6I/kyuh/+8Ifo6enBbbfdhqysLLWroxAZGYkf/vCHAID77rsPsiyrXCMi0gMGDERERH6yadMmfPzxx7BarfjJT36idnW8uvvuu5GYmIhvvvkGK1euVLs6RKQDkszhBSIiAtDT04NDhw75/P3UqVMBADNnzsTLL7/s87opU6b4vW56sXjxYqxevRrXX389XnvtNbWr49NPf/pTPP7445g6dSp2796tdnWISOMYMBAR0aD0phstWLCA+e9eHDp0CBMnToQsy1i9ejW+9a1vqV0ln/bs2YNp06YBANauXYuFCxeqWyEi0jSmJBEREfnByy+/DFmWkZqaivPOO0/t6vRr6tSp7hmj5cuXq1wbItI6BgxERORXA+2StHDhQkiS5B7VLiwsxJ133olx48YhIiICY8aMwW233YaSkhLF7fbu3YtbbrkF48aNg81mw6hR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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import jaxquantum as jqt\n", + "import jax.numpy as jnp\n", + "import jax.experimental.sparse as sparse\n", + "import numpy as np\n", + "import time\n", + "import matplotlib.pyplot as plt\n", + "from jax import jit, grad, vmap\n", + "\n", + "# Set up plotting\n", + "plt.style.use('default')\n", + "plt.rcParams['figure.figsize'] = (10, 6)\n", + "\n", + "\n", + "Omega = 2*jnp.pi*.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", + "\n", + "sz0 = jqt.sigmaz() ^ jqt.identity(N=2)\n", + "\n", + "H0 = Omega*(jqt.sigmax()^jqt.identity(N=2))/2\n", + "\n", + "H0 = H0.to_sparse()\n", + "\n", + "states = jqt.sesolve(H0, g_state, ts) \n", + "szt = jnp.real(jqt.overlap(states, sz0))\n", + "\n", + "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": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Array([-0.31415927, -0.31415927, 0.31415927, 0.31415927], dtype=float64)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "H0.eigenenergies()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = ((2,), (1,)), bdims = (), shape = (2, 1), type = ket, impl = dense\n", + "Qarray data =\n", + "[[1.+0.j]\n", + " [0.+0.j]]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jqt.sigmaz() @ jqt.basis(2,0).to_sparse()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantum array: dims = ((1,), (2,)), bdims = (), shape = (1, 2), type = bra, impl = sparse\n", + "Qarray data =\n", + "BCOO(complex128[1, 2], nse=1)" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "jqt.basis(2,0).to_sparse().dag()" + ] + }, + { + "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 +} diff --git a/jaxquantum/core/qarray.py b/jaxquantum/core/qarray.py index de3b3eed..dd521332 100644 --- a/jaxquantum/core/qarray.py +++ b/jaxquantum/core/qarray.py @@ -1,17 +1,18 @@ -"""QArray.""" +"""New Qarray implementation with sparse support.""" from __future__ import annotations +from abc import ABC, abstractmethod from flax import struct from jax import Array, config, vmap -from typing import List, Union - - -from math import prod -from copy import deepcopy -from numpy import ndarray +from typing import List, Union, TypeVar, Generic, overload, Literal, Any import jax.numpy as jnp import jax.scipy as jsp +from jax.experimental import sparse +from numpy import ndarray +from copy import deepcopy +from math import prod +from enum import Enum from jaxquantum.core.settings import SETTINGS from jaxquantum.utils.utils import robust_isscalar @@ -19,27 +20,403 @@ config.update("jax_enable_x64", True) +# Type variable for implementation types +ImplT = TypeVar("ImplT", bound="QarrayImpl") -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 +class QarrayImplType(Enum): + """Enumeration of available Qarray implementation types.""" + DENSE = "dense" + SPARSE = "sparse" -@struct.dataclass # this allows us to send in and return Qarray from jitted functions -class Qarray: + @classmethod + def has(cls, x) -> bool: + """Return True if x corresponds to a member of QarrayImplType. + + Accepts: + - an existing QarrayImplType member + - a string equal to the member name or value (case-insensitive) + - an implementation class (e.g. DenseImpl, SparseImpl) if available + """ + if isinstance(x, cls): + return True + + if isinstance(x, str): + xl = x.lower() + return any(xl == member.value or xl == member.name.lower() for member in cls) + + # Try mapping from an implementation class to an enum member + try: + cls.from_impl_class(x) + return True + except Exception: + return False + + @classmethod + def from_impl_class(cls, impl_class) -> "QarrayImplType": + """Get implementation type from implementation class.""" + if impl_class == DenseImpl: + return cls.DENSE + elif impl_class == SparseImpl: + return cls.SPARSE + else: + raise ValueError(f"Unknown implementation class: {impl_class}") + + def get_impl_class(self): + """Get the implementation class for this type.""" + if self == QarrayImplType.DENSE: + return DenseImpl + elif self == QarrayImplType.SPARSE: + return SparseImpl + else: + raise ValueError(f"No implementation class for type: {self}") + +def robust_asarray(data) -> Union[Array, sparse.BCOO]: + """Convert data to JAX array or sparse BCOO array.""" + if isinstance(data, sparse.BCOO): + return data + return jnp.asarray(data) + +class QarrayImpl(ABC): + """Abstract base class for Qarray implementations.""" + + @abstractmethod + def get_data(self) -> Array: + """Get the underlying data array.""" + pass + + @property + def data(self) -> Array: + return self.get_data() + + @property + def impl_type(self) -> QarrayImplType: + return QarrayImplType.from_impl_class(type(self)) + + @abstractmethod + def matmul(self, other: "QarrayImpl") -> "QarrayImpl": + """Matrix multiplication.""" + pass + + @abstractmethod + def add(self, other: "QarrayImpl") -> "QarrayImpl": + """Addition.""" + pass + + @abstractmethod + def sub(self, other: "QarrayImpl") -> "QarrayImpl": + """Subtraction.""" + pass + + @abstractmethod + def mul(self, scalar) -> "QarrayImpl": + """Scalar multiplication.""" + pass + + @abstractmethod + def dag(self) -> "QarrayImpl": + """Conjugate transpose.""" + pass + + @abstractmethod + def to_dense(self) -> "DenseImpl": + """Convert to dense implementation.""" + pass + + @abstractmethod + def to_sparse(self) -> "SparseImpl": + """Convert to sparse implementation.""" + pass + + @abstractmethod + def shape(self) -> tuple: + """Get shape of data.""" + pass + + @abstractmethod + def dtype(self): + """Get dtype of data.""" + pass + + @abstractmethod + def __deepcopy__(self, memo=None): + pass + + @abstractmethod + def tidy_up(self, atol): + """Tidy up small values in data.""" + pass + +@struct.dataclass +class DenseImpl(QarrayImpl): + """Dense implementation using JAX dense arrays.""" _data: Array + + def get_data(self) -> Array: + return self._data + + def matmul(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + return DenseImpl(self._data @ other._data) + elif isinstance(other, SparseImpl): + # Convert sparse to dense for matmul + dense_other = other.to_dense() + return DenseImpl(self._data @ dense_other._data) + else: + raise TypeError(f"Unsupported type for matmul: {type(other)}") + + def add(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + return DenseImpl(self._data + other._data) + elif isinstance(other, SparseImpl): + # Convert sparse to dense for addition + dense_other = other.to_dense() + return DenseImpl(self._data + dense_other._data) + else: + raise TypeError(f"Unsupported type for add: {type(other)}") + + def sub(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + return DenseImpl(self._data - other._data) + elif isinstance(other, SparseImpl): + # Convert sparse to dense for subtraction + dense_other = other.to_dense() + return DenseImpl(self._data - dense_other._data) + else: + raise TypeError(f"Unsupported type for sub: {type(other)}") + + def mul(self, scalar) -> QarrayImpl: + return DenseImpl(scalar * self._data) + + def dag(self) -> QarrayImpl: + return DenseImpl(jnp.moveaxis(jnp.conj(self._data), -1, -2)) + + def to_dense(self) -> "DenseImpl": + return self + + def to_sparse(self) -> "SparseImpl": + sparse_data = sparse.BCOO.fromdense(self._data) + return SparseImpl(sparse_data) + + def shape(self) -> tuple: + return self._data.shape + + def dtype(self): + return self._data.dtype + + def frobenius_norm(self) -> float: + """Compute Frobenius norm.""" + return jnp.sqrt(jnp.sum(jnp.abs(self._data) ** 2)) + + def real(self) -> QarrayImpl: + """Element-wise real part.""" + return DenseImpl(jnp.real(self._data)) + + def imag(self) -> QarrayImpl: + """Element-wise imaginary part.""" + return DenseImpl(jnp.imag(self._data)) + + def conj(self) -> QarrayImpl: + """Element-wise complex conjugate.""" + return DenseImpl(jnp.conj(self._data)) + + def __deepcopy__(self, memo=None): + return DenseImpl( + _data=deepcopy(self._data, memo) + ) + + def tidy_up(self, atol): + """Tidy up small values in data.""" + + data = self._data + 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 DenseImpl( + _data=data_new + ) + +@struct.dataclass +class SparseImpl(QarrayImpl): + """Sparse implementation using JAX sparse BCOO arrays.""" + _data: sparse.BCOO + + def get_data(self) -> Array: + return self._data + + def matmul(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + # Convert sparse to dense for matmul + dense_self = self.to_dense() + return DenseImpl(dense_self._data @ other._data) + + elif isinstance(other, SparseImpl): + return SparseImpl(self._data @ other._data) + else: + raise TypeError(f"Unsupported type for matmul: {type(other)}") + + def add(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + # Convert sparse to dense for addition + dense_self = self.to_dense() + return DenseImpl(dense_self._data + other._data) + elif isinstance(other, SparseImpl): + return SparseImpl(self._data + other._data) + else: + raise TypeError(f"Unsupported type for add: {type(other)}") + + def sub(self, other: QarrayImpl) -> QarrayImpl: + if isinstance(other, DenseImpl): + # Convert sparse to dense for subtraction + dense_self = self.to_dense() + return DenseImpl(dense_self._data - other._data) + elif isinstance(other, SparseImpl): + return SparseImpl(self._data - other._data) + else: + raise TypeError(f"Unsupported type for sub: {type(other)}") + + def mul(self, scalar) -> QarrayImpl: + return SparseImpl(scalar * self._data) + + def dag(self) -> QarrayImpl: + # Implement sparse conjugate transpose directly + # Transpose the sparse matrix (last two dimensions only) and conjugate the data + ndim = self._data.ndim + if ndim >= 2: + # Create permutation that swaps only the last two dimensions + permutation = tuple(range(ndim - 2)) + (ndim - 1, ndim - 2) + transposed_data = sparse.bcoo_transpose(self._data, permutation=permutation) + else: + transposed_data = self._data + + conjugated_data = sparse.BCOO((jnp.conj(transposed_data.data), transposed_data.indices), + shape=transposed_data.shape) + return SparseImpl(conjugated_data) + + def to_dense(self) -> "DenseImpl": + return DenseImpl(self._data.todense()) + + @classmethod + def _to_sparse(cls, data) -> sparse.BCOO: + if isinstance(data, sparse.BCOO): + return data + return sparse.BCOO.fromdense(data) + + def to_sparse(self) -> "SparseImpl": + return self + + def shape(self) -> tuple: + return self._data.shape + + def dtype(self): + return self._data.dtype + + def frobenius_norm(self) -> float: + """Compute Frobenius norm directly from sparse data.""" + # For sparse matrices, we can compute the Frobenius norm as sqrt(sum(|data|^2)) + # This avoids converting to dense + return jnp.sqrt(jnp.sum(jnp.abs(self._data.data) ** 2)) + + @classmethod + def _real(cls, data): + return sparse.BCOO( + (jnp.real(data.data), data.indices), + shape=data.shape + ) + + def real(self) -> QarrayImpl: + """Element-wise real part.""" + return SparseImpl(SparseImpl._real(self._data)) + + @classmethod + def _imag(cls, data): + return sparse.BCOO( + (jnp.imag(data.data), data.indices), + shape=data.shape + ) + + def imag(self) -> QarrayImpl: + """Element-wise imaginary part.""" + return SparseImpl(SparseImpl._imag(self._data)) + + @classmethod + def _conj(cls, data): + return sparse.BCOO( + (jnp.conj(data.data), data.indices), + shape=data.shape + ) + + def conj(self) -> QarrayImpl: + """Element-wise complex conjugate.""" + return SparseImpl(SparseImpl._conj(self._data)) + + @classmethod + def _abs(cls, data): + return sparse.sparsify(jnp.abs)(data) + + def abs(self) -> QarrayImpl: + """Element-wise absolute value.""" + return SparseImpl(SparseImpl._abs(self._data)) + + def __deepcopy__(self, memo=None): + return SparseImpl( + _data=deepcopy(self._data, memo) + ) + + def tidy_up(self, atol): + # """Tidy up small values in data.""" + + # data = self._data + # data_re = SparseImpl._real(data) + # data_im = SparseImpl._imag(data) + # data_re_mask = SparseImpl._abs(data_re) > atol + # data_im_mask = SparseImpl._abs(data_im) > atol # NOTE: This does not work for sparse arrays + # data_new = data_re * data_re_mask + 1j * data_im * data_im_mask + + # return SparseImpl( + # _data=data_new + # ) + pass + +@struct.dataclass +class Qarray(Generic[ImplT]): + """Quantum array with implementation-based architecture.""" + _impl: ImplT _qdims: Qdims = struct.field(pytree_node=False) _bdims: tuple[int] = struct.field(pytree_node=False) # Initialization ---- @classmethod - def create(cls, data, dims=None, bdims=None): + @classmethod + @overload + def create(cls, data, dims=None, bdims=None, implementation: Literal[QarrayImplType.DENSE] = QarrayImplType.DENSE) -> "Qarray[DenseImpl]": + ... + + @classmethod + @overload + def create(cls, data, dims=None, bdims=None, implementation: Literal[QarrayImplType.SPARSE] = ...) -> "Qarray[SparseImpl]": + ... + + @classmethod + @overload + def create(cls, data, dims=None, bdims=None, implementation=...) -> "Qarray[DenseImpl]": + ... + + @classmethod + def create(cls, data, dims=None, bdims=None, implementation=QarrayImplType.DENSE): + """Create a Qarray from data. + + Args: + data: Input data array + dims: Quantum dimensions + bdims: Batch dimensions + implementation: QarrayImplType.DENSE or QarrayImplType.SPARSE + """ # Step 1: Prepare data ---- - data = jnp.asarray(data) + data = robust_asarray(data) if len(data.shape) == 1 and data.shape[0] > 0: data = data.reshape(data.shape[0], 1) @@ -80,12 +457,41 @@ def create(cls, data, dims=None, bdims=None): # NOTE: 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"]) + # We could instead use this tidy up where we think we need it. + + implementation = QarrayImplType(implementation) + if implementation == QarrayImplType.SPARSE: + impl = SparseImpl(SparseImpl._to_sparse(data)) + # impl = impl.tidy_up(SETTINGS["auto_tidyup_atol"]) + # Sparse tidy up is currently not implemented. - return cls(data, qdims, bdims) + elif implementation == QarrayImplType.DENSE: + impl = DenseImpl(data) + impl = impl.tidy_up(SETTINGS["auto_tidyup_atol"]) - # ---- + return cls(impl, qdims, bdims) + + @classmethod + @classmethod + @overload + def from_sparse(cls, data, dims=None, bdims=None) -> "Qarray[SparseImpl]": + ... + + @classmethod + def from_sparse(cls, data, dims=None, bdims=None): + """Create a Qarray from sparse data.""" + return cls.create(data.todense(), dims=dims, bdims=bdims, implementation=QarrayImplType.SPARSE) + + @classmethod + @classmethod + @overload + def from_list(cls, qarr_list: List["Qarray[DenseImpl]"]) -> "Qarray[DenseImpl]": + ... + + @classmethod + @overload + def from_list(cls, qarr_list: List["Qarray[SparseImpl]"]) -> "Qarray[SparseImpl]": + ... @classmethod def from_list(cls, qarr_list: List[Qarray]) -> Qarray: @@ -99,13 +505,33 @@ def from_list(cls, qarr_list: List[Qarray]) -> Qarray: else: dims = qarr_list[0].dims bdims = qarr_list[0].bdims + # Check if all have the same implementation type + impl_type = type(qarr_list[0]._impl) if not all(qarr.dims == dims and qarr.bdims == bdims for qarr in qarr_list): raise ValueError("All Qarrays in the list must have the same dimensions.") bdims = (len(qarr_list),) + bdims - return cls.create(data, dims=dims, bdims=bdims) + # Check if all implementations are the same type + if len(qarr_list) > 0 and all(isinstance(qarr._impl, impl_type) for qarr in qarr_list): + implementation = QarrayImplType.from_impl_class(impl_type) + else: + # If mixed or empty, default to dense + implementation = QarrayImplType.DENSE + + return cls.create(data, dims=dims, bdims=bdims, implementation=implementation) + + @classmethod + @classmethod + @overload + def from_array(cls, qarr_arr: "Qarray[DenseImpl]") -> "Qarray[DenseImpl]": + ... + + @classmethod + @overload + def from_array(cls, qarr_arr: "Qarray[SparseImpl]") -> "Qarray[SparseImpl]": + ... @classmethod def from_array(cls, qarr_arr) -> Qarray: @@ -150,7 +576,7 @@ def qtype(self): @property def dtype(self): - return self._data.dtype + return self._impl.dtype() @property def dims(self): @@ -176,11 +602,11 @@ def space_dims(self): @property def data(self): - return self._data + return self._impl.data @property def shaped_data(self): - return self._data.reshape(self.bdims + self.dims[0] + self.dims[1]) + return self.data.reshape(self.bdims + self.dims[0] + self.dims[1]) @property def shape(self): @@ -190,6 +616,33 @@ def shape(self): def is_batched(self): return len(self.bdims) > 0 + @property + def is_sparse(self): + return self._impl.impl_type == QarrayImplType.SPARSE + + @property + def is_dense(self): + return self._impl.impl_type == QarrayImplType.DENSE + + @property + def impl_type(self): + """Get the implementation type.""" + return self._impl.impl_type + + def to_sparse(self) -> "Qarray[SparseImpl]": + """Convert to sparse implementation.""" + if self.is_sparse: + return self + new_impl = self._impl.to_sparse() + return Qarray(new_impl, self._qdims, self._bdims) + + def to_dense(self) -> "Qarray[DenseImpl]": + """Convert to dense implementation.""" + if self.is_dense: + return self + new_impl = self._impl.to_dense() + return Qarray(new_impl, self._qdims, self._bdims) + def __getitem__(self, index): if len(self.bdims) > 0: return Qarray.create( @@ -207,10 +660,14 @@ def reshape_bdims(self, *args): new_shape = new_bdims else: new_shape = new_bdims + (prod(self.dims[0]),) + (-1,) + + # Preserve implementation type + implementation = self.impl_type return Qarray.create( self.data.reshape(new_shape), dims=self.dims, bdims=new_bdims, + implementation=implementation, ) def space_to_qdims(self, space_dims: List[int]): @@ -243,7 +700,9 @@ def reshape_qdims(self, *args): new_qdims = self.space_to_qdims(new_space_dims) new_bdims = self.bdims - return Qarray.create(self.data, dims=new_qdims, bdims=new_bdims) + # Preserve implementation type + implementation = self.impl_type + return Qarray.create(self.data, dims=new_qdims, bdims=new_bdims, implementation=implementation) def resize(self, new_shape): """Resize the Qarray to a new shape. @@ -252,9 +711,12 @@ def resize(self, new_shape): """ dims = self.dims data = jnp.resize(self.data, new_shape) + # Preserve implementation type + implementation = self.impl_type return Qarray.create( data, dims=dims, + implementation=implementation, ) def __len__(self): @@ -274,34 +736,29 @@ def __eq__(self, other): if self.bdims != other.bdims: return False - return jnp.all(self.data == other.data) + # Convert both to dense for comparison to handle sparse arrays + self_dense = self.data.todense() if hasattr(self.data, 'todense') else self.data + other_dense = other.data.todense() if hasattr(other.data, 'todense') else other.data + + return jnp.all(self_dense == other_dense) def __ne__(self, other): return not self.__eq__(other) - # ---- - # Elementary Math ---- def __matmul__(self, other): if not isinstance(other, Qarray): return NotImplemented + _qdims_new = self._qdims @ other._qdims + new_impl = self._impl.matmul(other._impl) + return Qarray.create( - self.data @ other.data, + new_impl.data, dims=_qdims_new.dims, + implementation=new_impl.impl_type, ) - # NOTE: not possible to reach this. - # def __rmatmul__(self, other): - # if not isinstance(other, Qarray): - # return NotImplemented - - # _qdims_new = other._qdims @ self._qdims - # return Qarray.create( - # other.data @ self.data, - # dims=_qdims_new.dims, - # ) - def __mul__(self, other): if isinstance(other, Qarray): return self.__matmul__(other) @@ -310,16 +767,14 @@ def __mul__(self, other): if not robust_isscalar(other) and len(other.shape) > 0: # not a scalar other = other.reshape(other.shape + (1, 1)) + new_impl = self._impl.mul(other) return Qarray.create( - other * self.data, + new_impl.data, dims=self._qdims.dims, + implementation=new_impl.impl_type, ) def __rmul__(self, other): - # NOTE: not possible to reach this. - # if isinstance(other, Qarray): - # return self.__rmatmul__(other) - return self.__mul__(other) def __neg__(self): @@ -343,7 +798,12 @@ def __add__(self, other): + repr(other.dims) ) raise ValueError(msg) - return Qarray.create(self.data + other.data, dims=self.dims) + new_impl = self._impl.add(other._impl) + return Qarray.create( + new_impl.data, + dims=self.dims, + implementation=new_impl.impl_type, + ) if robust_isscalar(other) and other == 0: return self.copy() @@ -356,6 +816,7 @@ def __add__(self, other): other * jnp.eye(self.data.shape[-2], dtype=self.data.dtype), dims=self.dims, ) + # TODO: move this math into implementaiton to support sparse! return self.__add__(other) return NotImplemented @@ -373,7 +834,12 @@ def __sub__(self, other): + repr(other.dims) ) raise ValueError(msg) - return Qarray.create(self.data - other.data, dims=self.dims) + new_impl = self._impl.sub(other._impl) + return Qarray.create( + new_impl.data, + dims=self.dims, + implementation=new_impl.impl_type, + ) if robust_isscalar(other) and other == 0: return self.copy() @@ -386,6 +852,7 @@ def __sub__(self, other): other * jnp.eye(self.data.shape[-2], dtype=self.data.dtype), dims=self.dims, ) + # TODO: move this math into implementaiton to support sparse! return self.__sub__(other) return NotImplemented @@ -409,16 +876,16 @@ def __pow__(self, other): return powm(self, other) - # ---- - # String Representation ---- def _str_header(self): + impl_type = self.impl_type.value out = ", ".join( [ "Quantum array: dims = " + str(self.dims), "bdims = " + str(self.bdims), - "shape = " + str(self._data.shape), + "shape = " + str(self.data.shape), "type = " + str(self.qtype), + "impl = " + impl_type, ] ) return out @@ -434,18 +901,15 @@ def header(self): def __repr__(self): return self.__str__() - # ---- - # Utilities ---- def copy(self, memo=None): - # return Qarray.create(deepcopy(self.data), dims=self.dims) return self.__deepcopy__(memo) def __deepcopy__(self, memo): """Need to override this when defininig __getattr__.""" return Qarray( - _data=deepcopy(self._data, memo=memo), + _impl=deepcopy(self._impl, memo=memo), _qdims=deepcopy(self._qdims, memo=memo), _bdims=deepcopy(self._bdims, memo=memo), ) @@ -469,17 +933,21 @@ def __getattr__(self, method_name): ) def func(*args, **kwargs): - res = method_f(self.data, *args, **kwargs) + # For operations that might not be supported in sparse, convert to dense + if self.is_sparse: + dense_self = self.to_dense() + res = method_f(dense_self.data, *args, **kwargs) + else: + res = method_f(self.data, *args, **kwargs) if getattr(res, "shape", None) is None or res.shape != self.data.shape: return res else: - return Qarray.create(res, dims=self._qdims.dims) + # Preserve implementation type + return Qarray.create(res, dims=self._qdims.dims, implementation=self.impl_type) return func - # ---- - # Conversions / Reshaping ---- def dag(self): return dag(self) @@ -502,14 +970,44 @@ def transpose(self, *args): def keep_only_diag_elements(self): return keep_only_diag_elements(self) - # ---- - # Math Functions ---- def unit(self): return unit(self) def norm(self): return norm(self) + + def frobenius_norm(self): + """Compute Frobenius norm directly from implementation.""" + return self._impl.frobenius_norm() + + def real(self): + """Element-wise real part.""" + new_impl = self._impl.real() + return Qarray.create( + new_impl.data, + dims=self.dims, + implementation=new_impl.impl_type, + ) + + def imag(self): + """Element-wise imaginary part.""" + new_impl = self._impl.imag() + + return Qarray.create( + new_impl.data, + dims=self.dims, + implementation=new_impl.impl_type, + ) + + def conj(self): + """Element-wise complex conjugate.""" + new_impl = self._impl.conj() + return Qarray.create( + new_impl.data, + dims=self.dims, + implementation=new_impl.impl_type, + ) def expm(self): return expm(self) @@ -538,14 +1036,12 @@ def eigenstates(self): def eigenenergies(self): return eigenenergies(self) + def eigenvalues(self): + return eigenenergies(self) + def collapse(self, mode="sum"): return collapse(self, mode=mode) - # ---- - - -ARRAY_TYPES = (Array, ndarray, Qarray) - # Qarray operations --------------------------------------------------------------------- def concatenate(qarr_list: List[Qarray], axis: int = 0) -> Qarray: @@ -571,7 +1067,6 @@ def concatenate(qarr_list: List[Qarray], axis: int = 0) -> Qarray: dims = non_empty_qarr_list[0].dims return Qarray.create(concatenated_data, dims=dims) - def collapse(qarr: Qarray, mode="sum") -> Qarray: """Collapse the Qarray. @@ -581,25 +1076,22 @@ def collapse(qarr: Qarray, mode="sum") -> Qarray: Returns: Collapsed quantum array """ + if mode == "sum": if len(qarr.bdims) == 0: return qarr batch_axes = list(range(len(qarr.bdims))) - return Qarray.create(jnp.sum(qarr.data, axis=batch_axes), dims=qarr.dims) - + + # Preserve implementation type + implementation = qarr.impl_type + return Qarray.create(jnp.sum(qarr.data, axis=batch_axes), dims=qarr.dims, implementation=implementation) def transpose(qarr: Qarray, indices: List[int]) -> Qarray: - """Transpose the quantum array. - - Args: - qarr (Qarray): quantum array - *args: axes to transpose - - Returns: - tranposed Qarray - """ + """Transpose the quantum array.""" + qarr = qarr.to_dense() + indices = list(indices) shaped_data = qarr.shaped_data @@ -607,7 +1099,6 @@ def transpose(qarr: Qarray, indices: List[int]) -> Qarray: bdims_indxs = list(range(len(qarr.bdims))) reshape_indices = indices + [j + len(dims[0]) for j in indices] - reshape_indices = bdims_indxs + [j + len(bdims_indxs) for j in reshape_indices] shaped_data = shaped_data.transpose(reshape_indices) @@ -618,8 +1109,10 @@ def transpose(qarr: Qarray, indices: List[int]) -> Qarray: full_dims = prod(dims[0]) full_data = shaped_data.reshape(*qarr.bdims, full_dims, -1) - return Qarray.create(full_data, dims=new_dims) - + + # Preserve implementation type + implementation = qarr.impl_type + return Qarray.create(full_data, dims=new_dims, implementation=implementation) def unit(qarr: Qarray) -> Qarray: """Normalize the quantum array. @@ -632,8 +1125,9 @@ def unit(qarr: Qarray) -> Qarray: """ return qarr / qarr.norm() - def norm(qarr: Qarray) -> float: + qarr = qarr.to_dense() # TODO: support sparse norm! + qdata = qarr.data bdims = qarr.bdims @@ -660,28 +1154,20 @@ def norm(qarr: Qarray) -> float: else: return jnp.linalg.norm(qdata) - def tensor(*args, **kwargs) -> Qarray: - """Tensor product. - - Args: - *args (Qarray): tensors to take the product of - parallel (bool): if True, use parallel einsum for tensor product - true: [A,B] ^ [C,D] = [A^C, B^D] - false (default): [A,B] ^ [C,D] = [A^C, A^D, B^C, B^D] - - Returns: - Tensor product of given tensors - - """ - + """Tensor product.""" parallel = kwargs.pop("parallel", False) - data = args[0].data - dims = deepcopy(args[0].dims) + # For tensor operations, we'll need to handle mixed implementations + # For now, convert all to dense for tensor operations + dense_args = [arg.to_dense() if arg.impl_type != QarrayImplType.DENSE else arg for arg in args] + + data = dense_args[0].data + dims = deepcopy(dense_args[0].dims) dims_0 = dims[0] dims_1 = dims[1] - for arg in args[1:]: + + for arg in dense_args[1:]: if parallel: a = data b = arg.data @@ -707,69 +1193,37 @@ def tensor(*args, **kwargs) -> Qarray: return Qarray.create(data, dims=(dims_0, dims_1)) - def tr(qarr: Qarray, **kwargs) -> Array: - """Full trace. - - Args: - qarr (Qarray): quantum array - - Returns: - Full trace. - """ + """Full trace.""" axis1 = kwargs.get("axis1", -2) axis2 = kwargs.get("axis2", -1) return jnp.trace(qarr.data, axis1=axis1, axis2=axis2, **kwargs) - def trace(qarr: Qarray, **kwargs) -> Array: - """Full trace. - - Args: - qarr (Qarray): quantum array - - Returns: - Full trace. - """ + """Full trace.""" return tr(qarr, **kwargs) - def expm_data(data: Array, **kwargs) -> Array: - """Matrix exponential wrapper. - - Returns: - matrix exponential - """ + """Matrix exponential wrapper.""" return jsp.linalg.expm(data, **kwargs) - def expm(qarr: Qarray, **kwargs) -> Qarray: - """Matrix exponential wrapper. - - Returns: - matrix exponential - """ + """Matrix exponential wrapper.""" dims = qarr.dims - data = expm_data(qarr.data, **kwargs) + # Convert to dense for expm + dense_data = qarr.to_dense().data + data = expm_data(dense_data, **kwargs) return Qarray.create(data, dims=dims) - def powm(qarr: Qarray, n: Union[int, float], clip_eigvals=False) -> Qarray: - """Matrix power. - - Args: - qarr (Qarray): quantum array - n (int): power - clip_eigvals (bool): clip eigenvalues to always be able to compute - non-integer powers - - Returns: - matrix power - """ + """Matrix power.""" + # Convert to dense for powm + dense_qarr = qarr.to_dense() + if isinstance(n, int): - data_res = jnp.linalg.matrix_power(qarr.data, n) + data_res = jnp.linalg.matrix_power(dense_qarr.data, n) else: - evalues, evectors = jnp.linalg.eig(qarr.data) + evalues, evectors = jnp.linalg.eig(dense_qarr.data) if clip_eigvals: evalues = jnp.maximum(evalues, 0) else: @@ -780,60 +1234,46 @@ def powm(qarr: Qarray, n: Union[int, float], clip_eigvals=False) -> Qarray: "Got a matrix with a negative eigenvalue." ) data_res = evectors * jnp.pow(evalues, n) @ jnp.linalg.inv(evectors) + return Qarray.create(data_res, dims=qarr.dims) - def cosm_data(data: Array, **kwargs) -> Array: - """Matrix cosine wrapper. - - Returns: - matrix cosine - """ + """Matrix cosine wrapper.""" return (expm_data(1j * data) + expm_data(-1j * data)) / 2 - def cosm(qarr: Qarray) -> Qarray: - """Matrix cosine wrapper. - - Args: - qarr (Qarray): quantum array - - Returns: - matrix cosine - """ + """Matrix cosine wrapper.""" dims = qarr.dims - data = cosm_data(qarr.data) + # Convert to dense for cosm + dense_data = qarr.to_dense().data + data = cosm_data(dense_data) return Qarray.create(data, dims=dims) - def sinm_data(data: Array, **kwargs) -> Array: - """Matrix sine wrapper. - - Args: - data: matrix - - Returns: - matrix sine - """ + """Matrix sine wrapper.""" return (expm_data(1j * data) - expm_data(-1j * data)) / (2j) - def sinm(qarr: Qarray) -> Qarray: + """Matrix sine wrapper.""" dims = qarr.dims - data = sinm_data(qarr.data) + # Convert to dense for sinm + dense_data = qarr.to_dense().data + data = sinm_data(dense_data) return Qarray.create(data, dims=dims) - def keep_only_diag_elements(qarr: Qarray) -> Qarray: + """Keep only diagonal elements.""" if len(qarr.bdims) > 0: raise ValueError("Cannot keep only diagonal elements of a batched Qarray.") dims = qarr.dims data = jnp.diag(jnp.diag(qarr.data)) - return Qarray.create(data, dims=dims) - + # Preserve implementation type + implementation = qarr.impl_type + return Qarray.create(data, dims=dims, implementation=implementation) def to_ket(qarr: Qarray) -> Qarray: + """Convert to ket.""" if qarr.qtype == Qtypes.ket: return qarr elif qarr.qtype == Qtypes.bra: @@ -841,18 +1281,12 @@ def to_ket(qarr: Qarray) -> Qarray: else: raise ValueError("Can only get ket from a ket or bra.") - def eigenstates(qarr: Qarray) -> Qarray: - """Eigenstates of a quantum array. - - Args: - qarr (Qarray): quantum array - - Returns: - eigenvalues and eigenstates - """ - - evals, evecs = jnp.linalg.eigh(qarr.data) + """Eigenstates of a quantum array.""" + # Convert to dense for eigenstates + dense_qarr = qarr.to_dense() + + evals, evecs = jnp.linalg.eigh(dense_qarr.data) idxs_sorted = jnp.argsort(evals, axis=-1) dims = ket_from_op_dims(qarr.dims) @@ -872,40 +1306,20 @@ def eigenstates(qarr: Qarray) -> Qarray: return evals, evecs - def eigenenergies(qarr: Qarray) -> Array: - """Eigenvalues of a quantum array. - - Args: - qarr (Qarray): quantum array - - Returns: - eigenvalues - """ - - evals = jnp.linalg.eigvalsh(qarr.data) + """Eigenvalues of a quantum array.""" + # Convert to dense for eigenenergies + dense_qarr = qarr.to_dense() + evals = jnp.linalg.eigvalsh(dense_qarr.data) return evals - -# More quantum specific ----------------------------------------------------- - - def ptrace(qarr: Qarray, indx) -> Qarray: - """Partial Trace. - - Args: - rho: density matrix - 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 - """ - - qarr = ket2dm(qarr) - rho = qarr.shaped_data - dims = qarr.dims + """Partial Trace.""" + # Convert to dense for ptrace + dense_qarr = qarr.to_dense() + dense_qarr = ket2dm(dense_qarr) + rho = dense_qarr.shaped_data + dims = dense_qarr.dims Nq = len(dims[0]) @@ -916,7 +1330,7 @@ def ptrace(qarr: Qarray, indx) -> Qarray: indxs.append(j) indxs.append(j + Nq) - bdims = qarr.bdims + bdims = dense_qarr.bdims len_bdims = len(bdims) bdims_indxs = list(range(len_bdims)) indxs = bdims_indxs + [j + len_bdims for j in indxs] @@ -927,22 +1341,15 @@ def ptrace(qarr: Qarray, indx) -> Qarray: return Qarray.create(rho) - def dag(qarr: Qarray) -> Qarray: - """Conjugate transpose. - - Args: - qarr (Qarray): quantum array - - Returns: - conjugate transpose of qarr - """ + """Conjugate transpose.""" dims = qarr.dims[::-1] - - data = dag_data(qarr.data) - - return Qarray.create(data, dims=dims) - + new_impl = qarr._impl.dag() + return Qarray.create( + new_impl.data, + dims=dims, + implementation=new_impl.impl_type, + ) def dag_data(arr: Array) -> Array: """Conjugate transpose. @@ -961,18 +1368,8 @@ def dag_data(arr: Array) -> Array: jnp.conj(arr), -1, -2 ) # transposes last two axes, good for batching - def ket2dm(qarr: Qarray) -> Qarray: - """Turns ket into density matrix. - Does nothing if already operator. - - Args: - qarr (Qarray): qarr - - Returns: - Density matrix - """ - + """Turns ket into density matrix.""" if qarr.qtype == Qtypes.oper: return qarr @@ -981,29 +1378,17 @@ def ket2dm(qarr: Qarray) -> Qarray: return qarr @ qarr.dag() - -# Data level operations ---- - - +# Data level operations def is_dm_data(data: Array) -> bool: - """Check if data is a density matrix. - - Args: - data: matrix - Returns: - True if data is a density matrix - """ + """Check if data is a density matrix.""" return data.shape[-2] == data.shape[-1] - def powm_data(data: Array, n: int) -> Array: - """Matrix power. + """Matrix power.""" + return jnp.linalg.matrix_power(data, n) - Args: - data: matrix - n: power +# Type aliases for readability +DenseQarray = Qarray[DenseImpl] +SparseQarray = Qarray[SparseImpl] - Returns: - matrix power - """ - return jnp.linalg.matrix_power(data, n) +ARRAY_TYPES = (Array, ndarray, Qarray) \ No newline at end of file diff --git a/test/test_core_solvers.py b/test/test_core_solvers.py index 02b6b9a0..b8f50ea3 100644 --- a/test/test_core_solvers.py +++ b/test/test_core_solvers.py @@ -90,33 +90,28 @@ def test_sesolve_edge_cases(): def test_mesolve_batch(): N = 100 + a = jqt.destroy(N); n = a.dag() @ a - omega_a = 2.0*jnp.pi*5.0 - kappa = 2*jnp.pi*jnp.array([1,2]) # Batching to explore two different kappa values! - initial_state = jqt.displace(N, 0.1) @ jqt.basis(N,0) - initial_state_dm = initial_state.to_dm() - ts = jnp.linspace(0, 4*2*jnp.pi/omega_a, 101) + omega_a = 2.0*jnp.pi*5.0; H0 = omega_a*n # Hamiltonian - a = jqt.destroy(N) - n = a.dag() @ a + kappa = 2*jnp.pi*jnp.array([1,2]); batched_loss_op = jnp.sqrt(kappa)*a; + c_ops = jqt.Qarray.from_list([batched_loss_op]) # collapse operators - c_ops = jqt.Qarray.from_list([jnp.sqrt(kappa)*a]) + initial_state = (jqt.displace(N, 0.1) @ jqt.basis(N,0)).to_dm() # initial state - @jit - def Ht(t): - H0 = omega_a*n - return H0 + ts = jnp.linspace(0, 4*2*jnp.pi/omega_a, 101) # Time points + + solver_options = jqt.SolverOptions.create(progress_meter=True) + states = jqt.mesolve( + H0, initial_state, ts, c_ops=c_ops, solver_options=solver_options) # solve + + n_exp = jnp.real(jqt.overlap(n, states)); a_exp = jqt.overlap(a, states) # expectation values - solver_options = jqt.SolverOptions.create(progress_meter=True) - states = jqt.mesolve(Ht, initial_state_dm, ts, c_ops=c_ops, solver_options=solver_options) - nt = jnp.real(jqt.overlap(n, states)) - a_real = jnp.real(jqt.overlap(a, states)) - a_imag = jnp.imag(jqt.overlap(a, states)) for j in range(2): test_time = ts[50] - test_nt = nt[50,j] - expected_nt = jnp.exp(-kappa[j]*test_time) * jnp.abs(jqt.overlap(n, initial_state_dm)) # Expectation value of n at time t + test_nt = n_exp[50,j] + expected_nt = jnp.exp(-kappa[j]*test_time) * jnp.abs(jqt.overlap(n, initial_state)) # Expectation value of n at time t assert jnp.isclose(test_nt, expected_nt, atol=1e-8), f"Expected {expected_nt}, got {test_nt}" def test_mesolve(): diff --git a/test/test_new_qarray.py b/test/test_new_qarray.py new file mode 100644 index 00000000..15a905f1 --- /dev/null +++ b/test/test_new_qarray.py @@ -0,0 +1,502 @@ +import pytest +import sys +import os + +# Add the jaxquantum directory to the sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import jaxquantum as jqt +import jax.numpy as jnp +from jax.experimental import sparse + +minimum_version_for_tests = "0.2.0" + + +# Version +# ======================================== +def test_version(): + high_enough = False + ver_tuple = tuple(map(int, jqt.__version__.split('.'))) + test_ver_tuple = tuple(map(int, minimum_version_for_tests.split('.'))) + high_enough = high_enough or (ver_tuple[0] > test_ver_tuple[0]) + high_enough = high_enough or (ver_tuple[0] == test_ver_tuple[0] and ver_tuple[1] > test_ver_tuple[1]) + high_enough = high_enough or (ver_tuple[0] == test_ver_tuple[0] and ver_tuple[1] == test_ver_tuple[1] and ver_tuple[2] >= test_ver_tuple[2]) + assert high_enough +# ======================================== + +# Implementation Type Tests +# ======================================== +def test_implementation_types(): + """Test that Qarray can be created with different implementation types.""" + + # Test dense implementation (default) + a_dense = jqt.Qarray.create(jnp.array([1, 2, 3])) + assert a_dense.is_dense + assert not a_dense.is_sparse + assert isinstance(a_dense._impl, jqt.DenseImpl) + + # Test sparse implementation + a_sparse = jqt.Qarray.create(jnp.array([1, 2, 3]), implementation=jqt.QarrayImplType.SPARSE) + assert a_sparse.is_sparse + assert not a_sparse.is_dense + assert isinstance(a_sparse._impl, jqt.SparseImpl) + + # Test from_sparse + sparse_data = sparse.BCOO.fromdense(jnp.array([[1, 0], [0, 2]])) + a_from_sparse = jqt.Qarray.from_sparse(sparse_data) + assert a_from_sparse.is_sparse + assert isinstance(a_from_sparse._impl, jqt.SparseImpl) + +def test_conversion_between_implementations(): + """Test conversion between dense and sparse implementations.""" + + data = jnp.array([[1, 0, 2], [0, 3, 0], [4, 0, 5]]) + + # Dense to sparse + a_dense = jqt.Qarray.create(data) + a_sparse = a_dense.to_sparse() + assert a_sparse.is_sparse + assert jnp.allclose(a_dense.data, a_sparse.data.todense()) + + # Sparse to dense + a_dense_again = a_sparse.to_dense() + assert a_dense_again.is_dense + assert jnp.allclose(a_dense.data, a_dense_again.data) + + # Test that conversion is idempotent + assert a_dense.to_dense() == a_dense + assert a_sparse.to_sparse() == a_sparse + +def test_implementation_preservation(): + """Test that operations preserve implementation type when possible.""" + + # Dense operations should stay dense + a = jqt.Qarray.create(jnp.array([[1, 2], [3, 4]])) + b = jqt.Qarray.create(jnp.array([[5, 6], [7, 8]])) + + # Basic operations + assert (a + b).is_dense + assert (a - b).is_dense + assert (a @ b).is_dense + assert (a * 2).is_dense + assert a.dag().is_dense + + # Sparse operations should stay sparse for supported operations + a_sparse = a.to_sparse() + b_sparse = b.to_sparse() + + assert (a_sparse + b_sparse).is_sparse + assert (a_sparse - b_sparse).is_sparse + assert (a_sparse @ b_sparse).is_sparse + assert (a_sparse * 2).is_sparse + # Note: dag() converts to dense for sparse due to JAX limitations + +def test_mixed_implementation_operations(): + """Test operations between dense and sparse implementations.""" + + a_dense = jqt.Qarray.create(jnp.array([[1, 2], [3, 4]])) + a_sparse = a_dense.to_sparse() + + # Mixed operations should work (auto-conversion) + result1 = a_dense + a_sparse + result2 = a_sparse + a_dense + result3 = a_dense @ a_sparse + result4 = a_sparse @ a_dense + + # Results should be consistent regardless of order + # Convert both to dense for comparison + result1_dense = result1.data.todense() if hasattr(result1.data, 'todense') else result1.data + result2_dense = result2.data.todense() if hasattr(result2.data, 'todense') else result2.data + result3_dense = result3.data.todense() if hasattr(result3.data, 'todense') else result3.data + result4_dense = result4.data.todense() if hasattr(result4.data, 'todense') else result4.data + + assert jnp.allclose(result1_dense, result2_dense) + assert jnp.allclose(result3_dense, result4_dense) + + # Test with different sparse matrices + sparse_data = sparse.BCOO.fromdense(jnp.array([[1, 0], [0, 2]])) + b_sparse = jqt.Qarray.from_sparse(sparse_data) + + result5 = a_dense + b_sparse + result6 = b_sparse + a_dense + + # Convert both to dense for comparison + result5_dense = result5.data.todense() if hasattr(result5.data, 'todense') else result5.data + result6_dense = result6.data.todense() if hasattr(result6.data, 'todense') else result6.data + assert jnp.allclose(result5_dense, result6_dense) + +# ======================================== + +# Backward Compatibility Tests +# ======================================== +def test_backward_compatibility_basic(): + """Test that basic operations work the same as the old implementation.""" + + # Test basic creation + a = jqt.Qarray.create(jnp.array([1,2,3])) + assert a.shape == (3,1) + assert a.qtype == jqt.Qtypes.ket + + a = jqt.Qarray.create(jnp.array([[1,2,3],[4,5,6]])) + assert a.shape == (2,3,1) + + a = jqt.Qarray.create(jnp.array([[1,2,],[4,5]]), bdims=(2,)) + assert a.shape == (2,2,1) + + a = jqt.Qarray.from_list([]) + assert a.dims == ((),()) and a.shape == jnp.array([]).shape + +def test_backward_compatibility_properties(): + """Test that properties work the same as the old implementation.""" + + a = jqt.Qarray.create(jnp.array([[1+1.0j,2,3],[4,5,6]])) + assert a.qtype == jqt.Qtypes.ket + assert a.dtype == jnp.array([1+1.0j]).dtype + assert a.bdims == (2,) + assert a.qdims == jqt.basis(3,1).qdims + + a_dag = a.dag() + assert a.space_dims == a.dims[0] + assert a_dag.space_dims == a_dag.dims[1] + + assert jnp.all(a.shaped_data == a.data.reshape(2,3,1)) + + assert a.is_batched == True + assert a[0] == jqt.Qarray.create(jnp.array([1+1.0j,2,3])) + + with pytest.raises(ValueError): + print(a[0][0]) + + a_reshaped = a.reshape_bdims(2,1) + assert a_reshaped.shape == (2,1,3,1) + + assert len(a) == 2 + with pytest.raises(ValueError): + print(len(a[0])) + + with pytest.raises(ValueError): + print(a == a.data) + + assert a != a[0] + assert a != jqt.Qarray.create(jnp.array([1,2])) + + with pytest.raises(ValueError): + print(a/a_dag) + + assert a/3 == 1/3*a + +def test_backward_compatibility_math(): + """Test that mathematical operations work the same as the old implementation.""" + + N = 3 + + a = jqt.displace(N,1.0) + b = jqt.displace(N,1.25) + c = jqt.displace(N,1.5) + + scalar = 1.23 + scalar_id_data = scalar*jnp.eye(N) + + arr = jqt.Qarray.from_list([a,b]) + + # Test basic operations + assert jnp.max(jnp.abs(((scalar+a).data) - (scalar_id_data + a.data))) < 1e-10 + assert jnp.max(jnp.abs(((a+scalar).data) - (a.data + scalar_id_data))) < 1e-10 + assert jnp.max(jnp.abs(((a+b).data) - (a.data + b.data))) < 1e-10 + assert jnp.max(jnp.abs((a * b).data - (a.data @ b.data))) < 1e-10 + assert jnp.max(jnp.abs((a ** 2).data - jnp.linalg.matrix_power(a.data, 2))) < 1e-10 + +def test_backward_compatibility_tensor(): + """Test that tensor operations work the same as the old implementation.""" + + N = 3 + a = jqt.displace(N,1.0) + b = jqt.displace(N,1.25) + c = jqt.displace(5,1.5) + + arr = jqt.Qarray.from_list([a,b]) + + # Test tensor products + assert jnp.max(jnp.abs((a ^ b).data - (jnp.kron(a.data,b.data))) < 1e-10) + assert jnp.max(jnp.abs((arr ^ b).data - (jnp.kron(arr.data,b.data))) < 1e-10) + +# ======================================== + +# Sparse-Specific Tests +# ======================================== +def test_sparse_creation_and_operations(): + """Test sparse-specific functionality.""" + + # Create a sparse matrix + dense_data = jnp.array([[1, 0, 2], [0, 3, 0], [4, 0, 5]]) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # Test that it's actually sparse + assert a_sparse.is_sparse + assert hasattr(a_sparse.data, 'todense') # BCOO has todense method + + # Test sparse operations + b_sparse = a_sparse + a_sparse + assert b_sparse.is_sparse + + c_sparse = a_sparse * 2 + assert c_sparse.is_sparse + + d_sparse = a_sparse @ a_sparse + assert d_sparse.is_sparse + + # Test that results are correct + assert jnp.allclose(b_sparse.data.todense(), dense_data * 2) + assert jnp.allclose(c_sparse.data.todense(), dense_data * 2) + assert jnp.allclose(d_sparse.data.todense(), dense_data @ dense_data) + +def test_sparse_fallback_operations(): + """Test operations that should fallback to dense for sparse.""" + + dense_data = jnp.array([[1, 0, 2], [0, 3, 0], [4, 0, 5]]) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # These operations should convert to dense automatically + exp_a = a_sparse.expm() + assert exp_a.is_dense # Should be converted to dense + + pow_a = a_sparse.powm(2) + assert pow_a.is_dense # Should be converted to dense + + sin_a = a_sparse.sinm() + assert sin_a.is_dense # Should be converted to dense + + cos_a = a_sparse.cosm() + assert cos_a.is_dense # Should be converted to dense + + # Test that results are correct + dense_a = a_sparse.to_dense() + assert jnp.allclose(exp_a.data, dense_a.expm().data) + assert jnp.allclose(pow_a.data, dense_a.powm(2).data) + +def test_sparse_eigenoperations(): + """Test eigenvalue operations with sparse matrices.""" + + # Create a Hermitian sparse matrix + dense_data = jnp.array([[2, 0, 1], [0, 3, 0], [1, 0, 2]]) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # Eigenvalue operations should convert to dense + evals_sparse = a_sparse.eigenenergies() + evals_dense = a_sparse.to_dense().eigenenergies() + + assert jnp.allclose(evals_sparse, evals_dense) + + # Eigenstates should also convert to dense + evals_sparse, evecs_sparse = a_sparse.eigenstates() + evals_dense, evecs_dense = a_sparse.to_dense().eigenstates() + + assert jnp.allclose(evals_sparse, evals_dense) + assert jnp.allclose(evecs_sparse.data, evecs_dense.data) + +def test_sparse_tensor_operations(): + """Test tensor operations with sparse matrices.""" + + dense_data1 = jnp.array([[1, 0], [0, 2]]) + dense_data2 = jnp.array([[3, 0, 0], [0, 4, 0], [0, 0, 5]]) + + a_sparse = jqt.Qarray.create(dense_data1, implementation="sparse") + b_sparse = jqt.Qarray.create(dense_data2, implementation="sparse") + + # Tensor product should convert to dense + tensor_result = a_sparse ^ b_sparse + assert tensor_result.is_dense + + # Compare with dense result + a_dense = a_sparse.to_dense() + b_dense = b_sparse.to_dense() + tensor_dense = a_dense ^ b_dense + + assert jnp.allclose(tensor_result.data, tensor_dense.data) + +def test_sparse_memory_efficiency(): + """Test that sparse matrices are actually more memory efficient for sparse data.""" + + # Create a large sparse matrix + size = 100 + dense_data = jnp.zeros((size, size)) + # Add some non-zero elements + for i in range(0, size, 10): + dense_data = dense_data.at[i, i].set(i) + if i + 1 < size: + dense_data = dense_data.at[i, i+1].set(1) + + a_dense = jqt.Qarray.create(dense_data) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # Both should give the same results + a_sparse_dense = a_sparse.data.todense() if hasattr(a_sparse.data, 'todense') else a_sparse.data + assert jnp.allclose(a_dense.data, a_sparse_dense) + + # Test that sparse operations work correctly + b_sparse = a_sparse + a_sparse + b_dense = a_dense + a_dense + + assert jnp.allclose(b_sparse.data.todense(), b_dense.data) + +# ======================================== + +# Error Handling Tests +# ======================================== +def test_error_handling(): + """Test error handling for invalid operations.""" + + a = jqt.Qarray.create(jnp.array([[1, 2], [3, 4]])) + b = jqt.Qarray.create(jnp.array([1, 2, 3])) # Different dimensions + + # Test dimension mismatch + with pytest.raises(ValueError): + a + b + + with pytest.raises(TypeError): # This now raises TypeError for dimension mismatch + a @ b + + # Test division by Qarray + with pytest.raises(ValueError): + a / a + + # Test indexing non-batched Qarray + c = jqt.Qarray.create(jnp.array([[1, 2], [3, 4]])) + with pytest.raises(ValueError): + print(c[0]) + +def test_sparse_error_handling(): + """Test error handling specific to sparse operations.""" + + # Test creating sparse from non-2D data + # This actually works now - BCOO can handle 1D data + sparse_1d = jqt.Qarray.create(jnp.array([1, 2, 3]), implementation=jqt.QarrayImplType.SPARSE) + assert sparse_1d.is_sparse + + # Test operations that should fail gracefully + dense_data = jnp.array([[1, 0], [0, 2]]) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # These should work (with fallback to dense if needed) + try: + result = a_sparse.expm() + assert result.is_dense # Should have fallen back to dense + except Exception as e: + pytest.fail(f"sparse expm should fallback to dense, but got: {e}") + +def test_sparse_new_operations(): + """Test the new sparse operations that stay sparse.""" + + # Create a complex sparse matrix + dense_data = jnp.array([[1+2j, 0, 3+4j], [0, 5+6j, 0], [7+8j, 0, 9+10j]]) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + a_dense = a_sparse.to_dense() + + # Test frobenius_norm + sparse_norm = a_sparse.frobenius_norm() + dense_norm = a_dense.frobenius_norm() + assert jnp.allclose(sparse_norm, dense_norm), f"Sparse norm {sparse_norm} != dense norm {dense_norm}" + + # Test real part + real_sparse = a_sparse.real() + real_dense = a_dense.real() + assert real_sparse.is_sparse, "Real part should stay sparse" + assert jnp.allclose(real_sparse.data.todense(), real_dense.data), "Real parts should match" + + # Test imaginary part + imag_sparse = a_sparse.imag() + imag_dense = a_dense.imag() + assert imag_sparse.is_sparse, "Imaginary part should stay sparse" + assert jnp.allclose(imag_sparse.data.todense(), imag_dense.data), "Imaginary parts should match" + + # Test conjugate + conj_sparse = a_sparse.conj() + conj_dense = a_dense.conj() + assert conj_sparse.is_sparse, "Conjugate should stay sparse" + assert jnp.allclose(conj_sparse.data.todense(), conj_dense.data), "Conjugates should match" + + + # Test dag (should now stay sparse) + dag_sparse = a_sparse.dag() + dag_dense = a_dense.dag() + assert dag_sparse.is_sparse, "Dag should stay sparse" + assert jnp.allclose(dag_sparse.data.todense(), dag_dense.data), "Dags should match" + +def test_sparse_dag_performance(): + """Test that sparse dag is more efficient than dense conversion.""" + + # Create a large sparse matrix + size = 50 + dense_data = jnp.zeros((size, size)) + # Make it sparse by setting only diagonal and a few off-diagonal elements + dense_data = dense_data.at[jnp.diag_indices(size)].set(jnp.arange(size) + 1j * jnp.arange(size)) + dense_data = dense_data.at[0, 1].set(1+2j) + dense_data = dense_data.at[1, 0].set(3+4j) + + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # Test that dag stays sparse + dag_sparse = a_sparse.dag() + assert dag_sparse.is_sparse, "Dag should stay sparse" + + # Verify correctness + dag_dense = a_sparse.to_dense().dag() + assert jnp.allclose(dag_sparse.data.todense(), dag_dense.data), "Sparse and dense dag should match" + +def test_sparse_batched_operations(): + """Test sparse operations with batched data.""" + + # Create batched data + batched_data = jnp.array([[[1+2j, 3+4j], [5+6j, 7+8j]], + [[9+10j, 11+12j], [13+14j, 15+16j]]]) + + sparse_batched = jqt.Qarray.create(batched_data, implementation=jqt.QarrayImplType.SPARSE) + dense_batched = sparse_batched.to_dense() + + # Test dag with batched data + sparse_dag = sparse_batched.dag() + dense_dag = dense_batched.dag() + + assert sparse_dag.is_sparse, "Batched dag should stay sparse" + assert sparse_dag.shape == dense_dag.shape, "Shapes should match" + assert jnp.allclose(sparse_dag.data.todense(), dense_dag.data), "Batched dag results should match" + + # Test that batch dimensions are preserved + assert sparse_dag.bdims == dense_dag.bdims, "Batch dimensions should be preserved in dag" + +# ======================================== + +# Performance Tests (Basic) +# ======================================== +def test_basic_performance(): + """Basic performance comparison between dense and sparse.""" + + # Create a moderately sparse matrix + size = 50 + dense_data = jnp.zeros((size, size)) + # Make it sparse (only diagonal and first off-diagonal) + for i in range(size): + dense_data = dense_data.at[i, i].set(i + 1) + if i + 1 < size: + dense_data = dense_data.at[i, i+1].set(1) + + a_dense = jqt.Qarray.create(dense_data) + a_sparse = jqt.Qarray.create(dense_data, implementation=jqt.QarrayImplType.SPARSE) + + # Test that both give the same results for basic operations + # Addition + result_dense_add = a_dense + a_dense + result_sparse_add = a_sparse + a_sparse + assert jnp.allclose(result_dense_add.data, result_sparse_add.data.todense()) + + # Multiplication + result_dense_mul = a_dense * 2 + result_sparse_mul = a_sparse * 2 + assert jnp.allclose(result_dense_mul.data, result_sparse_mul.data.todense()) + + # Matrix multiplication + result_dense_matmul = a_dense @ a_dense + result_sparse_matmul = a_sparse @ a_sparse + assert jnp.allclose(result_dense_matmul.data, result_sparse_matmul.data.todense()) + +# ========================================