├── constraints.txt ├── qiskit_dynamics ├── VERSION.txt ├── solvers │ ├── perturbative_solvers │ │ ├── __init__.py │ │ └── magnus_solver.py │ ├── jax_odeint.py │ ├── diffrax_solver.py │ ├── scipy_solve_ivp.py │ └── __init__.py ├── arraylias │ ├── register_functions │ │ ├── __init__.py │ │ ├── transpose.py │ │ ├── matmul.py │ │ ├── conjugate.py │ │ ├── multiply.py │ │ ├── rmatmul.py │ │ ├── asarray.py │ │ └── linear_combo.py │ └── __init__.py ├── __init__.py ├── systems │ ├── subsystem.py │ ├── subsystem_operators.py │ └── __init__.py ├── backend │ ├── __init__.py │ └── dynamics_job.py ├── perturbation │ ├── perturbation_data.py │ ├── __init__.py │ └── perturbation_utils.py ├── version.py ├── pulse │ └── __init__.py └── models │ ├── model_utils.py │ └── __init__.py ├── .gitattributes ├── docs ├── release_notes.rst ├── apidocs │ ├── pulse.rst │ ├── models.rst │ ├── backend.rst │ ├── signals.rst │ ├── solvers.rst │ ├── systems.rst │ ├── arraylias.rst │ ├── perturbation.rst │ └── index.rst ├── discussions │ └── index.rst ├── tutorials │ ├── index.rst │ └── systems_modelling.rst ├── userguide │ ├── index.rst │ └── how_to_use_pulse_schedule_for_jax_jit.rst ├── _templates │ └── autosummary │ │ ├── class_no_inherited_members.rst │ │ └── class.rst ├── index.rst ├── conf.py └── refs.bib ├── MANIFEST.in ├── .stestr.conf ├── releasenotes ├── config.yaml └── notes │ ├── drop-3.9-068213c3b17235bd.yaml │ ├── add-python-3.13-a36df1b35febc0b4.yaml │ ├── array-dispatch-removed-9f691a2f243ac6e5.yaml │ ├── fix-dynamics-backend-solve-bug-9feec2f51f62f9d3.yaml │ ├── fix-qiskit-ibm-runtime-d3ff79ecceda0a65.yaml │ ├── backend-num-qubits-700d8c03e08b324a.yaml │ ├── 0.6.0-f7b07109b632757b.yaml │ ├── qutip-qobj-handling-5ed79d8e4b5e96a7.yaml │ ├── 0.2 │ ├── 0.2.1-patch-eb5b64e5ea68d953.yaml │ └── 0.2-summary-734dd5eb5f550189.yaml │ ├── default_tspan_backend_solve-b1ad23f6ea291474.yaml │ ├── level1list-6564aec23c46bf7a.yaml │ ├── backend-hex-results-fd3762b9188cdd01.yaml │ ├── bound-qiskit-version-93a09ea5a3c4afbb.yaml │ ├── systems-module-227a233674a6100e.yaml │ ├── 0.5 │ └── 0.5.0-notes-5d846b13c228f5c3.yaml │ ├── 0.4 │ └── 0.4-summary-3de98711c3b7aa09.yaml │ └── 0.3 │ └── 0.3-summary-8bdd0273e78a9795.yaml ├── pyproject.toml ├── .github ├── ISSUE_TEMPLATE │ ├── documentation.md │ ├── FEATURE_REQUEST.md │ └── BUG_REPORT.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── release.yml │ ├── docs.yml │ ├── docs_stable.yml │ └── main.yml ├── CODE_OF_CONDUCT.md ├── CITATION.bib ├── test ├── __init__.py └── dynamics │ ├── __init__.py │ ├── solvers │ ├── __init__.py │ ├── test_lanczos.py │ ├── test_diffrax_DOP5.py │ └── test_jax_odeint.py │ ├── systems │ ├── __init__.py │ ├── test_subsystem.py │ └── test_subsystem_mapping.py │ ├── backend │ ├── __init__.py │ ├── test_dynamics_job.py │ └── test_qiskit_experiments.py │ ├── perturbation │ ├── __init__.py │ └── test_perturbation_data.py │ ├── pulse │ └── __init__.py │ ├── arraylias │ ├── __init__.py │ └── register_functions │ │ ├── test_matmul.py │ │ ├── test_multiply.py │ │ ├── test_asarray.py │ │ └── test_rmatmul.py │ ├── signals │ ├── __init__.py │ └── test_transfer_functions.py │ ├── models │ ├── __init__.py │ └── test_model_utils.py │ └── test_jax_transformations.py ├── requirements-dev.txt ├── tox.ini ├── .gitignore ├── setup.py └── README.md /constraints.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qiskit_dynamics/VERSION.txt: -------------------------------------------------------------------------------- 1 | 0.6.0 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-vendored 2 | -------------------------------------------------------------------------------- /docs/release_notes.rst: -------------------------------------------------------------------------------- 1 | .. release-notes:: Release Notes 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include qiskit_dynamics/VERSION.txt 2 | include README.md 3 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./test/dynamics 3 | top_dir=./test 4 | -------------------------------------------------------------------------------- /releasenotes/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | encoding: utf8 3 | default_branch: main 4 | -------------------------------------------------------------------------------- /releasenotes/notes/drop-3.9-068213c3b17235bd.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 3.9 has been dropped. -------------------------------------------------------------------------------- /releasenotes/notes/add-python-3.13-a36df1b35febc0b4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for Python 3.13 has been added. -------------------------------------------------------------------------------- /releasenotes/notes/array-dispatch-removed-9f691a2f243ac6e5.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | The ``array`` and ``dispatch`` submodules have been removed. -------------------------------------------------------------------------------- /docs/apidocs/pulse.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-pulse: 2 | 3 | .. automodule:: qiskit_dynamics.pulse 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/models.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-models: 2 | 3 | .. automodule:: qiskit_dynamics.models 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/backend.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-backend: 2 | 3 | .. automodule:: qiskit_dynamics.backend 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/signals.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-signals: 2 | 3 | .. automodule:: qiskit_dynamics.signals 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/solvers.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-solvers: 2 | 3 | .. automodule:: qiskit_dynamics.solvers 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/systems.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-systems: 2 | 3 | .. automodule:: qiskit_dynamics.systems 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /docs/apidocs/arraylias.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-arraylias: 2 | 3 | .. automodule:: qiskit_dynamics.arraylias 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-dynamics-backend-solve-bug-9feec2f51f62f9d3.yaml: -------------------------------------------------------------------------------- 1 | fixes: 2 | - | 3 | :meth:`.DynamicsBackend.solve` method has been fixed to allow `y0` values of type `np.ndarray`. -------------------------------------------------------------------------------- /docs/apidocs/perturbation.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit_dynamics-perturbation: 2 | 3 | .. automodule:: qiskit_dynamics.perturbation 4 | :no-members: 5 | :no-inherited-members: 6 | :no-special-members: 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.black] 6 | line-length = 100 7 | target-version = ['py39', 'py310', 'py311', 'py312'] 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: 'Create a report to help us improve the documentation ' 4 | title: '' 5 | labels: documentation 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-qiskit-ibm-runtime-d3ff79ecceda0a65.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | The dependency ``qiskit_ibm_runtime`` has been bounded to version ``0.36.1``. 5 | - | 6 | Qiskit Dynamics requires a version of Qiskit Experiments ``<0.9``. -------------------------------------------------------------------------------- /releasenotes/notes/backend-num-qubits-700d8c03e08b324a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Setting of the ``num_qubits`` ``Target`` attribute in ``DynamicsBackend`` initialization has 5 | been removed. Users will now need to set this at ``Target`` instantiation if needed. -------------------------------------------------------------------------------- /docs/discussions/index.rst: -------------------------------------------------------------------------------- 1 | #################### 2 | Dynamics Discussions 3 | #################### 4 | 5 | Discussions about topics and concepts related to the package. 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | 10 | dyson_magnus.rst 11 | 12 | 13 | .. Hiding - Indices and tables 14 | :ref:`genindex` 15 | :ref:`modindex` 16 | :ref:`search` 17 | -------------------------------------------------------------------------------- /docs/apidocs/index.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit-dynamics: 2 | 3 | .. module:: qiskit_dynamics 4 | 5 | ============================= 6 | Qiskit Dynamics API Reference 7 | ============================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | solvers 13 | backend 14 | models 15 | systems 16 | signals 17 | pulse 18 | perturbation 19 | arraylias 20 | -------------------------------------------------------------------------------- /releasenotes/notes/0.6.0-f7b07109b632757b.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | 4 | | Qiskit Dynamics ``0.6.0`` is the last release of the package for the foreseeable future. This 5 | release contains a series of bug fixes, as well as a new module :mod:`.systems` for building 6 | and operating on models of quantum systems. The readme has been modified to note that the 7 | package is no longer being maintained. -------------------------------------------------------------------------------- /releasenotes/notes/qutip-qobj-handling-5ed79d8e4b5e96a7.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | To enable compatibility with QuTiP 5.0.0, ``qutip.Qobj`` instances are now converted to a raw 5 | array type in Dynamics via the ``qutip.Qobj.full`` method. This method always returns a dense 6 | array, and hence ``qutip.Qobj`` instances are no longer turned into ``scipy.sparse.csr_matrix`` 7 | instances in Dynamics. -------------------------------------------------------------------------------- /releasenotes/notes/0.2/0.2.1-patch-eb5b64e5ea68d953.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Increased minimum jax and jaxlib versions for better sparse support, enabling 5 | reverse mode automatic differentiation of :class:`qiskit_dynamics.models.LindbladModel`. 6 | fixes: 7 | - | 8 | Fixes a bug where simulations could raise an error using JAX solvers for 9 | Statevector and DensityMatrix initial states. 10 | -------------------------------------------------------------------------------- /releasenotes/notes/default_tspan_backend_solve-b1ad23f6ea291474.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | ``DynamicsBackend.solve()`` method can now work without specifying a ``t_span`` argument. 5 | The default ``t_span`` is set to be ``[0, solve_input.duration]`` for each provided ``solve_input``. 6 | This allows users to solve the dynamics of a quantum circuit without having to specify its 7 | duration in advance. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/level1list-6564aec23c46bf7a.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | The data type of memory data produced by :class:`~.DynamicsBackend` for 5 | measurement level 1 was changed from a Numpy array to a list in order to 6 | match the type documented for the ``ExperimentResultData`` class in Qiskit. 7 | To get the old Numpy format back, it is sufficient to call ``numpy.array`` 8 | on the result data. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F680 Feature request" 3 | about: "Suggest an idea for this project \U0001F4A1!" 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | ### What is the expected behavior? 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Code of Conduct 4 | All members of this project agree to adhere to the Qiskit Code of Conduct listed at [https://github.com/Qiskit/qiskit/blob/master/CODE_OF_CONDUCT.md](https://github.com/Qiskit/qiskit/blob/master/CODE_OF_CONDUCT.md) 5 | 6 | ---- 7 | 8 | License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), 9 | Copyright Contributors to Qiskit. 10 | -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | ################## 2 | Dynamics Tutorials 3 | ################## 4 | 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | systems_modelling.rst 10 | Rabi_oscillations.rst 11 | Lindblad_dynamics_simulation.rst 12 | qiskit_pulse.rst 13 | optimizing_pulse_sequence.rst 14 | dynamics_backend.rst 15 | 16 | 17 | .. Hiding - Indices and tables 18 | :ref:`genindex` 19 | :ref:`modindex` 20 | :ref:`search` 21 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @article{qiskit_dynamics_2023, 2 | title = {Qiskit {Dynamics}: {A} {Python} package for simulating the time dynamics of quantum systems}, 3 | volume = {8}, 4 | issn = {2475-9066}, 5 | url = {https://joss.theoj.org/papers/10.21105/joss.05853}, 6 | doi = {10.21105/joss.05853}, 7 | number = {90}, 8 | journal = {Journal of Open Source Software}, 9 | author = {Puzzuoli, Daniel and Wood, Christopher J. and Egger, Daniel J. and Rosand, Benjamin and Ueda, Kento}, 10 | year = {2023}, 11 | pages = {5853} 12 | } -------------------------------------------------------------------------------- /docs/userguide/index.rst: -------------------------------------------------------------------------------- 1 | .. _qiskit-dynamics-userguide: 2 | 3 | .. module:: qiskit_dynamics_userguide 4 | 5 | ========================== 6 | Qiskit Dynamics User Guide 7 | ========================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | how_to_use_different_array_libraries.rst 13 | how_to_configure_simulations.rst 14 | advanced_system_modelling.rst 15 | perturbative_solvers.rst 16 | how_to_use_pulse_schedule_for_jax_jit.rst 17 | 18 | 19 | .. Hiding - Indices and tables 20 | :ref:`genindex` 21 | :ref:`modindex` 22 | :ref:`search` 23 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Qiskit ode tests 15 | """ 16 | -------------------------------------------------------------------------------- /releasenotes/notes/backend-hex-results-fd3762b9188cdd01.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | :class:`.DynamicsBackend` now returns results in hexadecimal. Previously, results were returned 5 | in ``n``-ary, where ``n`` is the value of the ``max_outcome_level`` option. The hexadecimal 6 | values are generated by calling ``hex(int(x, n))``, where ``n`` is ``max_outcome_level`` and 7 | ``x`` is the original ``n``-ary value. 8 | - | 9 | Corresponding to the above change, the default behaviour for ``max_outcome_level`` is now to 10 | treat a ``None`` value as ``1``. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### Summary 13 | 14 | 15 | 16 | ### Details and comments 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/dynamics/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Qiskit Dynamics tests 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/solvers/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | solvers tests 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/systems/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2025. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | systems tests 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Dynamics backend module tests. 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/perturbation/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Perturbation module tests. 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/pulse/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Pulse interfacing module tests. 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/arraylias/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Dynamics arraylias module tests. 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/signals/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2018, 2019, 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Tests for model modules 15 | """ 16 | -------------------------------------------------------------------------------- /test/dynamics/models/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2018, 2019, 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | Tests for converter functions 15 | """ 16 | -------------------------------------------------------------------------------- /releasenotes/notes/bound-qiskit-version-93a09ea5a3c4afbb.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | This version of Qiskit Dynamics requires ``qiskit <= 1.3``, as a result of the removal of 5 | ``qiskit.pulse`` in Qiskit 2.0. Due to the minimal dependance of Qiskit Dynamics on other parts 6 | of Qiskit, any workflow not utilizing ``qiskit.pulse`` should still be compatible with the 7 | latest version of Qiskit. 8 | - | 9 | In addition to the above, while Qiskit Dynamics does not formally depend on Qiskit Experiments, 10 | the :class:`.DynamicsBackend` will now only work with Qiskit Experiments `0.8` releases. -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | stestr>=3.0.0 2 | astroid==3.2.2 3 | pylint==3.2.3 4 | black~=24.1 5 | qiskit-sphinx-theme~=2.0 6 | sphinx-autodoc-typehints~=2.2.3 7 | jupyter-sphinx 8 | # The following line is needed until 9 | # https://github.com/jupyter/jupyter-sphinx/pull/226 is resolved. 10 | ipykernel>=4.5.1 11 | pygments>=2.4 12 | reno>=4.1.0 13 | nbsphinx~=0.9.5 14 | sphinx>=6.2.1,<8.2 15 | sphinxcontrib-bibtex 16 | qutip 17 | ddt~=1.4.2 18 | matplotlib>=3.3.0 19 | qiskit-experiments<0.9 20 | qiskit_ibm_runtime<=0.36.1 21 | pandas # for docs only 22 | pylatexenc>=1.4 # for docs only 23 | ipython<8.13.0 # for docs build with python 3.8 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: "Create a report to help us improve \U0001F914." 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | ### Informations 14 | 15 | - **Qiskit Dynamics version**: 16 | - **Python version**: 17 | - **Operating system**: 18 | 19 | ### What is the current behavior? 20 | 21 | 22 | 23 | ### Steps to reproduce the problem 24 | 25 | 26 | 27 | ### What is the expected behavior? 28 | 29 | 30 | 31 | ### Suggested solutions 32 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/perturbative_solvers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2022. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Subfolder for perturbation theory-based solvers. 17 | """ 18 | 19 | from .dyson_solver import DysonSolver 20 | from .magnus_solver import MagnusSolver 21 | -------------------------------------------------------------------------------- /releasenotes/notes/systems-module-227a233674a6100e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | This release introduces the new :mod:`.systems` module, containing tools for building abstract 5 | models of quantum systems. The module provides high-level interfaces for building models and 6 | performing non-trivial calculations, with the goal of minimizing the need for a user to 7 | explicitly build and work with arrays (an error-prone process). These interfaces sit at a level 8 | of abstraction above the :class:`.Solver` interface, though still utilize this underlying 9 | functionality. See the :ref:`Systems Modelling Tutorial ` and the 10 | :ref:`How-to use advanced system modelling functionality ` for 11 | detailed examples. 12 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Artifacts 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | wheel-build: 8 | name: Build and Publish Release Artifacts 9 | runs-on: ubuntu-latest 10 | environment: release 11 | permissions: 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v5 15 | - uses: actions/setup-python@v6 16 | name: Install Python 17 | with: 18 | python-version: '3.13' 19 | - name: Install Deps 20 | run: pip install -U wheel setuptools 21 | - name: Build Artifacts 22 | run: | 23 | python setup.py sdist 24 | python setup.py bdist_wheel 25 | shell: bash 26 | - uses: actions/upload-artifact@v4 27 | with: 28 | path: ./dist/qiskit* 29 | - name: Publish to PyPi 30 | uses: pypa/gh-action-pypi-publish@release/v1 -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | """ 15 | Register custom functions using alias 16 | """ 17 | 18 | from .asarray import register_asarray 19 | from .matmul import register_matmul 20 | from .rmatmul import register_rmatmul 21 | from .multiply import register_multiply 22 | from .linear_combo import register_linear_combo 23 | from .conjugate import register_conjugate 24 | from .transpose import register_transpose 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs Publish 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.11' 19 | - name: Install dependencies 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install -U virtualenv setuptools wheel tox 23 | sudo apt-get install graphviz pandoc 24 | - name: Build docs 25 | env: 26 | QISKIT_DOCS_BUILD_TUTORIALS: 'always' 27 | run: tox -edocs 28 | - name: Bypass Jekyll Processing # Necessary for setting the correct css path 29 | run: touch docs/_build/html/.nojekyll 30 | - name: Deploy 31 | uses: JamesIves/github-pages-deploy-action@v4 32 | with: 33 | folder: docs/_build/html/ 34 | clean-exclude: | 35 | stable/* 36 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/transpose.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Registering transpose. 17 | """ 18 | 19 | 20 | def register_transpose(alias): 21 | """Register linear functions for each array library.""" 22 | 23 | try: 24 | from jax.experimental.sparse import bcoo_transpose 25 | 26 | @alias.register_function(lib="jax_sparse", path="transpose") 27 | def _(arr, axes=None): 28 | return bcoo_transpose(arr, permutation=axes) 29 | 30 | except ImportError: 31 | pass 32 | -------------------------------------------------------------------------------- /docs/_templates/autosummary/class_no_inherited_members.rst: -------------------------------------------------------------------------------- 1 | {# 2 | This is very similar to the default class template, except this one is used 3 | when we don't want to generate any inherited methods. 4 | -#} 5 | 6 | {{ objname | escape | underline }} 7 | 8 | .. currentmodule:: {{ module }} 9 | 10 | .. autoclass:: {{ objname }} 11 | {#- 12 | Avoid having autodoc populate the class with the members we're about to 13 | summarize to avoid duplication. 14 | #} 15 | :no-members: 16 | :show-inheritance: 17 | :no-inherited-members: 18 | :no-special-members: 19 | 20 | {% block methods_summary %}{% set wanted_methods = (methods | reject('in', inherited_members) | reject('==', '__init__') | list) %}{% if wanted_methods %} 21 | .. rubric:: Methods Defined Here 22 | 23 | {% for item in wanted_methods %} 24 | .. automethod:: {{ name }}.{{ item }} 25 | {%- endfor %} 26 | {% endif %}{% endblock %} 27 | 28 | {% block attributes_summary %}{% if attributes %} 29 | .. rubric:: Attributes 30 | {# Attributes should all be summarized directly on the same page. -#} 31 | {% for item in attributes %} 32 | .. autoattribute:: {{ item }} 33 | {%- endfor %} 34 | {% endif %}{% endblock -%} 35 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/matmul.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Register matmul functions to alias. 17 | """ 18 | 19 | 20 | def register_matmul(alias): 21 | """Register matmul functions to required array libraries.""" 22 | 23 | @alias.register_function(lib="scipy_sparse", path="matmul") 24 | def _(x, y): 25 | return x * y 26 | 27 | try: 28 | from jax.experimental import sparse as jsparse 29 | import jax.numpy as jnp 30 | 31 | jsparse_matmul = jsparse.sparsify(jnp.matmul) 32 | 33 | @alias.register_function(lib="jax_sparse", path="matmul") 34 | def _(x, y): 35 | return jsparse_matmul(x, y) 36 | 37 | except ImportError: 38 | pass 39 | -------------------------------------------------------------------------------- /.github/workflows/docs_stable.yml: -------------------------------------------------------------------------------- 1 | name: Stable Docs Publish 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | deploy: 10 | if: github.repository_owner == 'Qiskit-Community' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Python 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.10' 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install -U virtualenv setuptools wheel tox 24 | sudo apt-get install graphviz pandoc 25 | - name: Build docs stable 26 | env: 27 | QISKIT_DOCS_BUILD_TUTORIALS: 'always' 28 | run: tox -e docs 29 | - name: Bypass Jekyll Processing # Necessary for setting the correct css path 30 | run: touch docs/_build/html/.nojekyll 31 | - name: Set current version 32 | run: | 33 | echo "version=$(git describe --abbrev=0 | cut -d'.' -f1,2)" >> "$GITHUB_ENV" 34 | - name: Deploy stable 35 | uses: JamesIves/github-pages-deploy-action@v4 36 | with: 37 | folder: docs/_build/html 38 | target-folder: stable/${{ env.version }} -------------------------------------------------------------------------------- /docs/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {# 2 | The general principle of this is that we manually document methods and attributes here in 3 | the same file. By default, we document 4 | all methods, including those defined by parent classes. 5 | -#} 6 | 7 | {{ objname | escape | underline }} 8 | 9 | .. currentmodule:: {{ module }} 10 | 11 | .. autoclass:: {{ objname }} 12 | {#- 13 | Avoid having autodoc populate the class with the members we're about to 14 | summarize to avoid duplication. 15 | #} 16 | :no-members: 17 | :show-inheritance: 18 | :no-inherited-members: 19 | :no-special-members: 20 | {# 21 | The documentation from `__init__` is automatically included in the 22 | standard class documentation, so we don't want to repeat it. 23 | -#} 24 | {% block methods_summary %}{% set wanted_methods = (methods | reject('==', '__init__') | list) %}{% if wanted_methods %} 25 | .. rubric:: Methods 26 | 27 | {% for item in wanted_methods %} 28 | .. automethod:: {{ name }}.{{ item }} 29 | {%- endfor %} 30 | {% endif %}{% endblock %} 31 | 32 | {% block attributes_summary %}{% if attributes %} 33 | .. rubric:: Attributes 34 | {# Attributes should all be summarized directly on the same page. -#} 35 | {% for item in attributes %} 36 | .. autoattribute:: {{ item }} 37 | {%- endfor %} 38 | {% endif %}{% endblock -%} 39 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/conjugate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Registering conjugate. 17 | """ 18 | 19 | 20 | def register_conjugate(alias): 21 | """Register linear functions for each array library.""" 22 | 23 | try: 24 | from jax.dtypes import canonicalize_dtype 25 | import jax.numpy as jnp 26 | from jax.experimental.sparse import sparsify 27 | 28 | # can be changed to sparsify(jnp.conjugate) when implemented 29 | def conj_workaround(x): 30 | if jnp.issubdtype(x.dtype, canonicalize_dtype(jnp.complex128)): 31 | return x.real - 1j * x.imag 32 | return x 33 | 34 | alias.register_function(func=sparsify(conj_workaround), lib="jax_sparse", path="conjugate") 35 | 36 | except ImportError: 37 | pass 38 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/multiply.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Register multiply functions to alias. 17 | """ 18 | 19 | 20 | def register_multiply(alias): 21 | """Register multiply functions to each array library.""" 22 | 23 | @alias.register_fallback(path="multiply") 24 | def _(x, y): 25 | return x * y 26 | 27 | @alias.register_function(lib="scipy_sparse", path="multiply") 28 | def _(x, y): 29 | return x.multiply(y) 30 | 31 | try: 32 | from jax.experimental import sparse as jsparse 33 | import jax.numpy as jnp 34 | 35 | jsparse_multiply = jsparse.sparsify(jnp.multiply) 36 | 37 | @alias.register_function(lib="jax_sparse", path="multiply") 38 | def _(x, y): 39 | return jsparse_multiply(x, y) 40 | 41 | except ImportError: 42 | pass 43 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.3.0 3 | envlist = py39,py310,py311,py312,lint 4 | isolated_build = true 5 | 6 | [testenv] 7 | usedevelop = True 8 | install_command = pip install -c{toxinidir}/constraints.txt -U {opts} {packages} 9 | setenv = 10 | VIRTUAL_ENV={envdir} 11 | QISKIT_SUPPRESS_PACKAGING_WARNINGS=Y 12 | deps = -r{toxinidir}/requirements-dev.txt 13 | commands = stestr run {posargs} 14 | 15 | [testenv:jax] 16 | deps = 17 | -r{toxinidir}/requirements-dev.txt 18 | jax 19 | jaxlib 20 | diffrax 21 | 22 | [testenv:lint] 23 | deps = 24 | -r{toxinidir}/requirements-dev.txt 25 | jax 26 | jaxlib 27 | diffrax 28 | commands = 29 | black --check {posargs} qiskit_dynamics test 30 | pylint -rn -j 0 --rcfile={toxinidir}/.pylintrc qiskit_dynamics/ test/ 31 | 32 | [testenv:black] 33 | deps = black 34 | commands = black {posargs} qiskit_dynamics test 35 | 36 | 37 | [testenv:docs] 38 | # Editable mode breaks macOS: https://github.com/sphinx-doc/sphinx/issues/10943 39 | usedevelop = False 40 | deps = 41 | -r{toxinidir}/requirements-dev.txt 42 | jax 43 | jaxlib 44 | diffrax 45 | setenv = 46 | PYDEVD_DISABLE_FILE_VALIDATION = 1 47 | commands = 48 | sphinx-build -W -T --keep-going {posargs} docs/ docs/_build/html 49 | 50 | [testenv:docs-clean] 51 | skip_install = true 52 | deps = 53 | allowlist_externals = rm 54 | commands = rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build 55 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ############################# 2 | Qiskit Dynamics documentation 3 | ############################# 4 | 5 | Qiskit Dynamics is an open-source project for building, transforming, and solving 6 | models of quantum systems in Qiskit. 7 | 8 | The goal of Qiskit Dynamics is to provide access to different numerical 9 | methods, and to automate common processes typically performed by hand, 10 | e.g. entering rotating frames, or doing the rotating wave approximation. 11 | 12 | Qiskit Dynamics can be configured to use either 13 | `NumPy `_ or `JAX `_ 14 | as the backend for array operations. `NumPy `_ is the default, 15 | and `JAX `_ is an optional dependency, which enables 16 | just-in-time compilation, automatic differentiation, and GPU execution of Qiskit Dynamics code. 17 | 18 | .. warning:: 19 | 20 | This package is still in the early stages of development and it is very likely 21 | that there will be breaking API changes in future releases. 22 | If you encounter any bugs please open an issue on 23 | `Github `_ 24 | 25 | 26 | .. toctree:: 27 | :maxdepth: 1 28 | 29 | Tutorials 30 | User Guide 31 | API References 32 | Discussions 33 | Release Notes 34 | 35 | .. Hiding - Indices and tables 36 | :ref:`genindex` 37 | :ref:`modindex` 38 | :ref:`search` 39 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/rmatmul.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | 16 | """ 17 | Register rmatmul functions to alias. 18 | """ 19 | 20 | import numpy as np 21 | 22 | 23 | def register_rmatmul(alias): 24 | """Register rmatmul functions to each array library.""" 25 | 26 | @alias.register_function(lib="numpy", path="rmatmul") 27 | def _(x, y): 28 | return np.matmul(y, x) 29 | 30 | @alias.register_function(lib="scipy_sparse", path="rmatmul") 31 | def _(x, y): 32 | return y * x 33 | 34 | try: 35 | from jax.experimental import sparse as jsparse 36 | import jax.numpy as jnp 37 | 38 | jsparse_matmul = jsparse.sparsify(jnp.matmul) 39 | 40 | @alias.register_function(lib="jax", path="rmatmul") 41 | def _(x, y): 42 | return jnp.matmul(y, x) 43 | 44 | @alias.register_function(lib="jax_sparse", path="rmatmul") 45 | def _(x, y): 46 | return jsparse_matmul(y, x) 47 | 48 | except ImportError: 49 | pass 50 | -------------------------------------------------------------------------------- /test/dynamics/systems/test_subsystem.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """Tests for subsystem.py.""" 15 | 16 | from qiskit_dynamics.systems import Subsystem 17 | from ..common import QiskitDynamicsTestCase 18 | 19 | 20 | class TestSubsystem(QiskitDynamicsTestCase): 21 | """Tests for Subsystem class.""" 22 | 23 | def test_properties(self): 24 | """Test basic properties.""" 25 | s = Subsystem(name="Q0", dim=3) 26 | self.assertEqual(s.name, "Q0") 27 | self.assertEqual(s.dim, 3) 28 | 29 | def test_string_representations(self): 30 | """Test string representations.""" 31 | s = Subsystem(name="Q0", dim=3) 32 | self.assertEqual(str(s), "Q0") 33 | self.assertEqual(repr(s), "Subsystem(name=Q0, dim=3)") 34 | 35 | def test_equality(self): 36 | """Test equality check.""" 37 | s0 = Subsystem(name="Q0", dim=3) 38 | s1 = Subsystem(name="Q0", dim=3) 39 | s2 = Subsystem(name="Q1", dim=3) 40 | self.assertNotEqual(s0, s2) 41 | self.assertEqual(s0, s1) 42 | -------------------------------------------------------------------------------- /qiskit_dynamics/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | ========================================== 17 | Qiskit Dynamics (:mod:`qiskit_dynamics`) 18 | ========================================== 19 | 20 | .. currentmodule:: qiskit_dynamics 21 | 22 | Qiskit extension module for simulating quantum dynamics. 23 | """ 24 | from .version import __version__ 25 | 26 | from .arraylias.alias import ( 27 | DYNAMICS_NUMPY_ALIAS, 28 | DYNAMICS_SCIPY_ALIAS, 29 | DYNAMICS_NUMPY, 30 | DYNAMICS_SCIPY, 31 | ArrayLike, 32 | ) 33 | 34 | from .models.rotating_frame import RotatingFrame 35 | 36 | from .signals.signals import Signal, DiscreteSignal 37 | 38 | from .solvers.solver_functions import solve_ode, solve_lmde 39 | from .solvers.solver_classes import Solver 40 | from .solvers.perturbative_solvers.dyson_solver import DysonSolver 41 | from .solvers.perturbative_solvers.magnus_solver import MagnusSolver 42 | from .backend.dynamics_backend import DynamicsBackend 43 | 44 | from . import models 45 | from . import signals 46 | from . import pulse 47 | from . import backend 48 | -------------------------------------------------------------------------------- /qiskit_dynamics/systems/subsystem.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2025. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Subsystem class 16 | """ 17 | 18 | 19 | class Subsystem: 20 | """A Hilbert space with a name and a dimension.""" 21 | 22 | def __init__(self, name: str, dim: int): 23 | """Initialize with name and dimension. 24 | 25 | Args: 26 | name: Name of the subsystem. 27 | dim: Dimension of the subsystem. 28 | """ 29 | self._name = name 30 | self._dim = dim 31 | 32 | @property 33 | def name(self) -> str: 34 | """Name of subsystem.""" 35 | return self._name 36 | 37 | @property 38 | def dim(self) -> int: 39 | """Dimension of subsystem.""" 40 | return self._dim 41 | 42 | def __str__(self) -> str: 43 | return self.name 44 | 45 | def __repr__(self) -> str: 46 | return f"Subsystem(name={self.name}, dim={self.dim})" 47 | 48 | def __eq__(self, other: "Subsystem") -> bool: 49 | if not isinstance(other, Subsystem): 50 | return False 51 | return self.name == other.name 52 | -------------------------------------------------------------------------------- /test/dynamics/arraylias/register_functions/test_matmul.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Test matmul functions 17 | """ 18 | import unittest 19 | import numpy as np 20 | from scipy.sparse import csr_matrix 21 | 22 | from qiskit_dynamics import DYNAMICS_NUMPY as unp 23 | from ...common import QiskitDynamicsTestCase 24 | 25 | 26 | class TestMatmulFunction(QiskitDynamicsTestCase): 27 | """Test cases for matmul functions registered in dynamics_numpy_alias.""" 28 | 29 | def test_scipy_sparse(self): 30 | """Test matmul for scipy_sparse.""" 31 | x = csr_matrix([[1, 0], [0, 1]]) 32 | y = csr_matrix([[2, 2], [2, 2]]) 33 | self.assertTrue(isinstance(unp.matmul(x, y), csr_matrix)) 34 | self.assertAllClose(csr_matrix.toarray(unp.matmul(x, y)), [[2, 2], [2, 2]]) 35 | 36 | def test_jax_sparse(self): 37 | """Test matmul for jax_sparse.""" 38 | try: 39 | from jax.experimental.sparse import BCOO 40 | 41 | x = BCOO.fromdense([[1, 0], [0, 1]]) 42 | y = BCOO.fromdense([[2, 2], [2, 2]]) 43 | self.assertTrue(isinstance(unp.matmul(x, y), BCOO)) 44 | self.assertAllClose(BCOO.todense(unp.matmul(x, y)), [[2, 2], [2, 2]]) 45 | except ImportError as err: 46 | raise unittest.SkipTest("Skipping jax tests.") from err 47 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/asarray.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Registering asarray functions to alias 17 | """ 18 | 19 | from typing import Iterable 20 | 21 | import numpy as np 22 | from scipy.sparse import csr_matrix, issparse 23 | 24 | 25 | def _isinstance_qutip_qobj(obj): 26 | """Check if the object is a qutip Qobj. 27 | Args: 28 | obj (any): Any object for testing. 29 | Returns: 30 | Bool: True if obj is qutip Qobj 31 | """ 32 | return type(obj).__name__ == "Qobj" 33 | 34 | 35 | def register_asarray(alias): 36 | """register asarray functions to each array libraries""" 37 | 38 | @alias.register_default(path="asarray") 39 | def _(arr): 40 | if _isinstance_qutip_qobj(arr): 41 | return arr.full() 42 | return np.asarray(arr) 43 | 44 | @alias.register_function(lib="scipy_sparse", path="asarray") 45 | def _(arr): 46 | if issparse(arr) or (isinstance(arr, Iterable) and issparse(arr[0])): 47 | return arr 48 | return csr_matrix(arr) 49 | 50 | try: 51 | from jax.experimental.sparse import BCOO 52 | 53 | @alias.register_function(lib="jax_sparse", path="asarray") 54 | def _(arr): 55 | if type(arr).__name__ == "BCOO": 56 | return arr 57 | return BCOO.fromdense(arr) 58 | 59 | except ImportError: 60 | pass 61 | -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/register_functions/linear_combo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Registering linear_combo functions to alias. This computes a linear combination of matrices (given 17 | by a 3d array). 18 | """ 19 | 20 | import numpy as np 21 | 22 | 23 | def register_linear_combo(alias): 24 | """Register linear functions for each array library.""" 25 | 26 | @alias.register_default(path="linear_combo") 27 | def _(coeffs, mats): 28 | return np.tensordot(coeffs, mats, axes=1) 29 | 30 | @alias.register_function(lib="numpy", path="linear_combo") 31 | def _(coeffs, mats): 32 | return np.tensordot(coeffs, mats, axes=1) 33 | 34 | try: 35 | import jax.numpy as jnp 36 | 37 | @alias.register_function(lib="jax", path="linear_combo") 38 | def _(coeffs, mats): 39 | # real and imag broken up to avoid real/complex tensordot warning 40 | return jnp.tensordot(coeffs, mats.real, axes=1) + 1j * jnp.tensordot( 41 | coeffs, mats.imag, axes=1 42 | ) 43 | 44 | from jax.experimental.sparse import sparsify 45 | 46 | jsparse_sum = sparsify(jnp.sum) 47 | 48 | @alias.register_function(lib="jax_sparse", path="linear_combo") 49 | def _(coeffs, mats): 50 | # pylint: disable=unexpected-keyword-arg 51 | return jsparse_sum(jnp.broadcast_to(coeffs[:, None, None], mats.shape) * mats, axis=0) 52 | 53 | except ImportError: 54 | pass 55 | -------------------------------------------------------------------------------- /qiskit_dynamics/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2022. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | r""" 16 | ======================================== 17 | Backend (:mod:`qiskit_dynamics.backend`) 18 | ======================================== 19 | 20 | .. currentmodule:: qiskit_dynamics.backend 21 | 22 | This module contains the :class:`.DynamicsBackend` class, which provides a 23 | :class:`~qiskit.providers.backend.BackendV2` interface for running pulse-level simulations with 24 | Qiskit Dynamics. The :class:`.DynamicsBackend` can directly simulate :class:`~qiskit.pulse.Schedule` 25 | and :class:`~qiskit.pulse.ScheduleBlock` instances, and can also be configured to simulate 26 | :class:`~qiskit.circuit.QuantumCircuit`\s at the pulse-level via circuit to pulse transpilation. 27 | 28 | This module also exposes some functions utilized by :class:`.DynamicsBackend` that may be of use to 29 | experienced users. The function :func:`.default_experiment_result_function` is the default method by 30 | which results are computed and returned to the user after the underlying differential equation is 31 | solved. This function can be overridden with a custom user-defined function by setting the 32 | ``experiment_result_function`` option of :class:`.DynamicsBackend`. 33 | 34 | 35 | Classes and functions 36 | ===================== 37 | 38 | .. autosummary:: 39 | :toctree: ../stubs/ 40 | 41 | DynamicsBackend 42 | default_experiment_result_function 43 | """ 44 | 45 | from .dynamics_backend import DynamicsBackend, default_experiment_result_function 46 | -------------------------------------------------------------------------------- /test/dynamics/arraylias/register_functions/test_multiply.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Test multiply functions 17 | """ 18 | import unittest 19 | import numpy as np 20 | from scipy.sparse import csr_matrix 21 | 22 | from qiskit_dynamics import DYNAMICS_NUMPY as unp 23 | from ...common import QiskitDynamicsTestCase 24 | 25 | 26 | class TestMultiplyFunction(QiskitDynamicsTestCase): 27 | """Test cases for multiply functions registered in dynamics_numpy_alias.""" 28 | 29 | def test_register_fallback(self): 30 | """Test register_fallback.""" 31 | x = np.array([1, 0]) 32 | y = np.array([1, 0]) 33 | self.assertTrue(isinstance(unp.multiply(x, y), np.ndarray)) 34 | self.assertAllClose(unp.multiply(x, y), [1, 0]) 35 | 36 | def test_scipy_sparse(self): 37 | """Test multiply for scipy_sparse.""" 38 | x = csr_matrix([[1, 0], [0, 1]]) 39 | y = csr_matrix([[2, 2], [2, 2]]) 40 | self.assertTrue(isinstance(unp.multiply(x, y), csr_matrix)) 41 | self.assertAllClose(csr_matrix.toarray(unp.multiply(x, y)), [[2, 0], [0, 2]]) 42 | 43 | def test_jax_sparse(self): 44 | """Test multiply for jax_sparse.""" 45 | try: 46 | from jax.experimental.sparse import BCOO 47 | 48 | x = BCOO.fromdense([[1, 0], [0, 1]]) 49 | y = BCOO.fromdense([[2, 2], [2, 2]]) 50 | self.assertTrue(isinstance(unp.multiply(x, y), BCOO)) 51 | self.assertAllClose(BCOO.todense(unp.multiply(x, y)), [[2, 0], [0, 2]]) 52 | except ImportError as err: 53 | raise unittest.SkipTest("Skipping jax tests.") from err 54 | -------------------------------------------------------------------------------- /test/dynamics/arraylias/register_functions/test_asarray.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Test asarray functions 17 | """ 18 | import unittest 19 | import numpy as np 20 | from scipy.sparse import csr_matrix 21 | from qiskit.quantum_info.operators import Operator 22 | 23 | 24 | from qiskit_dynamics import DYNAMICS_NUMPY_ALIAS 25 | from qiskit_dynamics import DYNAMICS_NUMPY as unp 26 | 27 | 28 | class TestAsarrayFunction(unittest.TestCase): 29 | """Test cases for asarray functions registered in dynamics_numpy_alias.""" 30 | 31 | def test_register_default(self): 32 | """Test register_default.""" 33 | arr = Operator.from_label("X") 34 | self.assertTrue(isinstance(unp.asarray(arr), np.ndarray)) 35 | 36 | def test_scipy_sparse(self): 37 | """Test asarray for scipy_sparse.""" 38 | arr = np.array([[1, 0], [0, 1]]) 39 | sparse_arr = csr_matrix([[1, 0], [0, 1]]) 40 | self.assertTrue(isinstance(unp.asarray(sparse_arr), csr_matrix)) 41 | self.assertTrue(isinstance(DYNAMICS_NUMPY_ALIAS(like=sparse_arr).asarray(arr), csr_matrix)) 42 | 43 | def test_jax_sparse(self): 44 | """Test asarray for jax_sparse.""" 45 | try: 46 | from jax.experimental.sparse import BCOO 47 | 48 | arr = np.array([[1, 0], [0, 1]]) 49 | sparse_arr = BCOO.fromdense([[1, 0], [0, 1]]) 50 | self.assertTrue(isinstance(unp.asarray(sparse_arr), BCOO)) 51 | self.assertTrue(isinstance(DYNAMICS_NUMPY_ALIAS(like=sparse_arr).asarray(arr), BCOO)) 52 | except ImportError as err: 53 | raise unittest.SkipTest("Skipping jax tests.") from err 54 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/jax_odeint.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | # pylint: disable=invalid-name 15 | 16 | """ 17 | Wrapper for jax.experimental.ode.odeint 18 | """ 19 | 20 | from typing import Callable, Optional 21 | from scipy.integrate._ivp.ivp import OdeResult 22 | 23 | from qiskit_dynamics import DYNAMICS_NUMPY as unp 24 | from qiskit_dynamics.arraylias import ArrayLike, requires_array_library 25 | 26 | from .solver_utils import merge_t_args_jax, trim_t_results_jax 27 | 28 | try: 29 | from jax.experimental.ode import odeint 30 | except ImportError: 31 | pass 32 | 33 | 34 | @requires_array_library("jax") 35 | def jax_odeint( 36 | rhs: Callable, 37 | t_span: ArrayLike, 38 | y0: ArrayLike, 39 | t_eval: Optional[ArrayLike] = None, 40 | **kwargs, 41 | ): 42 | """Routine for calling `jax.experimental.ode.odeint` 43 | 44 | Args: 45 | rhs: Callable of the form :math:`f(t, y)` 46 | t_span: Interval to solve over. 47 | y0: Initial state. 48 | t_eval: Optional list of time points at which to return the solution. 49 | **kwargs: Optional arguments to be passed to ``odeint``. 50 | 51 | Returns: 52 | OdeResult: Results object. 53 | """ 54 | 55 | t_list = merge_t_args_jax(t_span, t_eval) 56 | 57 | # determine direction of integration 58 | t_direction = unp.sign(unp.asarray(t_list[-1] - t_list[0], dtype=complex)) 59 | 60 | results = odeint( 61 | lambda y, t: rhs(unp.real(t_direction * t), y) * t_direction, 62 | y0=unp.asarray(y0, dtype=complex), 63 | t=unp.real(t_direction) * unp.asarray(t_list), 64 | **kwargs, 65 | ) 66 | 67 | results = OdeResult(t=t_list, y=results) 68 | 69 | return trim_t_results_jax(results, t_eval) 70 | -------------------------------------------------------------------------------- /qiskit_dynamics/perturbation/perturbation_data.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | r""" 14 | Class for storing results of perturbation theory computations. 15 | """ 16 | 17 | from typing import List, Mapping, Optional 18 | from dataclasses import dataclass 19 | 20 | from multiset import Multiset 21 | 22 | from qiskit import QiskitError 23 | 24 | 25 | @dataclass 26 | class _LabeledData: 27 | """Container for arbitrarily "labeled data", i.e. data whose indices are arbitrary python 28 | objects. The method ``get_item`` looks up an item according to the label. 29 | """ 30 | 31 | data: Mapping[int, any] 32 | labels: List[any] 33 | metadata: Optional[any] = None 34 | 35 | def get_item(self, label: any) -> any: 36 | """Look up an item in self.data according to the location of label in self.labels.""" 37 | label = self._preprocess_label(label) 38 | 39 | if label in self.labels: 40 | return self.data[self.labels.index(label)] 41 | 42 | raise QiskitError("label is not present in self.labels.") 43 | 44 | def _preprocess_label(self, label: any) -> any: 45 | return label 46 | 47 | 48 | class PowerSeriesData(_LabeledData): 49 | """Storage container for power series data. Labels are assumed to be ``Multiset`` instances, and 50 | data is assumed to be a dense ``ArrayLike``. 51 | """ 52 | 53 | def _preprocess_label(self, label: Multiset) -> Multiset: 54 | """Cast to a Multiset.""" 55 | return Multiset(label) 56 | 57 | 58 | class DysonLikeData(_LabeledData): 59 | """Storage container for DysonLike series data. Labels are assumed to be lists of ints, and data 60 | is assumed to be a dense ``ArrayLike``. 61 | """ 62 | 63 | def _preprocess_label(self, label: list) -> list: 64 | """Cast to a list.""" 65 | return list(label) 66 | -------------------------------------------------------------------------------- /test/dynamics/models/test_model_utils.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2024. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """Tests for model_utils.py.""" 15 | 16 | import numpy as np 17 | from scipy.sparse import csr_matrix 18 | 19 | from qiskit_dynamics.models.model_utils import ( 20 | vec_dissipator, 21 | vec_commutator, 22 | ) 23 | 24 | from ..common import QiskitDynamicsTestCase 25 | 26 | 27 | class Testvec_commutator_dissipator(QiskitDynamicsTestCase): 28 | """Tests for vec_commutator and vec_dissipator.""" 29 | 30 | def test_sparse_commutator_dissipator(self): 31 | """Tests that vec_commutator and vec_dissipator gives 32 | identical results, whether the array passed is a (k,n,n) 33 | Array or a (k,) Array of (n,n) sparse matrices.""" 34 | np.random.seed(21301239) 35 | 36 | def r(*args): 37 | return np.random.uniform(-1, 1, args) 38 | 39 | spm = csr_matrix(r(8, 8)) 40 | self.assertAllClose(vec_commutator(spm).toarray(), vec_commutator(spm.toarray())) 41 | multi_matrix = r(3, 8, 8) 42 | den_commutator = vec_commutator(multi_matrix) 43 | sps_commutator = vec_commutator([csr_matrix(mat) for mat in multi_matrix]) 44 | self.assertTrue( 45 | np.all( 46 | [ 47 | np.allclose(den_com, sps_com.toarray()) 48 | for den_com, sps_com in zip(den_commutator, sps_commutator) 49 | ] 50 | ) 51 | ) 52 | 53 | den_dissipator = vec_dissipator(multi_matrix) 54 | sps_dissipator = vec_dissipator([csr_matrix(mat) for mat in multi_matrix]) 55 | self.assertTrue( 56 | np.all( 57 | [ 58 | np.allclose(den_dis, sps_dis.toarray()) 59 | for den_dis, sps_dis in zip(den_dissipator, sps_dissipator) 60 | ] 61 | ) 62 | ) 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | .stestr/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | docs/stubs/* 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # System files 134 | *.DS_Store 135 | *.ini 136 | -------------------------------------------------------------------------------- /test/dynamics/arraylias/register_functions/test_rmatmul.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | Test rmatmul functions 17 | """ 18 | import unittest 19 | import numpy as np 20 | from scipy.sparse import csr_matrix 21 | 22 | from qiskit_dynamics import DYNAMICS_NUMPY as unp 23 | from ...common import QiskitDynamicsTestCase 24 | 25 | 26 | class TestRmatmulFunction(QiskitDynamicsTestCase): 27 | """Test cases for rmatmul functions registered in dynamics_numpy_alias.""" 28 | 29 | def test_numpy(self): 30 | """Test rmatmul for numpy.""" 31 | x = np.array([[1, 1], [1, 1]]) 32 | y = np.array([[1, 2], [3, 4]]) 33 | self.assertTrue(isinstance(unp.rmatmul(x, y), np.ndarray)) 34 | self.assertAllClose(unp.rmatmul(x, y), [[3, 3], [7, 7]]) 35 | 36 | def test_scipy_sparse(self): 37 | """Test rmatmul for scipy_sparse.""" 38 | x = csr_matrix([[1, 1], [1, 1]]) 39 | y = csr_matrix([[1, 2], [3, 4]]) 40 | self.assertTrue(isinstance(unp.rmatmul(x, y), csr_matrix)) 41 | self.assertAllClose(csr_matrix.toarray(unp.rmatmul(x, y)), [[3, 3], [7, 7]]) 42 | 43 | def test_jax(self): 44 | """Test rmatmul for jax.""" 45 | try: 46 | import jax.numpy as jnp 47 | 48 | x = jnp.array([[1, 1], [1, 1]]) 49 | y = jnp.array([[1, 2], [3, 4]]) 50 | self.assertTrue(isinstance(unp.rmatmul(x, y), jnp.ndarray)) 51 | self.assertAllClose(unp.rmatmul(x, y), [[3, 3], [7, 7]]) 52 | except ImportError as err: 53 | raise unittest.SkipTest("Skipping jax tests.") from err 54 | 55 | def test_jax_sparse(self): 56 | """Test rmatmul for jax_sparse.""" 57 | try: 58 | from jax.experimental.sparse import BCOO 59 | 60 | x = BCOO.fromdense([[1, 1], [1, 1]]) 61 | y = BCOO.fromdense([[1, 2], [3, 4]]) 62 | self.assertTrue(isinstance(unp.rmatmul(x, y), BCOO)) 63 | self.assertAllClose(BCOO.todense(unp.rmatmul(x, y)), [[3, 3], [7, 7]]) 64 | except ImportError as err: 65 | raise unittest.SkipTest("Skipping jax tests.") from err 66 | -------------------------------------------------------------------------------- /test/dynamics/perturbation/test_perturbation_data.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """Tests for perturbation_results.py""" 14 | 15 | import numpy as np 16 | 17 | from qiskit import QiskitError 18 | 19 | from multiset import Multiset 20 | 21 | from qiskit_dynamics.perturbation.perturbation_data import PowerSeriesData, DysonLikeData 22 | 23 | from ..common import QiskitDynamicsTestCase 24 | 25 | 26 | class TestDysonLikeData(QiskitDynamicsTestCase): 27 | """Test DysonLikeData.""" 28 | 29 | def test_get_item(self): 30 | """Test that get_term works.""" 31 | results = DysonLikeData(data=np.array([5, 6, 7]), labels=[[0], [1], [0, 1]]) 32 | self.assertTrue(results.get_item([1]) == 6) 33 | 34 | def test_get_item_error(self): 35 | """Test an error gets raised when a requested term doesn't exist.""" 36 | results = DysonLikeData(data=np.array([5, 6, 7]), labels=[[0], [1], [0, 1]]) 37 | with self.assertRaises(QiskitError): 38 | # pylint: disable=pointless-statement 39 | results.get_item([2]) 40 | 41 | 42 | class TestPowerSeriesData(QiskitDynamicsTestCase): 43 | """Test PowerSeriesData.""" 44 | 45 | def test_get_item(self): 46 | """Test that get_term works.""" 47 | results = PowerSeriesData( 48 | data=np.array([5, 6, 7]), labels=[Multiset([0]), Multiset([1]), Multiset([0, 1])] 49 | ) 50 | self.assertTrue(results.get_item(Multiset([1])) == np.array(6)) 51 | 52 | def test_automatic_casting(self): 53 | """Test that get_item works with automatic casting to Multiset.""" 54 | results = PowerSeriesData( 55 | data=np.array([5, 6, 7]), labels=[Multiset([0]), Multiset([1]), Multiset([0, 1])] 56 | ) 57 | self.assertTrue(results.get_item([1, 0]) == np.array(7)) 58 | 59 | def test_get_item_error(self): 60 | """Test an error gets raised when a requested term doesn't exist.""" 61 | results = PowerSeriesData( 62 | data=np.array([5, 6, 7]), labels=[Multiset([0]), Multiset([1]), Multiset([0, 1])] 63 | ) 64 | with self.assertRaises(QiskitError): 65 | # pylint: disable=pointless-statement 66 | results.get_item([2]) 67 | -------------------------------------------------------------------------------- /qiskit_dynamics/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """Functions for getting version information about aqt provider.""" 16 | 17 | import os 18 | import subprocess 19 | 20 | ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) 21 | 22 | 23 | def _minimal_ext_cmd(cmd): 24 | # construct minimal environment 25 | env = {} 26 | for k in ["SYSTEMROOT", "PATH"]: 27 | v = os.environ.get(k) 28 | if v is not None: 29 | env[k] = v 30 | # LANGUAGE is used on win32 31 | env["LANGUAGE"] = "C" 32 | env["LANG"] = "C" 33 | env["LC_ALL"] = "C" 34 | with subprocess.Popen( 35 | cmd, 36 | stdout=subprocess.PIPE, 37 | stderr=subprocess.PIPE, 38 | env=env, 39 | cwd=os.path.join(os.path.dirname(ROOT_DIR)), 40 | ) as proc: 41 | stdout, stderr = proc.communicate() 42 | if proc.returncode > 0: 43 | raise OSError( 44 | f"Command {cmd} exited with code {proc.returncode}: {stderr.strip().decode('ascii')}" 45 | ) 46 | return stdout 47 | 48 | 49 | def git_version(): 50 | """Get the current git head sha1.""" 51 | # Determine if we're at master 52 | try: 53 | out = _minimal_ext_cmd(["git", "rev-parse", "HEAD"]) 54 | git_revision = out.strip().decode("ascii") 55 | except OSError: 56 | git_revision = "Unknown" 57 | 58 | return git_revision 59 | 60 | 61 | with open(os.path.join(ROOT_DIR, "VERSION.txt"), "r", encoding="utf-8") as version_file: 62 | VERSION = version_file.read().strip() 63 | 64 | 65 | def get_version_info(): 66 | """Get the full version string.""" 67 | full_version = VERSION 68 | 69 | if not os.path.exists( 70 | os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(ROOT_DIR))), ".git") 71 | ): 72 | return full_version 73 | try: 74 | release = _minimal_ext_cmd(["git", "tag", "-l", "--points-at", "HEAD"]) 75 | except Exception: # pylint: disable=broad-except 76 | return full_version 77 | if not release: 78 | git_revision = git_version() 79 | full_version += ".dev0+" + git_revision[:7] 80 | 81 | return full_version 82 | 83 | 84 | __version__ = get_version_info() 85 | -------------------------------------------------------------------------------- /test/dynamics/backend/test_dynamics_job.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | """ 13 | Test DynamicsJob object. 14 | """ 15 | 16 | from qiskit.providers import JobStatus, JobError 17 | 18 | from qiskit_dynamics.backend.dynamics_job import DynamicsJob 19 | from ..common import QiskitDynamicsTestCase 20 | 21 | 22 | class TestDynamicsJob(QiskitDynamicsTestCase): 23 | """Tests DynamicsJob.""" 24 | 25 | def setUp(self): 26 | # pylint: disable=unused-argument 27 | def eval_func(job_id, x): 28 | return x**2 29 | 30 | self.simple_job = DynamicsJob( 31 | backend="", job_id="123", fn=eval_func, fn_kwargs={"x": 2}, other_kwarg="for testing" 32 | ) 33 | 34 | def test_submit_and_get_result(self): 35 | """Test basis job submission and results gathering.""" 36 | self.simple_job.submit() 37 | self.assertTrue(self.simple_job.result() == 4) 38 | 39 | def test_no_result_error(self): 40 | """Test error is raised if job not initially submitted.""" 41 | with self.assertRaisesRegex(JobError, "Job has not been submitted."): 42 | self.simple_job.result() 43 | 44 | def test_double_submit_error(self): 45 | """Test error is raised if job not initially submitted.""" 46 | with self.assertRaisesRegex(JobError, "Dynamics job has already been submitted."): 47 | self.simple_job.submit() 48 | self.simple_job.submit() 49 | 50 | def test_status(self): 51 | """Test correct status return.""" 52 | self.assertTrue(self.simple_job.status() == JobStatus.INITIALIZING) 53 | self.simple_job.submit() 54 | self.assertTrue(self.simple_job.status() == JobStatus.DONE) 55 | 56 | def test_metadata(self): 57 | """Test metadata storage.""" 58 | self.assertTrue(self.simple_job.metadata == {"other_kwarg": "for testing"}) 59 | 60 | def test_time_per_step(self): 61 | """Test correct handling of time_per_step.""" 62 | 63 | self.assertTrue(list(self.simple_job.time_per_step().keys()) == ["CREATED"]) 64 | 65 | self.simple_job.submit() 66 | keys = self.simple_job.time_per_step().keys() 67 | self.assertTrue(len(keys) == 2) 68 | self.assertTrue("CREATED" in keys) 69 | self.assertTrue("COMPLETED" in keys) 70 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2018. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | # pylint: disable=invalid-name 14 | 15 | # General options: 16 | 17 | project = 'Qiskit Dynamics' 18 | copyright = '2020, Qiskit Development Team' # pylint: disable=redefined-builtin 19 | author = 'Qiskit Development Team' 20 | 21 | # The short X.Y version 22 | version = '' 23 | # The full version, including alpha/beta/rc tags 24 | release = '0.6.0' 25 | 26 | extensions = [ 27 | 'sphinx.ext.napoleon', 28 | 'sphinx.ext.autodoc', 29 | 'sphinx.ext.autosummary', 30 | 'sphinx.ext.mathjax', 31 | 'sphinx.ext.viewcode', 32 | 'sphinx.ext.extlinks', 33 | 'jupyter_sphinx', 34 | 'sphinx_autodoc_typehints', 35 | 'reno.sphinxext', 36 | 'sphinx.ext.intersphinx', 37 | 'nbsphinx', 38 | 'sphinxcontrib.bibtex', 39 | "qiskit_sphinx_theme", 40 | ] 41 | templates_path = ["_templates"] 42 | 43 | numfig = True 44 | numfig_format = { 45 | 'table': 'Table %s' 46 | } 47 | language = 'en' 48 | pygments_style = 'colorful' 49 | add_module_names = False 50 | modindex_common_prefix = ['qiskit_dynamics.'] 51 | bibtex_default_style = 'unsrt' 52 | bibtex_bibfiles = ['refs.bib'] 53 | bibtex_bibliography_header = ".. rubric:: References" 54 | bibtex_footbibliography_header = bibtex_bibliography_header 55 | 56 | # html theme options 57 | html_theme = 'qiskit-ecosystem' 58 | html_last_updated_fmt = '%Y/%m/%d' 59 | 60 | docs_url_prefix = "qiskit-dynamics" 61 | 62 | html_context = {"version_list": ["0.4"]} 63 | 64 | html_theme_options = { 65 | "sidebar_qiskit_ecosystem_member": True, 66 | } 67 | 68 | # autodoc/autosummary options 69 | autosummary_generate = True 70 | autosummary_generate_overwrite = False 71 | autoclass_content = "both" 72 | intersphinx_mapping = { 73 | "qiskit": ("https://docs.quantum.ibm.com/api/qiskit", None), 74 | "qiskit_experiments": ("https://qiskit-community.github.io/qiskit-experiments/", None), 75 | "arraylias": ("https://qiskit-community.github.io/arraylias/", None) 76 | } 77 | 78 | # nbsphinx options (for tutorials) 79 | nbsphinx_timeout = 180 80 | # TODO: swap this with always if tutorial execution is too slow for ci and needs 81 | # a separate job 82 | # nbsphinx_execute = os.getenv('QISKIT_DOCS_BUILD_TUTORIALS', 'never') 83 | nbsphinx_execute = 'always' 84 | nbsphinx_widgets_path = '' 85 | exclude_patterns = ['_build', '**.ipynb_checkpoints'] -------------------------------------------------------------------------------- /qiskit_dynamics/arraylias/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2023. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | r""" 16 | ============================================ 17 | Arraylias (:mod:`qiskit_dynamics.arraylias`) 18 | ============================================ 19 | 20 | .. currentmodule:: qiskit_dynamics.arraylias 21 | 22 | This module contains Qiskit Dynamics-global extensions of the default NumPy and SciPy aliases 23 | provided by `Arraylias `_. These are used to manage 24 | dispatching of array operations for the different array types supported by Qiskit Dynamics. They 25 | have been configured beyond the `Arraylias `_ 26 | defaults to additionally support both `JAX `_ and SciPy 27 | sparse types. The following table summarizes the registered libraries and respective types. 28 | 29 | .. list-table:: Supported libraries 30 | :widths: 10 70 31 | :header-rows: 1 32 | 33 | * - ``array_library`` 34 | - Registered types 35 | * - ``"numpy"`` 36 | - Default supported by the Arraylias NumPy and SciPy aliases. 37 | * - ``"jax"`` 38 | - Default supported by the Arraylias NumPy and SciPy aliases. 39 | * - ``"jax_sparse"`` 40 | - The JAX ``jax.experimental.sparse.BCOO`` array type. 41 | * - ``"scipy_sparse"`` 42 | - Subclasses of the ``scipy.sparse.spmatrix`` sparse base class. When instantiating SciPy 43 | sparse arrays, the alias will specifically create ``scipy.sparse.csr_matrix`` instances. 44 | 45 | The global configured aliases and the aliased libraries can be imported from 46 | ``qiskit_dynamics.arraylias``, and are summarized by the following table. 47 | 48 | .. list-table:: Configured Arraylias objects 49 | :widths: 10 70 50 | :header-rows: 1 51 | 52 | * - Arraylias object 53 | - Description 54 | * - ``DYNAMICS_NUMPY_ALIAS`` 55 | - Qiskit Dynamics-global NumPy alias. 56 | * - ``DYNAMICS_SCIPY_ALIAS`` 57 | - Qiskit Dynamics-global SciPy alias. 58 | * - ``DYNAMICS_NUMPY`` 59 | - Qiskit Dynamics-global aliased NumPy library. 60 | * - ``DYNAMICS_SCIPY`` 61 | - Qiskit Dynamics-global aliased SciPy library. 62 | """ 63 | 64 | from .alias import ( 65 | DYNAMICS_NUMPY_ALIAS, 66 | DYNAMICS_SCIPY_ALIAS, 67 | DYNAMICS_NUMPY, 68 | DYNAMICS_SCIPY, 69 | ArrayLike, 70 | requires_array_library, 71 | ) 72 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2019. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | import os 16 | import setuptools 17 | 18 | requirements = [ 19 | "numpy>=1.17", 20 | "scipy>=1.4", 21 | "matplotlib>=3.0", 22 | "qiskit<=1.3", 23 | "multiset>=3.0.1", 24 | "sympy>=1.12", 25 | "arraylias" 26 | ] 27 | 28 | jax_extras = ['jax', 'jaxlib'] 29 | 30 | PACKAGES = setuptools.find_packages(exclude=['test*']) 31 | 32 | version_path = os.path.abspath( 33 | os.path.join(os.path.dirname(__file__), 'qiskit_dynamics', 34 | 'VERSION.txt')) 35 | 36 | with open(version_path, 'r') as fd: 37 | version = fd.read().rstrip() 38 | 39 | README_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 40 | 'README.md') 41 | with open(README_PATH) as readme_file: 42 | README = readme_file.read() 43 | 44 | setuptools.setup( 45 | name="qiskit-dynamics", 46 | version=version, 47 | packages=PACKAGES, 48 | description="Qiskit ODE solver", 49 | long_description=README, 50 | long_description_content_type='text/markdown', 51 | url="https://github.com/Qiskit-Community/qiskit-dynamics", 52 | author="Qiskit Development Team", 53 | author_email="qiskit@us.ibm.com", 54 | license="Apache 2.0", 55 | classifiers=[ 56 | "Environment :: Console", 57 | "License :: OSI Approved :: Apache Software License", 58 | "Intended Audience :: Developers", 59 | "Intended Audience :: Science/Research", 60 | "Operating System :: Microsoft :: Windows", 61 | "Operating System :: MacOS", 62 | "Operating System :: POSIX :: Linux", 63 | "Programming Language :: Python :: 3 :: Only", 64 | "Programming Language :: Python :: 3.10", 65 | "Programming Language :: Python :: 3.11", 66 | "Programming Language :: Python :: 3.12", 67 | "Programming Language :: Python :: 3.13", 68 | "Topic :: Scientific/Engineering", 69 | ], 70 | keywords="qiskit sdk quantum", 71 | project_urls={ 72 | "Bug Tracker": "https://github.com/Qiskit-Community/qiskit-dynamics/issues", 73 | "Source Code": "https://github.com/Qiskit-Community/qiskit-dynamics", 74 | "Documentation": "https://qiskit-community.github.io/qiskit-dynamics/", 75 | }, 76 | install_requires=requirements, 77 | include_package_data=True, 78 | python_requires=">=3.10", 79 | extras_require={ 80 | "jax": jax_extras 81 | }, 82 | zip_safe=False 83 | ) 84 | -------------------------------------------------------------------------------- /qiskit_dynamics/backend/dynamics_job.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """This module implements the job class used for DynamicsBackend objects.""" 14 | 15 | from typing import Callable, Dict 16 | from datetime import datetime 17 | 18 | from qiskit.providers.backend import Backend 19 | from qiskit.providers import JobV1 as Job 20 | from qiskit.providers import JobStatus, JobError 21 | 22 | 23 | class DynamicsJob(Job): 24 | """DynamicsJob class for DynamicsBackend.""" 25 | 26 | _async = False 27 | 28 | def __init__( 29 | self, backend: Backend, job_id: str, fn: Callable, fn_kwargs: Dict, **kwargs 30 | ) -> None: 31 | """Initializes the job. 32 | 33 | Args: 34 | backend: The backend used to run the job. 35 | job_id: A unique id in the context of the backend used to run the job. 36 | fn: Function to run the simulation. 37 | fn_kwargs: Kwargs for fn. 38 | kwargs: Any key value metadata to associate with this job. 39 | """ 40 | super().__init__(backend, job_id, **kwargs) 41 | self._fn = fn 42 | self._fn_kwargs = fn_kwargs 43 | self._result = None 44 | self._time_per_step = {"CREATED": datetime.now()} 45 | 46 | def submit(self): 47 | """Run the simulation. 48 | 49 | Raises: 50 | JobError: if trying to re-submit the job. 51 | """ 52 | if self._result is not None: 53 | raise JobError("Dynamics job has already been submitted.") 54 | self._result = self._fn(job_id=self.job_id(), **self._fn_kwargs) 55 | self._time_per_step["COMPLETED"] = datetime.now() 56 | 57 | def result(self): 58 | """Get job result. 59 | 60 | Returns: 61 | qiskit.Result: Result object. 62 | 63 | Raises: 64 | JobError: If job has not been submitted. 65 | """ 66 | if self._result is None: 67 | raise JobError("Job has not been submitted.") 68 | return self._result 69 | 70 | def status(self): 71 | """Gets the status of the job. 72 | 73 | Returns: 74 | JobStatus: The current JobStatus. 75 | """ 76 | if self._result is None: 77 | return JobStatus.INITIALIZING 78 | 79 | return JobStatus.DONE 80 | 81 | def time_per_step(self) -> Dict: 82 | """Return the date and time information on each step of the job processing. 83 | 84 | Returns: 85 | Dict for time of creation and time of completion of job. 86 | """ 87 | return self._time_per_step 88 | -------------------------------------------------------------------------------- /releasenotes/notes/0.2/0.2-summary-734dd5eb5f550189.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | The Qiskit Dynamics 0.2 release adds support for sparse JAX arrays when building 4 | solvers and models, and adds new fixed-step and parallelized JAX solvers. 5 | features: 6 | - | 7 | Adds JAX sparse evaluation mode to models and 8 | :class:`qiskit_dynamics.Solver`. 9 | This mode can be enabled by setting the ``evaluation_mode`` to 10 | ``'sparse'`` while :meth:`~qiskit_dynamics.array.Array.default_backend` is set to ``'jax'`` 11 | This evaluation mode is only recommended for use on CPU unless 12 | the arrays are extremely sparse. 13 | - | 14 | Adds additional fixed-step solvers implemented with JAX. New solvers are 15 | ``'jax_RK4'``, available through :func:`qiskit_dynamics.solve_ode` 16 | and parallelized solvers ``'jax_RK4_parallel'`` and ``'jax_expm_parallel'`` available 17 | via :func:`qiskit_dynamics.solve_lmde`. The parallelized solvers 18 | are only recommended for use with GPU. 19 | - | 20 | Adds new :class:`~qiskit_dynamics.array.Array` class methods 21 | :meth:`~qiskit_dynamics.array.Array.default_backend`, 22 | :meth:`~qiskit_dynamics.array.Array.set_default_backend`, 23 | :meth:`~qiskit_dynamics.array.Array.available_backends` which implement 24 | the functionality of the previous methods of the same name from the 25 | dispatch module. 26 | upgrade: 27 | - | 28 | The :class:`~qiskit_dynamics.array.Array` class and 29 | :func:`~qiskit_dynamics.array.wrap` function have been moved from the 30 | :mod:`qiskit_dynamics.dispatch` module to a separate 31 | :mod:`qiskit_dynamics.array` module to separate the ``Array`` class 32 | from the underlying dispatch system it uses internally. 33 | deprecations: 34 | - | 35 | The :func:`qiskit_dynamics.dispatch.default_backend` function has been 36 | deprecated and will be removed in 0.3.0 release. It has been replaced by 37 | the :meth:`qiskit_dynamics.array.Array.default_backend` class method. 38 | - | 39 | The :func:`qiskit_dynamics.dispatch.set_default_backend` function has been 40 | deprecated and will be removed in 0.3.0 release. It has been replaced by 41 | the :meth:`qiskit_dynamics.array.Array.set_default_backend` class method. 42 | - | 43 | The :func:`qiskit_dynamics.dispatch.available_backends` function has been 44 | deprecatedand will be removed in 0.3.0 release. It has been replaced by 45 | the :meth:`qiskit_dynamics.array.Array.available_backends` class method. 46 | - | 47 | The :func:`qiskit_dynamics.dispatch.backend_types` function has been 48 | deprecated as it is no longer needed. 49 | - | 50 | Importing the :class:`~qiskit_dynamics.array.Array` class for the 51 | :mod:`qiskit_dynamics.dispatch` module has been deprecated. It should now 52 | be imported from the :mod:`qiskit_dynamics.array` module instead. 53 | - | 54 | Importing the :class:`~qiskit_dynamics.array.wrap` function for the 55 | :mod:`qiskit_dynamics.dispatch` module has been deprecated. It should now 56 | be imported from the :mod:`qiskit_dynamics.array` module instead. 57 | -------------------------------------------------------------------------------- /test/dynamics/solvers/test_lanczos.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license 6 | # in the LICENSE.txt file in the root directory of this source tree or at 7 | # http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this copyright notice, and modified 10 | # files need to carry a notice indicating that they have been altered from the originals. 11 | # pylint: disable=invalid-name 12 | 13 | """ 14 | Direct tests of helper functions in lanczos.py. 15 | """ 16 | 17 | import numpy as np 18 | from scipy.linalg import expm 19 | from qiskit_dynamics.solvers.lanczos import ( 20 | lanczos_basis, 21 | lanczos_eigh, 22 | lanczos_expm, 23 | jax_lanczos_basis, 24 | jax_lanczos_eigh, 25 | jax_lanczos_expm, 26 | ) 27 | from ..common import QiskitDynamicsTestCase, JAXTestBase 28 | 29 | 30 | class TestLanczos(QiskitDynamicsTestCase): 31 | """Tests for lanczos.py.""" 32 | 33 | def setUp(self): 34 | self.basis = lanczos_basis 35 | self.eigh = lanczos_eigh 36 | self.expm = lanczos_expm 37 | 38 | self.dim = 8 39 | rng = np.random.default_rng(5213) 40 | rand_op = rng.uniform(-0.5, 0.5, (self.dim, self.dim)) 41 | # make hermitian 42 | rand_op = rand_op.conj().T + rand_op 43 | rand_y0 = rng.uniform(-0.5, 0.5, (self.dim,)) 44 | 45 | self.rand_op = rand_op 46 | self.rand_y0 = rand_y0 47 | 48 | def test_decomposition(self): 49 | """Test lanczos_basis function for correct projection.""" 50 | 51 | tridiagonal, q_basis = self.basis( 52 | self.rand_op, self.rand_y0 / np.linalg.norm(self.rand_y0), self.dim 53 | ) 54 | op = q_basis @ tridiagonal @ q_basis.T.conj() 55 | self.assertAllClose(self.rand_op, op) 56 | 57 | def test_ground_state(self): 58 | """Test lanczos_eigh function for ground state calculation.""" 59 | 60 | q_basis, eigen_values_l, eigen_vectors_t = self.eigh( 61 | self.rand_op, self.rand_y0 / np.linalg.norm(self.rand_y0), self.dim 62 | ) 63 | eigen_vectors_l = q_basis @ eigen_vectors_t 64 | eigen_values_np, eigen_vectors_np = np.linalg.eigh(self.rand_op) 65 | 66 | # test overlap is 1. 67 | overlap = np.abs(np.sum(eigen_vectors_np[:, 0].conj() * eigen_vectors_l[:, 0])) 68 | self.assertAllClose(overlap, 1.0) 69 | self.assertAllClose(eigen_values_np[0], eigen_values_l[0]) 70 | 71 | def test_expm(self): 72 | """Test lanczos_expm function.""" 73 | 74 | expAy_l = self.expm(-1j * self.rand_op, self.rand_y0, self.dim) 75 | expAy_s = expm(-1j * self.rand_op) @ self.rand_y0 76 | 77 | self.assertAllClose(expAy_s, expAy_l) 78 | 79 | 80 | class TestJaxLanczos(TestLanczos, JAXTestBase): 81 | """Tests for jax functions in lanczos.py.""" 82 | 83 | def setUp(self): 84 | super().setUp() 85 | self.basis = jax_lanczos_basis 86 | self.eigh = jax_lanczos_eigh 87 | self.expm = jax_lanczos_expm 88 | -------------------------------------------------------------------------------- /qiskit_dynamics/perturbation/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2022. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | r""" 16 | ========================================================= 17 | Perturbation Theory (:mod:`qiskit_dynamics.perturbation`) 18 | ========================================================= 19 | 20 | .. currentmodule:: qiskit_dynamics.perturbation 21 | 22 | This module contains tools for numerically computing and utilizing perturbation theory terms. 23 | Perturbation theory is an advanced topic; a brief review of the concepts and notation required to 24 | understand the contents in this module are given in the :ref:`Time-dependent perturbation theory and 25 | multi-variable series expansions review ` discussion. 26 | 27 | .. _td perturbation theory: 28 | 29 | Time-dependent perturbation theory 30 | ================================== 31 | 32 | The function :func:`.solve_lmde_perturbation` computes Dyson series :footcite:`dyson_radiation_1949` 33 | and Magnus expansion :footcite:`magnus_exponential_1954,blanes_magnus_2009` terms in a 34 | multi-variable setting via algorithms in :footcite:`puzzuoli_algorithms_2023`. It can also be used 35 | to compute Dyson-like integrals using the algorithm in :footcite:`haas_engineering_2019`. Results 36 | are returned in either a :class:`PowerSeriesData` or :class:`DysonLikeData` class, which are data 37 | classes with functionality for indexing and accessing specific perturbation terms. See the function 38 | documentation for further details. 39 | 40 | Truncated power-series representation and multisets 41 | =================================================== 42 | 43 | The class :class:`.ArrayPolynomial` represents an array-valued multivariable polynomial (i.e. a 44 | truncated power series), and provides functionality for both evaluating and transforming 45 | array-valued polynomials. 46 | 47 | This module makes use of the `multiset package `_ for indexing 48 | multi-variable power series. See the :ref:`multiset and power series notation section ` of the perturbation review for an explanation of this convention. 50 | 51 | 52 | Perturbation module functions 53 | ============================= 54 | 55 | .. autosummary:: 56 | :toctree: ../stubs/ 57 | 58 | solve_lmde_perturbation 59 | 60 | 61 | Perturbation module classes 62 | =========================== 63 | 64 | .. autosummary:: 65 | :toctree: ../stubs/ 66 | 67 | ArrayPolynomial 68 | PowerSeriesData 69 | DysonLikeData 70 | 71 | .. footbibliography:: 72 | """ 73 | 74 | from .array_polynomial import ArrayPolynomial 75 | from .solve_lmde_perturbation import solve_lmde_perturbation 76 | from .perturbation_data import PowerSeriesData, DysonLikeData 77 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: [ main, 'stable/*' ] 5 | pull_request: 6 | branches: [ main, 'stable/*' ] 7 | jobs: 8 | tests: 9 | name: tests-python${{ matrix.python-version }}-${{ matrix.os }} 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | python-version: ['3.10', '3.11', '3.12', '3.13'] 14 | os: ["ubuntu-latest", "macOS-latest", "windows-latest"] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Pip cache 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.cache/pip 25 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-tests-${{ hashFiles('setup.py','requirements-dev.txt','constraints.txt') }} 26 | restore-keys: | 27 | ${{ runner.os }}-${{ matrix.python-version }}-pip-tests- 28 | ${{ runner.os }}-${{ matrix.python-version }}-pip- 29 | ${{ runner.os }}-${{ matrix.python-version }} 30 | - name: Install Deps 31 | run: python -m pip install -U tox setuptools virtualenv wheel 32 | - name: Install and Run Tests with JAX 33 | run: tox -e jax 34 | if: runner.os != 'Windows' 35 | - name: Install and Run Tests 36 | run: tox -e py 37 | if: runner.os == 'Windows' 38 | lint: 39 | name: lint 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v3 43 | - name: Set up Python 3.10 44 | uses: actions/setup-python@v4 45 | with: 46 | python-version: '3.10' 47 | - name: Pip cache 48 | uses: actions/cache@v4 49 | with: 50 | path: ~/.cache/pip 51 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-lint-${{ hashFiles('setup.py','requirements-dev.txt','constraints.txt') }} 52 | restore-keys: | 53 | ${{ runner.os }}-${{ matrix.python-version }}-pip-lint- 54 | ${{ runner.os }}-${{ matrix.python-version }}-pip- 55 | ${{ runner.os }}-${{ matrix.python-version }}- 56 | - name: Install Deps 57 | run: python -m pip install -U tox 58 | - name: Run lint 59 | run: tox -elint 60 | docs: 61 | name: docs 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v3 65 | with: 66 | fetch-depth: 0 67 | - name: Set up Python 3.11 68 | uses: actions/setup-python@v4 69 | with: 70 | python-version: '3.11' 71 | - name: Pip cache 72 | uses: actions/cache@v4 73 | with: 74 | path: ~/.cache/pip 75 | key: ${{ runner.os }}-${{ matrix.python-version }}-pip-docs-${{ hashFiles('setup.py','requirements-dev.txt','constraints.txt') }} 76 | restore-keys: | 77 | ${{ runner.os }}-pip-docs- 78 | ${{ runner.os }}-pip- 79 | ${{ runner.os }}- 80 | - name: Install Deps 81 | run: | 82 | python -m pip install -U tox 83 | sudo apt-get install graphviz pandoc 84 | - name: Build Docs 85 | run: tox -edocs 86 | - uses: actions/upload-artifact@v4 87 | with: 88 | name: html_docs 89 | path: docs/_build/html 90 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/diffrax_solver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2022. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | # pylint: disable=invalid-name 15 | 16 | """ 17 | Wrapper for diffrax solvers 18 | """ 19 | 20 | from typing import Callable, Optional 21 | from scipy.integrate._ivp.ivp import OdeResult 22 | from qiskit import QiskitError 23 | 24 | from qiskit_dynamics.arraylias import ArrayLike, requires_array_library 25 | 26 | try: 27 | import jax.numpy as jnp 28 | except ImportError: 29 | pass 30 | 31 | 32 | @requires_array_library("jax") 33 | def diffrax_solver( 34 | rhs: Callable, 35 | t_span: ArrayLike, 36 | y0: ArrayLike, 37 | method: "AbstractSolver", 38 | t_eval: Optional[ArrayLike] = None, 39 | **kwargs, 40 | ): 41 | """Routine for calling ``diffrax.diffeqsolve`` 42 | 43 | Args: 44 | rhs: Callable of the form :math:`f(t, y)`. 45 | t_span: Interval to solve over. 46 | y0: Initial state. 47 | method: Which diffeq solving method to use. 48 | t_eval: Optional list of time points at which to return the solution. 49 | **kwargs: Optional arguments to be passed to ``diffeqsolve``. 50 | 51 | Returns: 52 | OdeResult: Results object. 53 | 54 | Raises: 55 | QiskitError: Passing both `SaveAt` argument and `t_eval` argument. 56 | """ 57 | 58 | from diffrax import ODETerm, SaveAt 59 | from diffrax import diffeqsolve 60 | 61 | # convert rhs and y0 to real 62 | rhs = real_rhs(rhs) 63 | y0 = c2r(y0) 64 | 65 | term = ODETerm(lambda t, y, _: rhs(t.real, y)) 66 | 67 | if "saveat" in kwargs and t_eval is not None: 68 | raise QiskitError( 69 | """Only one of t_eval or saveat can be passed when using 70 | a diffrax solver, but both were specified.""" 71 | ) 72 | 73 | if t_eval is not None: 74 | kwargs["saveat"] = SaveAt(ts=t_eval) 75 | 76 | results = diffeqsolve( 77 | term, 78 | solver=method, 79 | t0=t_span[0], 80 | t1=t_span[-1], 81 | dt0=None, 82 | y0=jnp.array(y0, dtype=float), 83 | **kwargs, 84 | ) 85 | 86 | sol_dict = vars(results) 87 | ys = sol_dict.pop("ys") 88 | ts = sol_dict.pop("ts") 89 | 90 | ys = jnp.swapaxes(r2c(jnp.swapaxes(ys, 0, 1)), 0, 1) 91 | 92 | results_out = OdeResult(t=ts, y=jnp.array(ys, dtype=complex), **sol_dict) 93 | 94 | return results_out 95 | 96 | 97 | def real_rhs(rhs): 98 | """Convert complex RHS to real RHS function""" 99 | 100 | def _real_rhs(t, y): 101 | return c2r(rhs(t, r2c(y))) 102 | 103 | return _real_rhs 104 | 105 | 106 | def c2r(arr): 107 | """Convert complex array to a real array""" 108 | return jnp.concatenate([jnp.real(arr), jnp.imag(arr)]) 109 | 110 | 111 | def r2c(arr): 112 | """Convert a real array to a complex array""" 113 | size = arr.shape[0] // 2 114 | return arr[:size] + 1j * arr[size:] 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qiskit Dynamics 2 | 3 | [![License](https://img.shields.io/github/license/Qiskit/qiskit-dynamics.svg?style=popout-square)](https://opensource.org/licenses/Apache-2.0) 4 | [![DOI](https://joss.theoj.org/papers/10.21105/joss.05853/status.svg)](https://doi.org/10.21105/joss.05853) 5 | 6 | **This repo is no longer being actively maintained.** 7 | 8 | Qiskit Dynamics is an open-source project for building, transforming, and solving time-dependent 9 | quantum systems in Qiskit. 10 | 11 | The goal of Qiskit Dynamics is to provide access to different numerical methods for solving 12 | differential equations, and to automate common processes typically performed by hand, e.g. applying 13 | frame transformations or rotating wave approximations to system and control Hamiltonians. 14 | 15 | Qiskit Dynamics can be configured to use either [NumPy](https://github.com/numpy/numpy) or 16 | [JAX](https://github.com/google/jax) as the backend for array operations. 17 | [NumPy](https://github.com/numpy/numpy) is the default, and [JAX](https://github.com/google/jax) is 18 | an optional dependency. [JAX](https://github.com/google/jax) provides just-in-time compilation, 19 | automatic differentiation, and GPU execution, and therefore is well-suited to tasks involving 20 | repeated evaluation of functions with different parameters; E.g. simulating a model of a quantum 21 | system over a range of parameter values, or optimizing the parameters of control sequence. 22 | 23 | Reference documentation may be found [here](https://qiskit-community.github.io/qiskit-dynamics/), including 24 | [tutorials](https://qiskit-community.github.io/qiskit-dynamics/tutorials/index.html), 25 | [user guide](https://qiskit-community.github.io/qiskit-dynamics/userguide/index.html), 26 | [API reference](https://qiskit-community.github.io/qiskit-dynamics/apidocs/index.html), and 27 | [Discussions](https://qiskit-community.github.io/qiskit-dynamics/discussions/index.html). 28 | 29 | ## Installation 30 | 31 | Qiskit Dynamics may be installed using pip via: 32 | 33 | ``` 34 | pip install qiskit-dynamics 35 | ``` 36 | 37 | Additionally, Qiskit Dynamics may be installed simultaneously with the CPU version of 38 | JAX via: 39 | 40 | ``` 41 | pip install "qiskit-dynamics[jax]" 42 | ``` 43 | 44 | Installing JAX with GPU support must be done manually, for instructions refer to the 45 | [JAX installation guide](https://github.com/google/jax#installation). 46 | 47 | 48 | ## Contribution Guidelines 49 | 50 | If you'd like to contribute to Qiskit Dynamics, please take a look at our 51 | [contribution guidelines](CONTRIBUTING.md). This project adheres to Qiskit's 52 | [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. 53 | 54 | We use [GitHub issues](https://github.com/Qiskit-Community/qiskit-dynamics/issues) for tracking 55 | requests and bugs. Please [join the Qiskit Slack community](https://qisk.it/join-slack) and use our 56 | [#qiskit-dynamics](https://qiskit.slack.com/archives/C03E7UVCDEV) channel for discussion and simple 57 | questions. For questions that are more suited for a forum we use the Qiskit tag in the 58 | [Stack Exchange](https://quantumcomputing.stackexchange.com/questions/tagged/qiskit). 59 | 60 | ## Authors and Citation 61 | 62 | Qiskit Dynamics is the work of 63 | [many people](https://github.com/Qiskit-Community/qiskit-dynamics/graphs/contributors) who 64 | contribute to the project at different levels. If you use Qiskit Dynamics, please cite our 65 | [paper](https://joss.theoj.org/papers/10.21105/joss.05853) as per the included 66 | [BibTeX file](https://github.com/Qiskit-Community/qiskit-dynamics/blob/main/CITATION.bib). 67 | 68 | ## License 69 | 70 | [Apache License 2.0](LICENSE.txt) 71 | -------------------------------------------------------------------------------- /qiskit_dynamics/pulse/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | """ 16 | ==================================== 17 | Pulse (:mod:`qiskit_dynamics.pulse`) 18 | ==================================== 19 | 20 | .. currentmodule:: qiskit_dynamics.pulse 21 | 22 | This module contains tools to interface :mod:`qiskit.pulse` with Qiskit Dynamics. Qiskit Dynamics 23 | simulates time evolution using the :class:`Signal` class, however :mod:`qiskit.pulse` specifies 24 | pulse instructions using a :class:`~qiskit.pulse.Schedule` or :class:`~qiskit.pulse.ScheduleBlock`. 25 | This module contains the required converters to convert from a :mod:`qiskit.pulse` control 26 | specification into :class:`Signal` instances for simulation. 27 | 28 | Converters 29 | ========== 30 | 31 | The conversion from a :class:`~qiskit.pulse.Schedule` to a list of :class:`Signal` instances is done 32 | with the :class:`InstructionToSignals` converter. The following codeblock shows a simple example 33 | instantiation, and how to use it to convert a :class:`~qiskit.pulse.Schedule` to a list of 34 | :class:`Signal` instances. 35 | 36 | .. warning:: 37 | 38 | The code blocks below supress ``DeprecationWarning`` instances raised by Qiskit Pulse in 39 | `qiskit` `1.3`. 40 | 41 | .. jupyter-execute:: 42 | :hide-code: 43 | 44 | # silence deprecation warnings from pulse 45 | import warnings 46 | warnings.filterwarnings("ignore", category=DeprecationWarning) 47 | 48 | .. code-block:: python 49 | 50 | converter = InstructionToSignals(dt=1, carriers=None) 51 | signals = converter.get_signals(sched) 52 | 53 | An example schedule, and the corresponding converted signals, is shown below. 54 | 55 | .. jupyter-execute:: 56 | :hide-code: 57 | 58 | import matplotlib.pyplot as plt 59 | import matplotlib.gridspec as gridspec 60 | 61 | import qiskit.pulse as pulse 62 | 63 | from qiskit_dynamics.pulse import InstructionToSignals 64 | 65 | 66 | with pulse.build(name="schedule") as sched: 67 | pulse.play(pulse.Drag(20, 0.5, 4, 0.5), pulse.DriveChannel(0)) 68 | pulse.shift_phase(1.0, pulse.DriveChannel(0)) 69 | pulse.play(pulse.Drag(20, 0.5, 4, 0.5), pulse.DriveChannel(0)) 70 | pulse.shift_frequency(0.5, pulse.DriveChannel(0)) 71 | pulse.play(pulse.GaussianSquare(200, 0.3, 4, 150), pulse.DriveChannel(0)) 72 | pulse.play(pulse.GaussianSquare(200, 0.3, 4, 150), pulse.DriveChannel(1)) 73 | 74 | fig = plt.figure(constrained_layout=True, figsize=(10, 7)) 75 | spec = gridspec.GridSpec(ncols=2, nrows=2, figure=fig) 76 | ax1 = fig.add_subplot(spec[0, :]) 77 | ax2 = fig.add_subplot(spec[1, 0]) 78 | ax3 = fig.add_subplot(spec[1, 1]) 79 | 80 | converter = InstructionToSignals(dt=1, carriers=None) 81 | 82 | signals = converter.get_signals(sched) 83 | 84 | signals[0].draw(0, 239, 400, axis=ax2, title="Signal from DriveChannel(0)") 85 | signals[1].draw(0, 239, 400, axis=ax3, title="Signal from DriveChannel(1)") 86 | sched.draw(axis=ax1) 87 | 88 | Converter class 89 | =============== 90 | 91 | .. autosummary:: 92 | :toctree: ../stubs/ 93 | 94 | InstructionToSignals 95 | """ 96 | from .pulse_to_signals import InstructionToSignals 97 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/scipy_solve_ivp.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Wrapper for calling scipy.integrate.solve_ivp. 16 | """ 17 | 18 | from typing import Callable, Union, Optional 19 | 20 | import numpy as np 21 | from scipy.integrate import solve_ivp, OdeSolver 22 | from scipy.integrate._ivp.ivp import OdeResult 23 | 24 | from qiskit import QiskitError 25 | from qiskit_dynamics.arraylias import ArrayLike 26 | 27 | # Supported scipy ODE methods 28 | COMPLEX_METHODS = ["RK45", "RK23", "BDF", "DOP853"] 29 | REAL_METHODS = ["LSODA", "Radau"] 30 | SOLVE_IVP_METHODS = COMPLEX_METHODS + REAL_METHODS 31 | 32 | 33 | def scipy_solve_ivp( 34 | rhs: Callable, 35 | t_span: ArrayLike, 36 | y0: ArrayLike, 37 | method: Union[str, OdeSolver], 38 | t_eval: Optional[ArrayLike] = None, 39 | **kwargs, 40 | ): 41 | """Routine for calling `scipy.integrate.solve_ivp`. 42 | 43 | Args: 44 | rhs: Callable of the form :math:`f(t, y)`. 45 | t_span: Interval to solve over. 46 | y0: Initial state. 47 | method: Solver method. 48 | t_eval: Points at which to evaluate the solution. 49 | **kwargs: Optional arguments to be passed to ``solve_ivp``. 50 | 51 | Returns: 52 | OdeResult: results object 53 | 54 | Raises: 55 | QiskitError: If unsupported kwarg present. 56 | """ 57 | 58 | if kwargs.get("dense_output", False) is True: 59 | raise QiskitError("dense_output not supported for solve_ivp.") 60 | 61 | y_shape = y0.shape 62 | 63 | # flatten y0 and rhs 64 | y0 = y0.flatten() 65 | rhs = flat_rhs(rhs, y_shape) 66 | 67 | # Check if solver is real only 68 | # TODO: Also check if model or y0 are complex 69 | # if they are both real we don't need to embed. 70 | embed_real = method in REAL_METHODS 71 | if embed_real: 72 | rhs = real_rhs(rhs) 73 | y0 = c2r(y0) 74 | 75 | results = solve_ivp(rhs, t_span=t_span, y0=y0.data, t_eval=t_eval, method=method, **kwargs) 76 | if embed_real: 77 | results.y = r2c(results.y) 78 | 79 | # convert to the standardized results format 80 | # solve_ivp returns the states as a 2d array with columns being the states 81 | results.y = results.y.transpose() 82 | results.y = np.array([y.reshape(y_shape) for y in results.y]) 83 | 84 | return OdeResult(**dict(results)) 85 | 86 | 87 | def flat_rhs(rhs, shape): 88 | """Convert an RHS with arbitrary state shape into one that is 1d.""" 89 | 90 | def _flat_rhs(t, y): 91 | return rhs(t, y.reshape(shape)).flatten() 92 | 93 | return _flat_rhs 94 | 95 | 96 | def real_rhs(rhs): 97 | """Convert complex RHS to real RHS function""" 98 | 99 | def _real_rhs(t, y): 100 | return c2r(rhs(t, r2c(y))) 101 | 102 | return _real_rhs 103 | 104 | 105 | def c2r(arr): 106 | """Convert complex array to a real array""" 107 | return np.concatenate([np.real(arr), np.imag(arr)]) 108 | 109 | 110 | def r2c(arr): 111 | """Convert a real array to a complex array""" 112 | size = arr.shape[0] // 2 113 | return arr[:size] + 1j * arr[size:] 114 | -------------------------------------------------------------------------------- /test/dynamics/backend/test_qiskit_experiments.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Integration tests that ensure this module interacts properly with qiskit-experiments. 16 | """ 17 | 18 | import numpy as np 19 | 20 | from qiskit import pulse 21 | from qiskit.circuit import Parameter 22 | from qiskit.circuit.library import XGate, SXGate 23 | from qiskit.transpiler import Target 24 | from qiskit.providers.backend import QubitProperties 25 | from qiskit_experiments.calibration_management.calibrations import Calibrations 26 | from qiskit_experiments.library.calibration.rough_frequency import RoughFrequencyCal 27 | 28 | from qiskit_dynamics import Solver, DynamicsBackend 29 | from ..common import JAXTestBase 30 | 31 | 32 | class TestExperimentsIntegration(JAXTestBase): 33 | """Test class for verifying correct integration with qiskit-experiments. Set to use JAX for 34 | speed. 35 | """ 36 | 37 | def setUp(self): 38 | """Build simple simulator for multiple tests.""" 39 | 40 | # build the solver 41 | solver = Solver( 42 | static_hamiltonian=2 * np.pi * 5.0 * np.array([[-1.0, 0.0], [0.0, 1.0]]) / 2, 43 | hamiltonian_operators=[2 * np.pi * np.array([[0.0, 1.0], [1.0, 0.0]]) / 2], 44 | rotating_frame=2 * np.pi * 5.0 * np.array([[-1.0, 0.0], [0.0, 1.0]]) / 2, 45 | hamiltonian_channels=["d0"], 46 | channel_carrier_freqs={"d0": 5.0}, 47 | dt=1.0 / 4.5, 48 | array_library="jax", 49 | ) 50 | 51 | # build target gate definitions 52 | target = Target() 53 | target.qubit_properties = [QubitProperties(frequency=5.0)] 54 | 55 | target.add_instruction(XGate()) 56 | target.add_instruction(SXGate()) 57 | 58 | self.simple_backend = DynamicsBackend( 59 | solver=solver, 60 | target=target, 61 | solver_options={"method": "jax_odeint", "atol": 1e-6, "rtol": 1e-8}, 62 | ) 63 | 64 | def test_RoughFrequencyCal_calibration(self): 65 | """Test RoughFrequencyCal outputs a sensible answer.""" 66 | 67 | cals = Calibrations() 68 | 69 | dur = Parameter("dur") 70 | sigma = Parameter("sigma") 71 | drive = pulse.DriveChannel(Parameter("ch0")) 72 | 73 | # Define and add template schedules. 74 | with pulse.build(name="x") as x: 75 | pulse.play(pulse.Drag(dur, Parameter("amp"), sigma, Parameter("beta")), drive) 76 | 77 | with pulse.build(name="sx") as sx: 78 | pulse.play(pulse.Drag(dur, Parameter("amp"), sigma, Parameter("beta")), drive) 79 | 80 | cals.add_schedule(x, num_qubits=1) 81 | cals.add_schedule(sx, num_qubits=1) 82 | 83 | for sched in ["x", "sx"]: 84 | cals.add_parameter_value(80, "sigma", schedule=sched) 85 | cals.add_parameter_value(0.5, "beta", schedule=sched) 86 | cals.add_parameter_value(320, "dur", schedule=sched) 87 | cals.add_parameter_value(0.5, "amp", schedule=sched) 88 | 89 | freq_estimate = 5.005 90 | frequencies = np.linspace(freq_estimate - 1e-2, freq_estimate + 1e-2, 51) 91 | 92 | self.simple_backend.set_options(seed_simulator=5243234) 93 | spec = RoughFrequencyCal([0], cals, frequencies, backend=self.simple_backend) 94 | spec.set_experiment_options(amp=0.05, sigma=80, duration=320) 95 | 96 | spec_data = spec.run().block_for_results() 97 | freq_fit = spec_data.analysis_results("f01").value.nominal_value 98 | 99 | self.assertTrue(np.abs(freq_fit - 5.0) < 1e-1) 100 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | r""" 16 | ======================================== 17 | Solvers (:mod:`qiskit_dynamics.solvers`) 18 | ======================================== 19 | 20 | .. currentmodule:: qiskit_dynamics.solvers 21 | 22 | This module provides classes and functions for solving differential equations. 23 | 24 | Table :ref:`1 ` summarizes the standard solver interfaces exposed in this 25 | module. It includes a high level class :class:`~qiskit_dynamics.solvers.Solver` for solving models 26 | of quantum systems, as well as low-level functions for solving both ordinary differential equations 27 | :math:`\dot{y}(t) = f(t, y(t))` and linear matrix differential equations 28 | :math:`\dot{y}(t) = G(t)y(t)`. 29 | 30 | Additionally, this module contains more specialized solvers for linear matrix differential equations 31 | based on perturbative expansions, described :ref:`below `. 32 | 33 | .. _standard solver table: 34 | 35 | .. list-table:: Solver interfaces 36 | :widths: 10 50 37 | :header-rows: 1 38 | 39 | * - Object 40 | - Description 41 | * - :class:`~qiskit_dynamics.solvers.Solver` 42 | - High level solver class for both Hamiltonian and Lindblad dynamics. Automatically constructs 43 | the relevant model type based on system details, and the 44 | :meth:`~qiskit_dynamics.solvers.Solver.solve` method automatically handles 45 | ``qiskit.quantum_info`` input types. 46 | * - :func:`~qiskit_dynamics.solvers.solve_ode` 47 | - Low level solver function for ordinary differential equations: 48 | 49 | .. math:: 50 | 51 | \dot{y}(t) = f(t, y(t)), 52 | 53 | for :math:`y(t)` arrays of arbitrary shape and :math:`f` specified as an arbitrary callable. 54 | * - :func:`~qiskit_dynamics.solvers.solve_lmde` 55 | - Low level solver function for linear matrix differential equations in *standard form*: 56 | 57 | .. math:: 58 | \dot{y}(t) = G(t)y(t), 59 | 60 | where :math:`G(t)` is either a callable or a ``qiskit_dynamics`` model type, and :math:`y(t)` 61 | arrays of suitable shape for the matrix multiplication above. 62 | 63 | .. _perturbative solvers: 64 | 65 | Perturbative Solvers 66 | ==================== 67 | 68 | The classes :class:`~qiskit_dynamics.solvers.DysonSolver` and 69 | :class:`~qiskit_dynamics.solvers.MagnusSolver` implement advanced solvers detailed in 70 | :footcite:`puzzuoli_algorithms_2023`, with the :class:`~qiskit_dynamics.solvers.DysonSolver` 71 | implementing a variant of the *Dysolve* algorithm originally introduced in 72 | :footcite:p:`shillito_fast_2020`. 73 | 74 | The solvers are specialized to linear matrix differential equations with :math:`G(t)` 75 | decomposed as: 76 | 77 | .. math:: 78 | 79 | G(t) = G_0 + \sum_j Re[f_j(t)e^{i2\pi\nu_jt}]G_j, 80 | 81 | and are fixed step with a pre-defined step size :math:`\Delta t`. The differential equation is 82 | solved by either computing a truncated Dyson series, or taking the exponential of a truncated Magnus 83 | expansion. 84 | 85 | Add reference to both userguide and perturbation theory module documentation. 86 | 87 | 88 | Solver classes 89 | ============== 90 | 91 | .. autosummary:: 92 | :toctree: ../stubs/ 93 | 94 | Solver 95 | DysonSolver 96 | MagnusSolver 97 | 98 | Solver functions 99 | ================ 100 | 101 | .. autosummary:: 102 | :toctree: ../stubs/ 103 | 104 | solve_ode 105 | solve_lmde 106 | 107 | .. footbibliography:: 108 | """ 109 | 110 | from .solver_functions import solve_ode, solve_lmde 111 | from .solver_classes import Solver 112 | from .perturbative_solvers.dyson_solver import DysonSolver 113 | from .perturbative_solvers.magnus_solver import MagnusSolver 114 | -------------------------------------------------------------------------------- /releasenotes/notes/0.5/0.5.0-notes-5d846b13c228f5c3.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | 4 | | Qiskit Dynamics ``0.5.0`` introduces a major change to the internal array dispatching 5 | mechanism. Users can now work directly with their supported array library of choice, without 6 | the need to personally manage the dispatching behaviour using the :class:`.Array` class as in 7 | previous versions. Internally, Qiskit Dynamics now uses `Arraylias 8 | `_ for dispatching. See the :ref:`user guide 9 | entry on using different array libraries with Qiskit Dynamics ` for more details on the updated approach. 11 | 12 | | To accompany this change, arguments that explicitly control the array library used to store 13 | matrices (e.g. ``evaluation_mode``) in models and related classes have also been changed. See 14 | the upgrade notes for a list of such changes, as well as the updated :ref:`user guide on 15 | configuring simulations for performance ` for how to use the new 16 | versions of these arguments. 17 | 18 | | Due to the scale of these changes, this release will generally break any pre-existing 19 | Dynamics code utilizing :class:`.Array` or any of the aformentioned model and solver 20 | arguments. The :class:`.Array` class and ``array`` and ``dispatch`` submodules have been 21 | deprecated, and will be removed in ``0.6.0``. The documentation for the previous release 22 | ``0.4.5`` is still available `here 23 | `_. 24 | features: 25 | - | 26 | Adds the :meth:`.DynamicsBackend.solve` method for running simulations of circuits and schedules 27 | for arbitrary input types, and returning the ODE simulation results. 28 | deprecations: 29 | - | 30 | The ``array`` and ``dispatch`` submodules of Qiskit Dynamics have been deprecated as of version 31 | 0.5.0, and will be removed in version ``0.6.0``. 32 | upgrade: 33 | - | 34 | Support for Python 3.12 has been added, and Python 3.8 has been dropped. 35 | - | 36 | The upper bound on JAX and Diffrax in the last version of Qiskit Dynamics has been removed. 37 | Users should try to use the latest version of JAX. 38 | - | 39 | The interface for :class:`.GeneratorModel`, :class:`.HamiltonianModel`, and 40 | :class:`.LindbladModel` have been modified. The ``copy`` method has been removed, and all setter 41 | methods other than ``in_frame_basis`` and ``signals`` have been removed. The ``evaluation_mode`` 42 | construction argument has been replaced by ``array_library``, which controls which array library 43 | is used internally to store and evaluate operations, and the additional ``vectorized`` boolean 44 | argument has been added to :class:`.LindbladModel` to control whether the equation is evaluated 45 | in vectorized mode. Note that, regardless of array library used, dense arrays must be supplied 46 | to the constructors of these classes, due to peculiarities of the internal setup for sparse 47 | libraries. 48 | - | 49 | In conjunction with the change to the ``evaluation_mode`` argument in the model classes, the 50 | :class:`.Solver` class has been updated to take the ``array_library`` constructor argument, as 51 | well as the ``vectorized`` constructor argument (for use when Lindblad terms are present). 52 | - | 53 | Similar to the :class:`.Solver` initialization method, the :meth:`.DynamicsBackend.from_backend` 54 | constructor method argument ``evaluation_mode`` has been removed and replaced by the arguments 55 | ``array_library`` and ``vectorized``. 56 | - | 57 | The logic in :meth:`.Solver.solve` for automatic ``jit`` compiling when using JAX and simulating 58 | a list of schedules has been updated to no longer be based on when ``Array.default_backend() == 59 | "jax"``. The attempted automatic ``jit`` now occurs based purely on whether 60 | ``method="jax_odeint"``, or ``method`` is a Diffrax integration method. A warning will be raised 61 | if the ``array_library`` is not known to be compatible with the compilation routine. (For now, 62 | ``"scipy_sparse"`` is the only ``array_library`` not compatible with this routine, however a 63 | warning will still be raised if no explicit ``array_library`` is provided, as in this case the 64 | JAX-compatibility is unknown.) -------------------------------------------------------------------------------- /qiskit_dynamics/models/model_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2024. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | # pylint: disable=invalid-name 15 | 16 | """Utilities model module.""" 17 | 18 | from typing import Union, List 19 | import numpy as np 20 | from scipy.sparse import csr_matrix, issparse 21 | from scipy.sparse import kron as sparse_kron 22 | from scipy.sparse import identity as sparse_identity 23 | 24 | from qiskit_dynamics.arraylias.alias import ArrayLike, _to_dense, _numpy_multi_dispatch 25 | 26 | 27 | def _kron(A, B): 28 | return _numpy_multi_dispatch(A, B, path="kron") 29 | 30 | 31 | def vec_commutator( 32 | A: Union[ArrayLike, csr_matrix, List[csr_matrix]], 33 | ) -> Union[ArrayLike, csr_matrix, List[csr_matrix]]: 34 | r"""Linear algebraic vectorization of the linear map X -> -i[A, X] 35 | in column-stacking convention. In column-stacking convention we have 36 | 37 | .. math:: 38 | vec(ABC) = C^T \otimes A vec(B), 39 | 40 | so for the commutator we have 41 | 42 | .. math:: 43 | -i[A, \cdot] = -i(A \cdot - \cdot A \mapsto id \otimes A - A^T \otimes id) 44 | 45 | Note: this function is also "vectorized" in the programming sense for dense arrays. 46 | 47 | Args: 48 | A: Either a 2d array representing the matrix A described above, 49 | a 3d array representing a list of matrices, a sparse matrix, or a 50 | list of sparse matrices. 51 | 52 | Returns: 53 | ArrayLike: Vectorized version of the map. 54 | """ 55 | 56 | if issparse(A): 57 | # single, sparse matrix 58 | sp_iden = sparse_identity(A.shape[-1], format="csr") 59 | return -1j * (sparse_kron(sp_iden, A) - sparse_kron(A.T, sp_iden)) 60 | if isinstance(A, (list, np.ndarray)) and issparse(A[0]): 61 | # taken to be 1d array of 2d sparse matrices 62 | sp_iden = sparse_identity(A[0].shape[-1], format="csr") 63 | out = [-1j * (sparse_kron(sp_iden, mat) - sparse_kron(mat.T, sp_iden)) for mat in A] 64 | return np.array(out) 65 | 66 | A = _to_dense(A) 67 | iden = np.eye(A.shape[-1]) 68 | axes = list(range(A.ndim)) 69 | axes[-1] = axes[-2] 70 | axes[-2] += 1 71 | return -1j * (_kron(iden, A) - _kron(A.transpose(axes), iden)) 72 | 73 | 74 | def vec_dissipator( 75 | L: Union[ArrayLike, csr_matrix, List[csr_matrix]], 76 | ) -> Union[ArrayLike, csr_matrix, List[csr_matrix]]: 77 | r"""Linear algebraic vectorization of the linear map 78 | X -> L X L^\dagger - 0.5 * (L^\dagger L X + X L^\dagger L) 79 | in column stacking convention. 80 | 81 | This gives 82 | 83 | .. math:: 84 | \overline{L} \otimes L - 0.5(id \otimes L^\dagger L + 85 | (L^\dagger L)^T \otimes id) 86 | 87 | Note: this function is also "vectorized" in the programming sense for dense matrices. 88 | """ 89 | 90 | if issparse(L): 91 | sp_iden = sparse_identity(L[0].shape[-1], format="csr") 92 | return sparse_kron(L.conj(), L) - 0.5 * ( 93 | sparse_kron(sp_iden, L.conj().T * L) + sparse_kron(L.T * L.conj(), sp_iden) 94 | ) 95 | if isinstance(L, (list, np.ndarray)) and issparse(L[0]): 96 | # taken to be 1d array of 2d sparse matrices 97 | sp_iden = sparse_identity(L[0].shape[-1], format="csr") 98 | out = [ 99 | sparse_kron(mat.conj(), mat) 100 | - 0.5 101 | * (sparse_kron(sp_iden, mat.conj().T * mat) + sparse_kron(mat.T * mat.conj(), sp_iden)) 102 | for mat in L 103 | ] 104 | return np.array(out) 105 | 106 | iden = np.eye(L.shape[-1]) 107 | axes = list(range(L.ndim)) 108 | 109 | L = _to_dense(L) 110 | axes[-1] = axes[-2] 111 | axes[-2] += 1 112 | Lconj = L.conj() 113 | LdagL = Lconj.transpose(axes) @ L 114 | LdagLtrans = LdagL.transpose(axes) 115 | 116 | return _kron(Lconj, iden) @ _kron(iden, L) - 0.5 * ( 117 | _kron(iden, LdagL) + _kron(LdagLtrans, iden) 118 | ) 119 | -------------------------------------------------------------------------------- /qiskit_dynamics/systems/subsystem_operators.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2025. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Concrete subsystem operators. 16 | """ 17 | 18 | import numpy as np 19 | 20 | from .abstract_subsystem_operators import AbstractSubsystemOperator 21 | 22 | 23 | class SubsystemOperator(AbstractSubsystemOperator): 24 | """A concrete operator specified in terms of a matrix.""" 25 | 26 | def __init__(self, matrix, subsystems, str_label=None): 27 | """Initialize. 28 | 29 | Args: 30 | matrix: The matrix of the operator. 31 | subsystems: The ordered subsystems representing the tensor factor system the matrix is 32 | specified on. 33 | """ 34 | if matrix.shape[0] != np.prod([s.dim for s in subsystems]): 35 | raise ValueError("Subsystem dimensions don't match matrix shape.") 36 | 37 | self._matrix = matrix 38 | self._str_label = str_label 39 | super().__init__(subsystems) 40 | 41 | def base_matrix(self): 42 | return self._matrix 43 | 44 | def __str__(self): 45 | str_label = self._str_label or "SubsystemOperator" 46 | 47 | subsystem_str = str(self.subsystems[0]) 48 | for s in self.subsystems[1:]: 49 | subsystem_str += f", {s}" 50 | return f"{str_label}({subsystem_str})" 51 | 52 | 53 | class A(AbstractSubsystemOperator): 54 | r"""Annihilation operator. 55 | 56 | Defined as the matrix with non-zero entries :math:`0, 1, \sqrt{2}, ..., \sqrt{n - 1}` in the 57 | first off-diagonal, where :math:`n` is the dimension of the subsystem being acted on. 58 | """ 59 | 60 | def base_matrix(self): 61 | return np.diag(np.sqrt(np.arange(1, self.subsystems[0].dim, dtype=complex)), 1) 62 | 63 | def __str__(self): 64 | return f"a({self.subsystems[0]})" 65 | 66 | 67 | class Adag(AbstractSubsystemOperator): 68 | r"""Creation operator. 69 | 70 | Defined as the matrix with non-zero entries :math:`0, 1, \sqrt{2}, ..., \sqrt{n - 1}` in the 71 | first lower off-diagonal, where :math:`n` is the dimension of the subsystem being acted on. 72 | """ 73 | 74 | def base_matrix(self): 75 | return np.diag(np.sqrt(np.arange(1, self.subsystems[0].dim, dtype=complex)), -1) 76 | 77 | def __str__(self): 78 | return f"adag({self.subsystems[0]})" 79 | 80 | 81 | class N(AbstractSubsystemOperator): 82 | """The number operator. 83 | 84 | Defined as the diagonal matrix with with entries ``[0, ..., dim - 1]``, where ``dim`` is the 85 | dimension of the :class:`Subsystem` the operator is defined on. 86 | """ 87 | 88 | def base_matrix(self): 89 | return np.diag(np.arange(self.subsystems[0].dim, dtype=complex)) 90 | 91 | def __str__(self): 92 | return f"N({self.subsystems[0]})" 93 | 94 | 95 | class I(AbstractSubsystemOperator): # noqa: E742 96 | """The identity operator.""" 97 | 98 | def base_matrix(self): 99 | return np.eye(self.subsystems[0].dim, dtype=complex) 100 | 101 | def __str__(self): 102 | return f"I({self.subsystems[0]})" 103 | 104 | 105 | class X(AbstractSubsystemOperator): 106 | """X operator. 107 | 108 | The standard Pauli :math:`X` operator, generalized to ``A + Adag`` for higher dimensions. 109 | """ 110 | 111 | def base_matrix(self): 112 | return A(self.subsystems).base_matrix() + Adag(self.subsystems).base_matrix() 113 | 114 | def __str__(self): 115 | return f"X({self.subsystems[0]})" 116 | 117 | 118 | class Y(AbstractSubsystemOperator): 119 | """Y operator. 120 | 121 | The standard Pauli :math:`Y` operator, generalized to ``-1j * (A - Adag)`` for higher 122 | dimensions. 123 | """ 124 | 125 | def base_matrix(self): 126 | return -1j * (A(self.subsystems).base_matrix() + (-1 * Adag(self.subsystems).base_matrix())) 127 | 128 | def __str__(self): 129 | return f"Y({self.subsystems[0]})" 130 | 131 | 132 | class Z(AbstractSubsystemOperator): 133 | """Z operator. 134 | 135 | The standard Pauli :math:`Z` operator, generalized to ``I - 2 * N`` for higher dimensions. 136 | """ 137 | 138 | def base_matrix(self): 139 | return I(self.subsystems).base_matrix() - 2 * N(self.subsystems).base_matrix() 140 | 141 | def __str__(self): 142 | return f"Z({self.subsystems[0]})" 143 | -------------------------------------------------------------------------------- /docs/refs.bib: -------------------------------------------------------------------------------- 1 | @article{bravyi_schriefferwolff_2011, 2 | title = {Schrieffer–{Wolff} transformation for quantum many-body systems}, 3 | volume = {326}, 4 | issn = {0003-4916}, 5 | doi = {10.1016/j.aop.2011.06.004}, 6 | language = {en}, 7 | number = {10}, 8 | journal = {Annals of Physics}, 9 | author = {Bravyi, Sergey and DiVincenzo, David P. and Loss, Daniel}, 10 | year = {2011}, 11 | pages = {2793--2826}, 12 | } 13 | 14 | @article{schrieffer_relation_1966, 15 | title = {Relation between the {Anderson} and {Kondo} {Hamiltonians}}, 16 | volume = {149}, 17 | doi = {10.1103/PhysRev.149.491}, 18 | number = {2}, 19 | urldate = {2022-02-28}, 20 | journal = {Physical Review}, 21 | author = {Schrieffer, J. R. and Wolff, P. A.}, 22 | year = {1966}, 23 | pages = {491--492}, 24 | } 25 | 26 | @article{luttinger_motion_1955, 27 | title = {Motion of {Electrons} and {Holes} in {Perturbed} {Periodic} {Fields}}, 28 | volume = {97}, 29 | doi = {10.1103/PhysRev.97.869}, 30 | number = {4}, 31 | urldate = {2022-02-28}, 32 | journal = {Physical Review}, 33 | author = {Luttinger, J. M. and Kohn, W.}, 34 | month = feb, 35 | year = {1955}, 36 | pages = {869--883}, 37 | } 38 | 39 | @misc{wikipedia_schriefferwolff_2021, 40 | title = {Schrieffer–{Wolff} transformation}, 41 | copyright = {Creative Commons Attribution-ShareAlike License}, 42 | url = {https://en.wikipedia.org/w/index.php?title=Schrieffer%E2%80%93Wolff_transformation&oldid=1059646489}, 43 | language = {en}, 44 | urldate = {2022-02-28}, 45 | journal = {Wikipedia}, 46 | year = {2021}, 47 | note = {Page Version ID: 1059646489}, 48 | } 49 | 50 | @article{puzzuoli_algorithms_2023, 51 | title = {Algorithms for perturbative analysis and simulation of quantum dynamics}, 52 | volume = {489}, 53 | issn = {0021-9991}, 54 | doi = {10.1016/j.jcp.2023.112262}, 55 | language = {en}, 56 | journal = {Journal of Computational Physics}, 57 | author = {Puzzuoli, D. and Lin, S. F. and Malekakhlagh, M. and Pritchett, E. and Rosand, B. and Wood, C. J.}, 58 | year = {2023}, 59 | } 60 | 61 | @article{haas_engineering_2019, 62 | title = {Engineering effective {Hamiltonians}}, 63 | volume = {21}, 64 | issn = {1367-2630}, 65 | doi = {10.1088/1367-2630/ab4525}, 66 | language = {en}, 67 | number = {10}, 68 | journal = {New Journal of Physics}, 69 | author = {Haas, H. and Puzzuoli, D. and Zhang, F. and Cory, D. G.}, 70 | year = {2019}, 71 | pages = {103011} 72 | } 73 | 74 | @article{blanes_magnus_2009, 75 | title = {The {Magnus} expansion and some of its applications}, 76 | volume = {470}, 77 | issn = {0370-1573}, 78 | doi = {10.1016/j.physrep.2008.11.001}, 79 | number = {5–6}, 80 | journal = {Physics Reports}, 81 | author = {Blanes, S. and Casas, F. and Oteo, J. A. and Ros, J.}, 82 | year = {2009}, 83 | pages = {151--238} 84 | } 85 | 86 | @article{magnus_exponential_1954, 87 | title = {On the exponential solution of differential equations for a linear operator}, 88 | volume = {7}, 89 | copyright = {Copyright © 1954 Wiley Periodicals, Inc., A Wiley Company}, 90 | issn = {1097-0312}, 91 | doi = {10.1002/cpa.3160070404}, 92 | language = {en}, 93 | number = {4}, 94 | journal = {Communications on Pure and Applied Mathematics}, 95 | author = {Magnus, W.}, 96 | year = {1954}, 97 | pages = {649--673} 98 | } 99 | 100 | @article{dyson_radiation_1949, 101 | title = {The {Radiation} {Theories} of {Tomonaga}, {Schwinger}, and {Feynman}}, 102 | volume = {75}, 103 | doi = {10.1103/PhysRev.75.486}, 104 | number = {3}, 105 | journal = {Physical Review}, 106 | author = {Dyson, F. J.}, 107 | year = {1949}, 108 | pages = {486--502} 109 | } 110 | 111 | @article{shillito_fast_2020, 112 | title = {Fast and differentiable simulation of driven quantum systems}, 113 | volume = {3}, 114 | doi = {10.1103/PhysRevResearch.3.033266}, 115 | number = {3}, 116 | journal = {Physical Review Research}, 117 | author = {Shillito, R. and Gross, J. A. and Di Paolo, A. and Genois, É. and Blais, A.}, 118 | year = {2021}, 119 | note = {Publisher: American Physical Society}, 120 | pages = {033266}, 121 | } 122 | 123 | @article{evans_timedependent_1967, 124 | title = {A time-dependent {Dyson} expansion - the nuclear resonance signal in a rotating single crystal}, 125 | volume = {92}, 126 | issn = {0370-1328}, 127 | doi = {10.1088/0370-1328/92/4/327}, 128 | language = {en}, 129 | number = {4}, 130 | journal = {Proceedings of the Physical Society}, 131 | author = {Evans, W. A. B. and Powles, J. G.}, 132 | year = {1967}, 133 | pages = {1046--1054} 134 | } 135 | 136 | @article{haeberlen_1968, 137 | title = {Coherent {A}veraging {E}ffects in {M}agnetic {R}esonance}, 138 | author = {Haeberlen, U. and Waugh, J. S.}, 139 | journal = {Physical Review}, 140 | volume = {175}, 141 | issue = {2}, 142 | pages = {453--467}, 143 | numpages = {0}, 144 | year = {1968}, 145 | publisher = {American Physical Society}, 146 | doi = {10.1103/PhysRev.175.453} 147 | } 148 | -------------------------------------------------------------------------------- /test/dynamics/signals/test_transfer_functions.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2018, 2019. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Tests for signal transfer functions. 16 | """ 17 | 18 | import numpy as np 19 | from qiskit_dynamics.signals import Convolution, DiscreteSignal, Sampler, IQMixer, Signal 20 | 21 | from ..common import QiskitDynamicsTestCase 22 | 23 | 24 | class TestTransferFunctions(QiskitDynamicsTestCase): 25 | """Tests for transfer functions.""" 26 | 27 | def setUp(self): 28 | pass 29 | 30 | def test_convolution(self): 31 | """Test of convolution function.""" 32 | ts = np.linspace(0, 100, 200) 33 | 34 | def gaussian(t): 35 | sigma = 4 36 | return 2.0 * ts[1] / np.sqrt(2.0 * np.pi * sigma**2) * np.exp(-(t**2) / (2 * sigma**2)) 37 | 38 | # Test the simple convolution of a signal without a carrier 39 | convolve = Convolution(gaussian) 40 | 41 | samples = [0.0 if t < 20.0 or t > 80.0 else 1.0 for t in ts] # Defines a square pulse. 42 | piecewise_const = DiscreteSignal( 43 | dt=ts[1] - ts[0], samples=samples, carrier_freq=0.0, start_time=0 44 | ) 45 | 46 | self.assertEqual(piecewise_const.duration, len(ts)) 47 | self.assertEqual(piecewise_const(21.0), 1.0) 48 | self.assertEqual(piecewise_const(81.0), 0.0) 49 | 50 | convolved = convolve(piecewise_const) 51 | 52 | self.assertLess(convolved(21.0), 1.0) 53 | self.assertGreater(convolved(81.0), 0.0) 54 | 55 | if isinstance(convolved, DiscreteSignal): 56 | self.assertEqual(convolved.duration, 2 * len(ts) - 1) 57 | else: 58 | self.fail() 59 | 60 | # Test that the normalization happens properly 61 | def non_normalized_gaussian(t): 62 | sigma = 4 63 | return 20.0 * np.exp(-(t**2) / (2 * sigma**2)) 64 | 65 | convolve = Convolution(non_normalized_gaussian) 66 | 67 | convolved2 = convolve(piecewise_const) 68 | 69 | self.assertAlmostEqual(convolved(15.0), convolved2(15.0), places=6) 70 | self.assertAlmostEqual(convolved(20.0), convolved2(20.0), places=6) 71 | self.assertAlmostEqual(convolved(25.0), convolved2(25.0), places=6) 72 | self.assertAlmostEqual(convolved(30.0), convolved2(30.0), places=6) 73 | 74 | def test_sampler(self): 75 | """Test the sampler.""" 76 | dt = 0.5 77 | signal = DiscreteSignal(dt=dt, samples=[0.3, 0.5], carrier_freq=0.2) 78 | sampler = Sampler(dt / 2, 4) 79 | 80 | new_signal = sampler(signal) 81 | 82 | self.assertTrue(np.allclose(new_signal.samples, [0.3, 0.3, 0.5, 0.5])) 83 | 84 | signal = DiscreteSignal(dt=dt, samples=[0.3, 0.4, 0.6, 0.8], carrier_freq=0.2) 85 | sampler = Sampler(2 * dt, 2) 86 | 87 | new_signal = sampler(signal) 88 | self.assertTrue(np.allclose(new_signal.samples, [0.4, 0.8])) 89 | 90 | def test_iq_mixer(self): 91 | """Test the IQ mixer by checking we can up-convert to 5GHz""" 92 | dt = 0.25 93 | 94 | # Sideband at 0.1 GHz 95 | in_phase = DiscreteSignal(dt, [1.0] * 200, carrier_freq=0.1, phase=0) 96 | quadrature = DiscreteSignal(dt, [1.0] * 200, carrier_freq=0.1, phase=np.pi / 2) 97 | 98 | sampler = Sampler(dt / 25, 5000) 99 | in_phase = sampler(in_phase) 100 | quadrature = sampler(quadrature) 101 | 102 | mixer = IQMixer(4.9) # LO at 4.9 GHz 103 | rf = mixer(in_phase, quadrature) 104 | 105 | # Check max amplitude of the fourier transform 106 | import scipy.fftpack 107 | 108 | rf_samples = DiscreteSignal.from_Signal(rf, dt=dt / 25, n_samples=5000).samples 109 | yf = scipy.fftpack.fft(np.real(rf_samples)) 110 | xf = np.linspace(0.0, 1.0 / (2.0 * dt / 25), len(rf_samples) // 2) 111 | 112 | self.assertAlmostEqual(5.0, xf[np.argmax(np.abs(yf[: len(rf_samples) // 2]))], 2) 113 | 114 | # Test the same using Signals and not DiscreteSignal 115 | in_phase = Signal(1.0, carrier_freq=0.1, phase=0) 116 | quadrature = Signal(1.0, carrier_freq=0.1, phase=-np.pi / 2) 117 | rf = mixer(in_phase, quadrature) 118 | dt = 0.01 119 | samples = DiscreteSignal.from_Signal(rf, dt=dt, n_samples=1000).samples 120 | 121 | yf = scipy.fftpack.fft(np.real(samples)) 122 | xf = np.linspace(0.0, 1.0 / (2.0 * dt), len(samples) // 2) 123 | 124 | self.assertAlmostEqual(4.8, xf[np.argmax(np.abs(yf[: len(samples) // 2]))], 1) 125 | -------------------------------------------------------------------------------- /test/dynamics/systems/test_subsystem_mapping.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2023. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """Tests for subsystem_mapping.py.""" 15 | 16 | from itertools import product 17 | 18 | import numpy as np 19 | 20 | from qiskit_dynamics.systems import Subsystem, SubsystemMapping, QuantumSystemModel 21 | from qiskit_dynamics.systems.subsystem_operators import X, Y, Z 22 | 23 | from ..common import QiskitDynamicsTestCase 24 | 25 | 26 | def swap(d0, d1): 27 | """Generate swap operator mapping C^d0 x C^d1 -> C^d1 x C^d0.""" 28 | W = np.zeros((d0 * d1, d0 * d1), dtype=float) 29 | 30 | for idx0, idx1 in product(range(d1), range(d0)): 31 | Eab = np.zeros((d1, d0), dtype=float) 32 | Eab[idx0, idx1] = 1 33 | W = W + np.kron(Eab, Eab.transpose()) 34 | 35 | return W 36 | 37 | 38 | class TestSubsystemMapping(QiskitDynamicsTestCase): 39 | """Tests for SubsystemMapping class.""" 40 | 41 | def test_basic_mapping(self): 42 | """Test simple example.""" 43 | 44 | q0 = Subsystem("Q0", dim=2) 45 | q1 = Subsystem("Q1", dim=3) 46 | A = np.eye(3, 2, dtype=complex) 47 | 48 | mapping = SubsystemMapping(matrix=A, in_subsystems=[q0], out_subsystems=[q1]) 49 | op = mapping(X(q0)) 50 | self.assertEqual(op.unmapped_subsystems, []) 51 | expected_mat = A @ X(q0).matrix() @ A.conj().transpose() 52 | self.assertAllClose(op.matrix([q1]), expected_mat) 53 | 54 | q2 = Subsystem("Q2", dim=4) 55 | expected_mat = np.kron(np.eye(4), expected_mat) 56 | self.assertAllClose(op.matrix([q1, q2]), expected_mat) 57 | 58 | def test_map_with_unmapped_subsystems(self): 59 | """Mapping on an unspecified subsystem.""" 60 | q0 = Subsystem("Q0", dim=2) 61 | q1 = Subsystem("Q1", dim=3) 62 | q2 = Subsystem("Q2", dim=2) 63 | A = np.eye(3, 2, dtype=complex) 64 | 65 | mapping = SubsystemMapping(matrix=A, in_subsystems=[q0], out_subsystems=[q1]) 66 | op = mapping(X(q2)) 67 | self.assertEqual(op.unmapped_subsystems, [q2]) 68 | expected_mat = np.kron(X(q2).matrix(), A @ np.eye(2) @ A.conj().transpose()) 69 | self.assertAllClose(op.matrix([q1, q2]), expected_mat) 70 | 71 | def test_multiple_subsystem_map(self): 72 | """Mapping with multiple subsystems.""" 73 | q0 = Subsystem("Q0", dim=2) 74 | q1 = Subsystem("Q1", dim=3) 75 | q2 = Subsystem("Q2", dim=2) 76 | 77 | rng = np.random.default_rng(523421) 78 | A = rng.random((2, 6)) + 1j * rng.random((2, 6)) 79 | mapping = SubsystemMapping(matrix=A, in_subsystems=[q0, q1], out_subsystems=[q2]) 80 | 81 | op = mapping(X(q0) @ Y(q1)) 82 | self.assertEqual(op.unmapped_subsystems, []) 83 | expected_mat = A @ np.kron(Y(q1).matrix(), X(q0).matrix()) @ A.conj().transpose() 84 | self.assertAllClose(op.matrix([q2]), expected_mat) 85 | 86 | q3 = Subsystem("Q3", dim=4) 87 | op = mapping(X(q0) @ Y(q1) @ Z(q3)) 88 | self.assertEqual(op.unmapped_subsystems, [q3]) 89 | expected_mat = np.kron( 90 | Z(q3).matrix(), A @ np.kron(Y(q1).matrix(), X(q0).matrix()) @ A.conj().transpose() 91 | ) 92 | self.assertAllClose(op.matrix([q2, q3]), expected_mat) 93 | 94 | def test_disjoint_outputs(self): 95 | """Mapping with multiple input and output subsystems that get output in weird order.""" 96 | q0 = Subsystem("Q0", dim=2) 97 | q1 = Subsystem("Q1", dim=3) 98 | q2 = Subsystem("Q2", dim=2) 99 | q3 = Subsystem("Q3", dim=4) 100 | q4 = Subsystem("Q4", dim=5) 101 | 102 | rng = np.random.default_rng(234223) 103 | A = rng.random((8, 6)) + 1j * rng.random((8, 6)) 104 | mapping = SubsystemMapping(matrix=A, in_subsystems=[q0, q1], out_subsystems=[q2, q3]) 105 | 106 | op = mapping(X(q0) @ Y(q1) @ Z(q4)) 107 | self.assertEqual(op.unmapped_subsystems, [q4]) 108 | expected_mat = np.kron( 109 | Z(q4).matrix(), A @ np.kron(Y(q1).matrix(), X(q0).matrix()) @ A.conj().transpose() 110 | ) 111 | expected_mat = ( 112 | np.kron(swap(5, 4), np.eye(2)) @ expected_mat @ np.kron(swap(4, 5), np.eye(2)) 113 | ) 114 | self.assertAllClose(op.matrix([q2, q4, q3]), expected_mat) 115 | 116 | def test_QuantumSystemModel_mapping(self): 117 | """Test simple example.""" 118 | 119 | q0 = Subsystem("Q0", dim=2) 120 | q1 = Subsystem("Q1", dim=3) 121 | A = np.eye(3, 2, dtype=complex) 122 | 123 | mapping = SubsystemMapping(matrix=A, in_subsystems=[q0], out_subsystems=[q1]) 124 | model = QuantumSystemModel( 125 | static_hamiltonian=Z(q0), 126 | drive_hamiltonian_coefficients=["d0"], 127 | drive_hamiltonians=[X(q0)], 128 | ) 129 | 130 | out_model = mapping(model) 131 | 132 | self.assertEqual(out_model.subsystems, [q1]) 133 | self.assertAllClose( 134 | out_model.static_hamiltonian.matrix(), A @ np.diag([1, -1]) @ A.conj().transpose() 135 | ) 136 | self.assertAllClose( 137 | out_model.drive_hamiltonians[0].matrix(), 138 | A @ np.array([[0, 1], [1, 0]]) @ A.conj().transpose(), 139 | ) 140 | -------------------------------------------------------------------------------- /test/dynamics/test_jax_transformations.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Tests for jax transformations. 16 | """ 17 | 18 | import numpy as np 19 | 20 | from qiskit.quantum_info.operators import Operator 21 | from qiskit_dynamics.models import HamiltonianModel 22 | from qiskit_dynamics.signals import Signal 23 | from qiskit_dynamics import solve_lmde 24 | 25 | from .common import JAXTestBase 26 | 27 | try: 28 | from jax import jit, grad 29 | import jax.numpy as jnp 30 | # pylint: disable=broad-except 31 | except Exception: 32 | pass 33 | 34 | 35 | class TestJaxTransformations(JAXTestBase): 36 | """Class for testing jax transformations of integrated use cases.""" 37 | 38 | def setUp(self): 39 | """Set up a basic parameterized simulation.""" 40 | 41 | self.w = 5.0 42 | self.r = 0.1 43 | 44 | operators = [ 45 | 2 * np.pi * self.w * Operator.from_label("Z").data / 2, 46 | 2 * np.pi * self.r * Operator.from_label("X").data / 2, 47 | ] 48 | 49 | self.operators = operators 50 | 51 | def param_sim(amp, drive_freq): 52 | signals = [1.0, Signal(lambda t: amp, carrier_freq=drive_freq)] 53 | 54 | ham = HamiltonianModel(operators=self.operators, signals=signals, validate=False) 55 | 56 | # The addition of 1e-16j is a workaround that should be removed when possible. See #371 57 | results = solve_lmde( 58 | ham, 59 | t_span=[0.0, 1 / self.r], 60 | y0=np.array([0.0, 1.0], dtype=complex) + 1e-16j, 61 | method="jax_odeint", 62 | atol=1e-10, 63 | rtol=1e-10, 64 | ) 65 | return results.y[-1] 66 | 67 | self.param_sim = param_sim 68 | 69 | def test_jit_solve_t_eval_jax_odeint(self): 70 | """Test compiling with a passed t_eval.""" 71 | 72 | def t_eval_param_sim(amp, drive_freq): 73 | signals = [1.0, Signal(lambda t: amp, carrier_freq=drive_freq)] 74 | 75 | ham = HamiltonianModel(operators=self.operators, signals=signals, validate=False) 76 | 77 | # The addition of 1e-16j is a workaround that should be removed when possible. See #371 78 | results = solve_lmde( 79 | ham, 80 | t_span=np.array([0.0, 1 / self.r]), 81 | y0=np.array([0.0, 1.0], dtype=complex) + 1e-16j, 82 | method="jax_odeint", 83 | t_eval=[0.0, 0.5 / self.r, 1 / self.r], 84 | atol=1e-10, 85 | rtol=1e-10, 86 | ) 87 | return results.y 88 | 89 | jit_sim = jit(t_eval_param_sim) 90 | 91 | yf = jit_sim(1.0, self.w) 92 | yf2 = jit_sim(2.0, self.w) 93 | 94 | # simple tests to verify correctness 95 | self.assertTrue(np.abs(yf[-1][0]) ** 2 > 0.999) 96 | self.assertTrue(np.abs(yf2[-1][0]) ** 2 < 0.001) 97 | 98 | def test_jit_solve_t_span(self): 99 | """Test compiling when t_span is influenced by the inputs.""" 100 | 101 | def param_sim(amp, drive_freq): 102 | signals = [1.0, Signal(lambda t: amp, carrier_freq=drive_freq)] 103 | 104 | ham = HamiltonianModel(operators=self.operators, signals=signals, validate=False) 105 | 106 | # The addition of 1e-16j is a workaround that should be removed when possible. See #371 107 | results = solve_lmde( 108 | ham, 109 | t_span=[0.0, (1 / self.r) * amp], 110 | y0=np.array([0.0, 1.0], dtype=complex) + 1e-16j, 111 | method="jax_odeint", 112 | atol=1e-10, 113 | rtol=1e-10, 114 | ) 115 | return results.y[-1] 116 | 117 | jit_sim = jit(param_sim) 118 | 119 | yf = jit_sim(1.0, self.w) 120 | 121 | # simple tests to verify correctness 122 | self.assertTrue(np.abs(yf[0]) ** 2 > 0.999) 123 | 124 | def test_jit_solve(self): 125 | """Test compiling a parameterized Hamiltonian simulation.""" 126 | 127 | jit_sim = jit(self.param_sim) 128 | 129 | # run the simulation twice, make sure it compiles and runs again 130 | yf = jit_sim(1.0, self.w) 131 | yf2 = jit_sim(2.0, self.w) 132 | 133 | # simple tests to verify correctness 134 | self.assertTrue(np.abs(yf[0]) ** 2 > 0.999) 135 | self.assertTrue(np.abs(yf2[0]) ** 2 < 0.001) 136 | 137 | def test_grad_solve(self): 138 | """Test computing gradient of a parameterized Hamiltonian simulation.""" 139 | 140 | def amp_to_prob(amp): 141 | return jnp.abs(self.param_sim(amp, self.w)[0]) ** 2 142 | 143 | grad_sim = grad(amp_to_prob) 144 | 145 | grad_p0 = grad_sim(1.0) 146 | 147 | self.assertTrue(np.abs(grad_p0) < 0.001) 148 | 149 | def test_jit_grad_solve(self): 150 | """Test compiling a computation of a gradient of a parameterized 151 | Hamiltonian simulation. 152 | """ 153 | 154 | def amp_to_prob(amp): 155 | return jnp.abs(self.param_sim(amp, self.w)[0]) ** 2 156 | 157 | jit_grad_sim = jit(grad(amp_to_prob)) 158 | 159 | grad_p0 = jit_grad_sim(1.0) 160 | 161 | self.assertTrue(np.abs(grad_p0) < 0.001) 162 | -------------------------------------------------------------------------------- /docs/tutorials/systems_modelling.rst: -------------------------------------------------------------------------------- 1 | .. _systems modelling tutorial: 2 | 3 | Building and solving models of quantum systems 4 | ============================================== 5 | 6 | In this tutorial we will walk through the simulation of a two-transmon system using the high level 7 | :mod:`.systems` modelling module. 8 | 9 | We will proceed in the following steps: 10 | 11 | 1. Define the model for the system as the summation of two transmon models with an exchange 12 | interaction. 13 | 2. Construct a :class:`.DressedBasis` object storing the dressed basis for the model. 14 | 3. Simulate the time evolution under driving of one of the transmons, starting in the ground state. 15 | 4. Restrict the :class:`.DressedBasis` to the computational subspace, and plot the populations of 16 | the computational states as a function of time under the above time evolution. 17 | 18 | First, we set JAX to work in 64 bit mode on CPU. 19 | 20 | .. jupyter-execute:: 21 | 22 | import jax 23 | jax.config.update("jax_enable_x64", True) 24 | 25 | # tell JAX we are using CPU 26 | jax.config.update('jax_platform_name', 'cpu') 27 | 28 | 29 | 1. Define the two transmon model 30 | -------------------------------- 31 | 32 | First, define a single transmon, modelled as a Duffing oscillator. We will use a 3-dimensional 33 | model. 34 | 35 | .. jupyter-execute:: 36 | 37 | from qiskit_dynamics.systems import Subsystem, DuffingOscillator 38 | 39 | # subsystem the model is to be defined on 40 | Q0 = Subsystem("0", dim=3) 41 | 42 | # the model 43 | Q0_model = DuffingOscillator( 44 | subsystem=Q0, 45 | frequency=5., 46 | anharm=-0.33, 47 | drive_strength=0.01 48 | ) 49 | 50 | Print the model to see its contents. 51 | 52 | .. jupyter-execute:: 53 | 54 | print(str(Q0_model)) 55 | 56 | 57 | Define a model for the second transmon, an exchange interaction, and add the models together. 58 | 59 | .. jupyter-execute:: 60 | 61 | from qiskit_dynamics.systems import ExchangeInteraction 62 | 63 | # subsytem for second transmon 64 | Q1 = Subsystem("1", 3) 65 | 66 | # model for second transmon 67 | Q1_model = DuffingOscillator( 68 | subsystem=Q1, 69 | frequency=5.05, 70 | anharm=-0.33, 71 | drive_strength=0.01 72 | ) 73 | 74 | # model for coupling 75 | coupling_model = ExchangeInteraction( 76 | subsystems=[Q0, Q1], 77 | g=0.002 78 | ) 79 | 80 | two_transmon_model = Q0_model + Q1_model + coupling_model 81 | 82 | 83 | Printing the string representation of the full ``two_transmon_model`` shows how the different 84 | components are combined by model addition. 85 | 86 | .. jupyter-execute:: 87 | 88 | print(str(two_transmon_model)) 89 | 90 | 91 | 2. Construct the dressed basis 92 | ------------------------------ 93 | 94 | The initial state and results will be computed in terms of the dressed basis. Call the 95 | ``dressed_basis`` method of the model to construct the :class:`.DressedBasis` instance corresponding 96 | to this model. 97 | 98 | .. jupyter-execute:: 99 | 100 | dressed_basis = two_transmon_model.dressed_basis() 101 | 102 | 103 | 3. Simulate the evolution of the system under a constant drive envelope on one of the transmons 104 | ----------------------------------------------------------------------------------------------- 105 | 106 | Using the ``solve`` method, run a simulation under a constant drive envelope on transmon ``0``. 107 | Note that in contrast to previous interfaces, like the :class:`.Solver` class, the signals are 108 | passed as a dictionary mapping coefficient names to the :class:`.Signal` instance. 109 | 110 | Use the ground state as the initial state, accessible via the ``ground_state`` property of the 111 | :class:`.DressedBasis` object. 112 | 113 | .. jupyter-execute:: 114 | 115 | import numpy as np 116 | from qiskit_dynamics import Signal 117 | 118 | tf = 0.5 / 0.01 119 | t_span = np.array([0., tf]) 120 | t_eval = np.linspace(0., t_span[-1], 50) 121 | 122 | result = two_transmon_model.solve( 123 | signals={"d0": Signal(1., carrier_freq=5.)}, 124 | t_span=t_span, 125 | t_eval=t_eval, 126 | y0=dressed_basis.ground_state, 127 | atol=1e-10, 128 | rtol=1e-10, 129 | method="jax_odeint" 130 | ) 131 | 132 | 4. Plot the populations of the computational states during the above time evolution 133 | ----------------------------------------------------------------------------------- 134 | 135 | First, we restrict the dressed basis to only the computational states, via the 136 | ``computational_states`` property. 137 | 138 | .. jupyter-execute:: 139 | 140 | computational_states = dressed_basis.computational_states 141 | 142 | 143 | The populations of observing a given state in one of the computational states can be computed via 144 | the :meth:`.ONBasis.probabilities` method. For example, we can compute them for the final state: 145 | 146 | .. jupyter-execute:: 147 | 148 | probabilities = computational_states.probabilities(result.y[-1]) 149 | for label, probability in zip(computational_states.labels, probabilities): 150 | print(f'{label["index"]}: {probability}') 151 | 152 | 153 | 154 | Applying this function to every intermediate time point, generate a plot of the computational state 155 | populations over the full time evolution: 156 | 157 | .. jupyter-execute:: 158 | 159 | import matplotlib.pyplot as plt 160 | from jax import vmap 161 | 162 | # vectorize the probability function and evaluate on all states 163 | probabilities = vmap( 164 | computational_states.probabilities 165 | )(result.y) 166 | 167 | # plot 168 | for label, data in zip(computational_states.labels, probabilities.transpose()): 169 | plt.plot(t_eval, data, label=str(label["index"])) 170 | plt.legend() -------------------------------------------------------------------------------- /test/dynamics/solvers/test_diffrax_DOP5.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Direct tests of diffrax_solver 16 | """ 17 | 18 | import numpy as np 19 | 20 | from qiskit_dynamics.solvers.diffrax_solver import diffrax_solver 21 | 22 | from ..common import JAXTestBase 23 | 24 | try: 25 | import jax.numpy as jnp 26 | from jax.lax import cond 27 | from diffrax import Dopri5, PIDController 28 | # pylint: disable=broad-except 29 | except Exception: 30 | pass 31 | 32 | 33 | class TestDiffraxDopri5(JAXTestBase): 34 | """Test cases for diffrax_solver.""" 35 | 36 | def setUp(self): 37 | # pylint: disable=unused-argument 38 | def simple_rhs(t, y): 39 | return cond(t < 1.0, lambda s: s, lambda s: s**2, jnp.array([t])) 40 | 41 | self.simple_rhs = simple_rhs 42 | 43 | def test_no_t_eval(self): 44 | """Test case for basic model without t_eval.""" 45 | t_span = np.array([0.0, 2.0]) 46 | y0 = jnp.array([1.0]) 47 | 48 | stepsize_controller = PIDController(rtol=1e-10, atol=1e-10) 49 | results = diffrax_solver( 50 | self.simple_rhs, 51 | t_span, 52 | y0, 53 | method=Dopri5(), 54 | t_eval=None, 55 | stepsize_controller=stepsize_controller, 56 | ) 57 | 58 | self.assertAllClose(results.t, t_span[-1]) 59 | 60 | def test_t_eval_arg_no_overlap(self): 61 | """Test handling of t_eval when no overlap with t_span.""" 62 | 63 | t_span = np.array([0.0, 2.0]) 64 | t_eval = np.array([1.0, 1.5, 1.7]) 65 | y0 = jnp.array([1.0]) 66 | 67 | stepsize_controller = PIDController(rtol=1e-10, atol=1e-10) 68 | results = diffrax_solver( 69 | self.simple_rhs, 70 | t_span, 71 | y0, 72 | method=Dopri5(), 73 | t_eval=t_eval, 74 | stepsize_controller=stepsize_controller, 75 | ) 76 | 77 | self.assertAllClose(t_eval, results.t) 78 | 79 | expected_y = jnp.array( 80 | [ 81 | [1 + 0.5], 82 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 83 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 84 | ] 85 | ) 86 | 87 | self.assertAllClose(expected_y, results.y) 88 | 89 | def test_t_eval_arg_no_overlap_backwards(self): 90 | """Test handling of t_eval when no overlap with t_span with backwards integration.""" 91 | 92 | t_span = np.array([2.0, 0.0]) 93 | t_eval = np.array([1.7, 1.5, 1.0]) 94 | y0 = jnp.array([1 + 0.5 + (2.0**3 - 1.0**3) / 3]) 95 | 96 | stepsize_controller = PIDController(rtol=1e-10, atol=1e-10) 97 | results = diffrax_solver( 98 | self.simple_rhs, 99 | t_span, 100 | y0, 101 | method=Dopri5(), 102 | t_eval=t_eval, 103 | stepsize_controller=stepsize_controller, 104 | ) 105 | 106 | self.assertAllClose(t_eval, results.t) 107 | 108 | expected_y = jnp.array( 109 | [ 110 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 111 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 112 | [1 + 0.5], 113 | ] 114 | ) 115 | 116 | self.assertAllClose(expected_y, results.y) 117 | 118 | def test_t_eval_arg_overlap(self): 119 | """Test handling of t_eval with overlap with t_span.""" 120 | 121 | t_span = np.array([0.0, 2.0]) 122 | t_eval = np.array([1.0, 1.5, 1.7, 2.0]) 123 | y0 = jnp.array([1.0]) 124 | 125 | stepsize_controller = PIDController(rtol=1e-10, atol=1e-10) 126 | results = diffrax_solver( 127 | self.simple_rhs, 128 | t_span, 129 | y0, 130 | method=Dopri5(), 131 | t_eval=t_eval, 132 | stepsize_controller=stepsize_controller, 133 | ) 134 | 135 | self.assertAllClose(t_eval, results.t) 136 | 137 | expected_y = jnp.array( 138 | [ 139 | [1 + 0.5], 140 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 141 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 142 | [1 + 0.5 + (2**3 - 1.0**3) / 3], 143 | ] 144 | ) 145 | 146 | self.assertAllClose(expected_y, results.y) 147 | 148 | def test_t_eval_arg_overlap_backwards(self): 149 | """Test handling of t_eval with overlap with t_span with backwards integration.""" 150 | 151 | t_span = np.array([2.0, 0.0]) 152 | t_eval = np.array([2.0, 1.7, 1.5, 1.0]) 153 | y0 = jnp.array([1 + 0.5 + (2.0**3 - 1.0**3) / 3]) 154 | 155 | stepsize_controller = PIDController(rtol=1e-10, atol=1e-10) 156 | results = diffrax_solver( 157 | self.simple_rhs, 158 | t_span, 159 | y0, 160 | method=Dopri5(), 161 | t_eval=t_eval, 162 | stepsize_controller=stepsize_controller, 163 | ) 164 | 165 | self.assertAllClose(t_eval, results.t) 166 | 167 | expected_y = jnp.array( 168 | [ 169 | [1 + 0.5 + (2**3 - 1.0**3) / 3], 170 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 171 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 172 | [1 + 0.5], 173 | ] 174 | ) 175 | 176 | self.assertAllClose(expected_y, results.y) 177 | -------------------------------------------------------------------------------- /qiskit_dynamics/systems/__init__.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2025. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | 13 | """ 14 | ======================================== 15 | Systems (:mod:`qiskit_dynamics.systems`) 16 | ======================================== 17 | 18 | .. currentmodule:: qiskit_dynamics.systems 19 | 20 | This module provides high level interfaces for building and solving models of quantum systems. Where 21 | the :mod:`.solvers` and :mod:`.models` modules provide interfaces for defining and solving systems 22 | in terms of user-defined arrays, this module provides tools for building descriptions of systems in 23 | terms of tensor-factor subsystems, an algebraic system for defining operators on subsystems, a high 24 | level class representing an abstract dynamical model of a quantum system, and tools for analysing 25 | results. The ultimate purpose of the module is to minimize the need for a user to work explicitly 26 | with building and manipulating arrays and array indexing, which can be time consuming and prone to 27 | error. See the :ref:`Systems Modelling Tutorial ` and the :ref:`How-to 28 | use advanced system modelling functionality ` for detailed examples. 29 | 30 | The core building block of a model is a :class:`.Subsystem`, which represents a single 31 | finite-dimensional complex vector space on which to define the model of a quantum system. A single 32 | model may be defined on multiple subsystems, in which each subsystem represents a tensor factor in a 33 | tensor-product space. 34 | 35 | .. code-block:: python 36 | 37 | Q0 = Subsystem(name="Q0", dim=2) 38 | Q1 = Subsystem(name="Q0", dim=2) 39 | 40 | 41 | Abstract operators acting on these subsystems can be defined as follows: 42 | 43 | .. code-block:: python 44 | 45 | X0 = X(Q0) 46 | Y1 = Y(Q1) 47 | 48 | Using algebraic operations, new operators may be defined. For example, the tensor product of ``X`` 49 | on ``Q0`` and ``Y`` on ``Q1`` can be constructed through matrix multiplication: 50 | 51 | .. code-block:: python 52 | 53 | X0 @ Y1 54 | 55 | Similarly, the sum of these operators can be constructed through addition ``X0 + Y1``. To facilitate 56 | working with operators on subsystems without needing to always specify the full context of all 57 | subsystems in a given model, operators are always assumed to act as the identity on all unspecified 58 | subsystems, similar to the common mathematical notation in which an operator :math:`a_2` means "the 59 | operator :math:`a` acting on subsystem :math:`2` and the identity on all others". 60 | 61 | The matrix of an abstract operator can be built by calling the ``matrix`` method. The specific 62 | ordering of the tensor factors desired can be supplied, e.g.: 63 | 64 | .. code-block:: python 65 | 66 | (X0 @ Y1).matrix(ordered_subsystems=[Q0, Q1]) 67 | 68 | If no explicitly ordering is supplied, the default internal ordering built during the construction 69 | of the operator will be used. In addition to a set of pre-defined operators, users can instantiate a 70 | :class:`.SubsystemOperator` instance with an arbitrary concrete matrix which acts on an arbitrary 71 | list of :class:`.Subsystem` instances. 72 | 73 | Operators can be assumbled into models of quantum systems using the :class:`.QuantumSystemModel` 74 | class. For example, a model of a standard qubit can be built as follows: 75 | 76 | .. code-block:: python 77 | 78 | q0_model = QuantumSystemModel( 79 | static_hamiltonian=2 * np.pi * 5. * N(Q0), 80 | drive_hamiltonians=[2 * np.pi * 0.1 * X(Q0)], 81 | drive_hamiltonian_coefficients=["d0"] 82 | ) 83 | 84 | This model can now be solved with a single call: 85 | 86 | .. code-block:: python 87 | 88 | results = q0_model.solve( 89 | signals={"d0": Signal(1., carrier_freq=5.)}, 90 | t_span=t_span, 91 | t_eval=t_eval, 92 | y0=y0 93 | ) 94 | 95 | with ``results`` being the standard ``OdeResult`` object returned by Qiskit Dynamics solvers. 96 | 97 | In addition to the functionality above, this module contains the :class:`SubsystemMapping` class for 98 | defining linear maps between tensor factor spaces given as lists of :class:`Subsystem` instances. As 99 | shown in the :ref:`How-to use advanced system modelling functionality ` 100 | userguide entry, this class can be used to define injections of subspaces into larger spaces, or to 101 | restrict a model to a subspace of interest. 102 | 103 | Furthermore, the :class:`ONBasis` and :class:`DressedBasis` classes represent bases for subspaces on 104 | tensor product spaces represented by lists of :class:`Subsystem` instances. 105 | 106 | System modelling classes 107 | ======================== 108 | 109 | .. autosummary:: 110 | :toctree: ../stubs/ 111 | 112 | Subsystem 113 | SubsystemOperator 114 | FunctionOperator 115 | ONBasis 116 | DressedBasis 117 | SubsystemMapping 118 | QuantumSystemModel 119 | IdealQubit 120 | DuffingOscillator 121 | ExchangeInteraction 122 | 123 | Pre-defined operators 124 | ===================== 125 | 126 | .. autosummary:: 127 | :toctree: ../stubs/ 128 | 129 | I 130 | X 131 | Y 132 | Z 133 | N 134 | A 135 | Adag 136 | """ 137 | 138 | from .subsystem import Subsystem 139 | from .subsystem_operators import * 140 | from .abstract_subsystem_operators import FunctionOperator 141 | from .orthonormal_basis import ONBasis, DressedBasis 142 | from .subsystem_mapping import SubsystemMapping 143 | from .quantum_system_model import * 144 | -------------------------------------------------------------------------------- /qiskit_dynamics/solvers/perturbative_solvers/magnus_solver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2022. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | # pylint: disable=invalid-name 15 | 16 | """ 17 | Magnus expansion-based solver. 18 | """ 19 | 20 | from typing import Optional, List, Union 21 | 22 | from scipy.linalg import expm 23 | from scipy.integrate._ivp.ivp import OdeResult 24 | 25 | from multiset import Multiset 26 | 27 | from qiskit.quantum_info import Operator 28 | 29 | from qiskit_dynamics import Signal, RotatingFrame, ArrayLike 30 | 31 | from .expansion_model import ExpansionModel 32 | from .perturbative_solver import _PerturbativeSolver, _perturbative_solve, _perturbative_solve_jax 33 | 34 | try: 35 | from jax.scipy.linalg import expm as jexpm 36 | except ImportError: 37 | pass 38 | 39 | 40 | class MagnusSolver(_PerturbativeSolver): 41 | """Solver for linear matrix differential equations based on the Magnus expansion. 42 | 43 | This class implements the Magnus expansion-based solver presented in 44 | [:footcite:`puzzuoli_algorithms_2023`], which is a Magnus expansion variant of the 45 | *Dysolve* algorithm originally introduced in [:footcite:p:`shillito_fast_2020`]. Its 46 | setup and behaviour are the same as as the :class:`~qiskit_dynamics.solvers.DysonSolver` 47 | class, with the sole exception being that it uses a truncated Magnus expansion 48 | and matrix exponentiation to solve over a single time step. See the 49 | :ref:`Time-dependent perturbation theory and multi-variable 50 | series expansions review ` for a description of the Magnus expansion, 51 | and the documentation for :class:`~qiskit_dynamics.solvers.DysonSolver` for more detailed 52 | behaviour of this class. 53 | """ 54 | 55 | def __init__( 56 | self, 57 | operators: List[Operator], 58 | rotating_frame: Union[ArrayLike, RotatingFrame, None], 59 | dt: float, 60 | carrier_freqs: ArrayLike, 61 | chebyshev_orders: List[int], 62 | expansion_order: Optional[int] = None, 63 | expansion_labels: Optional[List[Multiset]] = None, 64 | integration_method: Optional[str] = None, 65 | include_imag: Optional[List[bool]] = None, 66 | **kwargs, 67 | ): 68 | r"""Initialize. 69 | 70 | Args: 71 | operators: List of constant operators specifying the operators with signal coefficients. 72 | rotating_frame: Rotating frame to setup the solver in. 73 | Must be Hermitian or anti-Hermitian. 74 | dt: Fixed step size to compile to. 75 | carrier_freqs: Carrier frequencies of the signals in the generator decomposition. 76 | chebyshev_orders: Approximation degrees for each signal over the interval [0, dt]. 77 | expansion_order: Order of perturbation terms to compute up to. Specifying this 78 | argument results in computation of all terms up to the given order. 79 | Can be used in conjunction with ``expansion_terms``. 80 | expansion_labels: Specific perturbation terms to compute. If both ``expansion_order`` 81 | and ``expansion_terms`` are specified, then all terms up to 82 | ``expansion_order`` are computed, along with the additional terms 83 | specified in ``expansion_terms``. Labels are specified either as 84 | ``Multiset`` or as valid arguments to the ``Multiset`` constructor. 85 | This function further requires that ``Multiset``\s consist only of 86 | non-negative integers. 87 | integration_method: ODE solver method to use when computing perturbation terms. 88 | include_imag: List of bools determining whether to keep imaginary components in 89 | the signal approximation. Defaults to True for all signals. 90 | kwargs: Additional arguments to pass to the solver when computing perturbation terms. 91 | """ 92 | model = ExpansionModel( 93 | operators=operators, 94 | rotating_frame=rotating_frame, 95 | dt=dt, 96 | carrier_freqs=carrier_freqs, 97 | chebyshev_orders=chebyshev_orders, 98 | expansion_method="magnus", 99 | expansion_order=expansion_order, 100 | expansion_labels=expansion_labels, 101 | integration_method=integration_method, 102 | include_imag=include_imag, 103 | **kwargs, 104 | ) 105 | super().__init__(model=model) 106 | 107 | def _solve( 108 | self, 109 | t0: float, 110 | n_steps: int, 111 | y0: ArrayLike, 112 | signals: List[Signal], 113 | jax_control_flow: bool = False, 114 | ) -> OdeResult: 115 | ys = None 116 | if jax_control_flow: 117 | 118 | def single_step(x): 119 | return self.model.Udt @ jexpm(self.model.evaluate(x)) 120 | 121 | ys = [y0, _perturbative_solve_jax(single_step, self.model, signals, y0, t0, n_steps)] 122 | else: 123 | 124 | def single_step(coeffs, y): 125 | return self.model.Udt @ expm(self.model.evaluate(coeffs)) @ y 126 | 127 | ys = [y0, _perturbative_solve(single_step, self.model, signals, y0, t0, n_steps)] 128 | 129 | return OdeResult(t=[t0, t0 + n_steps * self.model.dt], y=ys) 130 | -------------------------------------------------------------------------------- /docs/userguide/how_to_use_pulse_schedule_for_jax_jit.rst: -------------------------------------------------------------------------------- 1 | .. _how-to use pulse schedules for jax-jit: 2 | 3 | How-to use pulse schedules generated by Qiskit Pulse with JAX transformations 4 | ============================================================================= 5 | 6 | .. warning:: 7 | 8 | This tutorial supresses ``DeprecationWarning`` instances raised by Qiskit Pulse in `qiskit` 9 | `1.3`. 10 | 11 | .. jupyter-execute:: 12 | :hide-code: 13 | 14 | # a parallelism warning raised by JAX is being raised due to somethign outside of Dynamics 15 | import warnings 16 | warnings.filterwarnings("ignore", message="os.fork") 17 | 18 | # also silence deprecation warnings from pulse 19 | warnings.filterwarnings("ignore", category=DeprecationWarning) 20 | 21 | 22 | Qiskit Pulse enables specification of time-dependence in quantum systems as pulse schedules, built 23 | from sequences of a variety of instructions, including the specification of shaped pulses (see the 24 | detailed API information about `Qiskit pulse API Reference 25 | `__). As of Qiskit 0.40.0, JAX support was added for 26 | the :class:`~qiskit.pulse.library.ScalableSymbolicPulse` class. This user guide entry demonstrates 27 | the technical elements of utilizing this class within JAX-transformable functions. 28 | 29 | .. note:: 30 | At present, only the :class:`~qiskit.pulse.library.ScalableSymbolicPulse` class is supported by 31 | JAX, as the validation present in other pulse types, such as 32 | :class:`~qiskit.pulse.library.Gaussian`, is not JAX-compatible. 33 | 34 | This guide addresses the following topics. See the :ref:`userguide on using JAX ` for a more detailed explanation of how to work with JAX in Qiskit 36 | Dynamics. 37 | 38 | 1. Configure JAX. 39 | 2. How to define a Gaussian pulse using :class:`~qiskit.pulse.library.ScalableSymbolicPulse`. 40 | 3. JAX transforming Pulse to Signal conversion involving 41 | :class:`~qiskit.pulse.library.ScalableSymbolicPulse`. 42 | 43 | 44 | 1. Configure JAX 45 | ---------------- 46 | 47 | First, configure JAX to run on CPU in 64 bit mode. 48 | 49 | .. jupyter-execute:: 50 | 51 | # configure jax to use 64 bit mode 52 | import jax 53 | jax.config.update("jax_enable_x64", True) 54 | 55 | # tell JAX we are using CPU 56 | jax.config.update('jax_platform_name', 'cpu') 57 | 58 | 59 | 2. How to define a Gaussian pulse using :class:`~qiskit.pulse.library.ScalableSymbolicPulse` 60 | -------------------------------------------------------------------------------------------- 61 | 62 | As the standard :class:`~qiskit.pulse.library.Gaussian` pulse is not JAX-compatible, to define a 63 | Gaussian pulse to use in optimization, we need to instantiate a 64 | :class:`~qiskit.pulse.library.ScalableSymbolicPulse` with a Gaussian parameterization. First, define 65 | the symbolic representation in `sympy`. 66 | 67 | .. jupyter-execute:: 68 | 69 | from qiskit import pulse 70 | from qiskit_dynamics.pulse import InstructionToSignals 71 | import sympy as sym 72 | 73 | dt = 0.222 74 | w = 5. 75 | 76 | # Helper function that returns a lifted Gaussian symbolic equation. 77 | def lifted_gaussian( 78 | t: sym.Symbol, 79 | center, 80 | t_zero, 81 | sigma, 82 | ) -> sym.Expr: 83 | t_shifted = (t - center).expand() 84 | t_offset = (t_zero - center).expand() 85 | 86 | gauss = sym.exp(-((t_shifted / sigma) ** 2) / 2) 87 | offset = sym.exp(-((t_offset / sigma) ** 2) / 2) 88 | 89 | return (gauss - offset) / (1 - offset) 90 | 91 | 92 | Next, define the :class:`~qiskit.pulse.library.ScalableSymbolicPulse` using the above expression. 93 | 94 | .. jupyter-execute:: 95 | 96 | _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") 97 | _center = _duration / 2 98 | 99 | envelope_expr = ( 100 | _amp * sym.exp(sym.I * _angle) * lifted_gaussian(_t, _center, _duration + 1, _sigma) 101 | ) 102 | 103 | gaussian_pulse = pulse.ScalableSymbolicPulse( 104 | pulse_type="Gaussian", 105 | duration=160, 106 | amp=0.3, 107 | angle=0, 108 | parameters={"sigma": 40}, 109 | envelope=envelope_expr, 110 | constraints=_sigma > 0, 111 | valid_amp_conditions=sym.Abs(_amp) <= 1.0, 112 | ) 113 | 114 | gaussian_pulse.draw() 115 | 116 | 117 | 3. JAX transforming Pulse to Signal conversion involving :class:`~qiskit.pulse.library.ScalableSymbolicPulse` 118 | ------------------------------------------------------------------------------------------------------------- 119 | 120 | Using a Gaussian pulse as an example, we show that a function involving 121 | :class:`~qiskit.pulse.library.ScalableSymbolicPulse` and the pulse to signal converter can be 122 | JAX-compiled (or more generally, JAX-transformed). 123 | 124 | .. jupyter-execute:: 125 | 126 | # use amplitude as the function argument 127 | def jit_func(amp): 128 | _t, _duration, _amp, _sigma, _angle = sym.symbols("t, duration, amp, sigma, angle") 129 | _center = _duration / 2 130 | 131 | envelope_expr = ( 132 | _amp * sym.exp(sym.I * _angle) * lifted_gaussian(_t, _center, _duration + 1, _sigma) 133 | ) 134 | 135 | # we need to set disable_validation True to enable jax-jitting. 136 | pulse.ScalableSymbolicPulse.disable_validation = True 137 | 138 | gaussian_pulse = pulse.ScalableSymbolicPulse( 139 | pulse_type="Gaussian", 140 | duration=160, 141 | amp=amp, 142 | angle=0, 143 | parameters={"sigma": 40}, 144 | envelope=envelope_expr, 145 | constraints=_sigma > 0, 146 | valid_amp_conditions=sym.Abs(_amp) <= 1.0, 147 | ) 148 | 149 | # build a pulse schedule 150 | with pulse.build() as schedule: 151 | pulse.play(gaussian_pulse, pulse.DriveChannel(0)) 152 | 153 | # convert from a pulse schedule to a list of signals 154 | converter = InstructionToSignals(dt, carriers={"d0": w}) 155 | 156 | return converter.get_signals(schedule)[0].samples 157 | 158 | jax.jit(jit_func)(0.4) 159 | -------------------------------------------------------------------------------- /test/dynamics/solvers/test_jax_odeint.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2020. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Direct tests of jax_odeint 16 | """ 17 | 18 | import numpy as np 19 | 20 | from qiskit_dynamics.solvers.jax_odeint import jax_odeint 21 | 22 | from ..common import JAXTestBase 23 | 24 | try: 25 | import jax.numpy as jnp 26 | from jax.lax import cond 27 | from jax import jit, grad 28 | # pylint: disable=broad-except 29 | except Exception: 30 | pass 31 | 32 | 33 | class TestJaxOdeint(JAXTestBase): 34 | """Test cases for jax_odeint.""" 35 | 36 | def setUp(self): 37 | # pylint: disable=unused-argument 38 | def simple_rhs(t, y): 39 | return cond(t < 1.0, lambda s: s, lambda s: s**2, jnp.array([t])) 40 | 41 | self.simple_rhs = simple_rhs 42 | 43 | def test_t_eval_arg_no_overlap(self): 44 | """Test handling of t_eval when no overlap with t_span.""" 45 | 46 | t_span = np.array([0.0, 2.0]) 47 | t_eval = np.array([1.0, 1.5, 1.7]) 48 | y0 = jnp.array([1.0]) 49 | 50 | results = jax_odeint(self.simple_rhs, t_span, y0, t_eval=t_eval, atol=1e-10, rtol=1e-10) 51 | 52 | self.assertAllClose(t_eval, results.t) 53 | 54 | expected_y = jnp.array( 55 | [ 56 | [1 + 0.5], 57 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 58 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 59 | ] 60 | ) 61 | 62 | self.assertAllClose(expected_y, results.y) 63 | 64 | def test_t_eval_arg_no_overlap_backwards(self): 65 | """Test handling of t_eval when no overlap with t_span with backwards integration.""" 66 | 67 | t_span = np.array([2.0, 0.0]) 68 | t_eval = np.array([1.7, 1.5, 1.0]) 69 | y0 = jnp.array([1 + 0.5 + (2.0**3 - 1.0**3) / 3]) 70 | 71 | results = jax_odeint(self.simple_rhs, t_span, y0, t_eval=t_eval, atol=1e-10, rtol=1e-10) 72 | 73 | self.assertAllClose(t_eval, results.t) 74 | 75 | expected_y = jnp.array( 76 | [ 77 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 78 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 79 | [1 + 0.5], 80 | ] 81 | ) 82 | 83 | self.assertAllClose(expected_y, results.y) 84 | 85 | def test_t_eval_arg_overlap(self): 86 | """Test handling of t_eval with overlap with t_span.""" 87 | 88 | t_span = np.array([0.0, 2.0]) 89 | t_eval = np.array([1.0, 1.5, 1.7, 2.0]) 90 | y0 = jnp.array([1.0]) 91 | 92 | results = jax_odeint(self.simple_rhs, t_span, y0, t_eval=t_eval, atol=1e-10, rtol=1e-10) 93 | 94 | self.assertAllClose(t_eval, results.t) 95 | 96 | expected_y = jnp.array( 97 | [ 98 | [1 + 0.5], 99 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 100 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 101 | [1 + 0.5 + (2**3 - 1.0**3) / 3], 102 | ] 103 | ) 104 | 105 | self.assertAllClose(expected_y, results.y) 106 | 107 | def test_t_eval_arg_overlap_backwards(self): 108 | """Test handling of t_eval with overlap with t_span with backwards integration.""" 109 | 110 | t_span = np.array([2.0, 0.0]) 111 | t_eval = np.array([2.0, 1.7, 1.5, 1.0]) 112 | y0 = jnp.array([1 + 0.5 + (2.0**3 - 1.0**3) / 3]) 113 | 114 | results = jax_odeint(self.simple_rhs, t_span, y0, t_eval=t_eval, atol=1e-10, rtol=1e-10) 115 | 116 | self.assertAllClose(t_eval, results.t) 117 | 118 | expected_y = jnp.array( 119 | [ 120 | [1 + 0.5 + (2**3 - 1.0**3) / 3], 121 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 122 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 123 | [1 + 0.5], 124 | ] 125 | ) 126 | self.assertAllClose(expected_y, results.y) 127 | 128 | def test_transformations_w_t_span_t_eval_no_overlap(self): 129 | """Test compiling/grad if both t_span and t_eval are specified.""" 130 | 131 | t_span = np.array([0.0, 2.0]) 132 | t_eval = np.array([1.0, 1.5, 1.7]) 133 | y0 = jnp.array([1.0], dtype=complex) 134 | 135 | def func(t_s, t_e): 136 | results = jax_odeint(self.simple_rhs, t_s, y0, t_eval=t_e, atol=1e-10, rtol=1e-10) 137 | return results.t, results.y 138 | 139 | t, y = jit(func)(t_span, t_eval) 140 | 141 | self.assertAllClose(t_eval, t) 142 | 143 | expected_y = jnp.array( 144 | [ 145 | [1 + 0.5], 146 | [1 + 0.5 + (1.5**3 - 1.0**3) / 3], 147 | [1 + 0.5 + (1.7**3 - 1.0**3) / 3], 148 | ] 149 | ) 150 | self.assertAllClose(expected_y, y) 151 | 152 | jit_grad_func = jit(grad(lambda a: func(t_span, a)[1][-1].real.sum())) 153 | out = jit_grad_func(t_eval) 154 | self.assertAllClose(out, np.array([0.0, 0.0, 1.7**2])) 155 | 156 | def test_transformations_t_eval_arg_overlap(self): 157 | """Test gradient transformation when t_eval has overlap with t_span.""" 158 | 159 | t_span = np.array([0.0, 2.0]) 160 | t_eval = np.array([1.0, 1.5, 1.7, 2.0]) 161 | y0 = jnp.array([1.0]) 162 | 163 | def sim_function(a): 164 | def rhs(t, y): 165 | return (a**2) * self.simple_rhs(t, y) 166 | 167 | results = jax_odeint(rhs, t_span, y0, t_eval=t_eval, atol=1e-10, rtol=1e-10) 168 | return results.y[-1].real.sum() 169 | 170 | self.assertAllClose( 171 | jit(grad(lambda a: sim_function(a).real.sum()))(2.0), 172 | 4 * (0.5 + (2.0**3 - 1.0**3) / 3), 173 | ) 174 | 175 | def test_empty_integration(self): 176 | """Test case for if t_span==[0., 0.].""" 177 | 178 | result = jax_odeint(rhs=self.simple_rhs, t_span=np.array([0.0, 0.0]), y0=np.array([2.1])) 179 | self.assertAllClose(result.y, jnp.array([[2.1], [2.1]])) 180 | -------------------------------------------------------------------------------- /qiskit_dynamics/models/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This code is part of Qiskit. 4 | # 5 | # (C) Copyright IBM 2020. 6 | # 7 | # This code is licensed under the Apache License, Version 2.0. You may 8 | # obtain a copy of this license in the LICENSE.txt file in the root directory 9 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 10 | # 11 | # Any modifications or derivative works of this code must retain this 12 | # copyright notice, and modified files need to carry a notice indicating 13 | # that they have been altered from the originals. 14 | 15 | r""" 16 | ====================================== 17 | Models (:mod:`qiskit_dynamics.models`) 18 | ====================================== 19 | 20 | .. currentmodule:: qiskit_dynamics.models 21 | 22 | This module contains classes for constructing the right-hand side of an ordinary differential 23 | equations. In this package, a "model of a quantum system" means a description of a differential 24 | equation used to model a physical quantum system, which in this case is either the *Schrodinger 25 | equation*: 26 | 27 | .. math:: 28 | \dot{y}(t) = -i H(t)y(t), 29 | 30 | where :math:`H(t)` is the Hamiltonian, or the *Lindblad equation*: 31 | 32 | .. math:: 33 | \dot{\rho}(t) = -i[H(t), \rho(t)] + 34 | \sum_j g_j(t) \left(L_j\rho(t)L_j^\dagger - 35 | \frac{1}{2}\{L_j^\dagger L_j, \rho(t)\}\right), 36 | 37 | where the second term is called the *dissipator* term. Each :math:`L_j` is a dissipation operator 38 | *dissipator*, and :math:`[\cdot, \cdot]` and :math:`\{\cdot, \cdot\}` are, respectively, the matrix 39 | commutator and anti-commutator. 40 | 41 | The classes for representing the Schrodinger and Lindblad equations are, respectively, 42 | :class:`~qiskit_dynamics.models.HamiltonianModel` and 43 | :class:`~qiskit_dynamics.models.LindbladModel`. Model classes primarily serve a *computational* 44 | purpose, and expose functions for evaluating model expressions, such as :math:`t \mapsto H(t)` or 45 | :math:`t,y \mapsto -iH(t)y` in the case of a Hamiltonian, and with similar functionality for 46 | :class:`~qiskit_dynamics.models.LindbladModel`. 47 | 48 | .. _Rotating frames: 49 | 50 | Rotating frames 51 | =============== 52 | 53 | Frame transformations are a common technique for solving time-dependent quantum differential 54 | equations. For example, for a Hamiltonian, this corresponds to the transformation 55 | 56 | .. math:: 57 | H(t) \mapsto e^{iH_0t}(H(t) - H_0)e^{-iH_0t}, 58 | 59 | for a Hermitian operator :math:`H_0` called the *frame operator*. 60 | 61 | .. note:: 62 | The *frame operator* is commonly equivalently expressed as the corresponding anti-Hermitian 63 | operator under the association :math:`F = -iH_0`. This package refers to either :math:`F` or 64 | :math:`H_0` as the *frame operator*, with this association being understood. 65 | 66 | Any model class can be transformed into a rotating frame by setting the 67 | :attr:`~qiskit_dynamics.models.BaseGeneratorModel.rotating_frame` property: 68 | 69 | .. code-block:: python 70 | 71 | model.rotating_frame = frame_operator 72 | 73 | where ``frame_operator`` is a specification of either :math:`H_0` or :math:`F = -iH_0` (see the 74 | documentation for :class:`~qiskit_dynamics.models.RotatingFrame` for valid types and behaviours). 75 | Setting this property modifies the behaviour of the evaluation functions, e.g. a 76 | :class:`~qiskit_dynamics.models.HamiltonianModel` will compute :math:`e^{-tF}(-iH(t) - F)e^{tF}` in 77 | place of :math:`H(t)`. :class:`~qiskit_dynamics.models.LindbladModel` has similar behaviour. 78 | 79 | Internally, the model classes make use of the :class:`~qiskit_dynamics.models.RotatingFrame` class, 80 | which is instantiated when the ``rotating_frame`` property is set. This class contains helper 81 | functions for transforming various objects into and out of the rotating frame. This class works 82 | directly with the anti-Hermitian form :math:`F = -iH_0`, however, it can also be instantiated with a 83 | Hermitian operator :math:`H_0` from which :math:`F` is automatically constructed. 84 | 85 | Rotating wave approximation 86 | =========================== 87 | 88 | The rotating wave approximation (RWA) is a transformation in which rapidly oscillating 89 | time-dependent components, above a given cutoff frequency, are removed from a model. This 90 | transformation is implemented in :meth:`~qiskit_dynamics.models.rotating_wave_approximation`, see 91 | its documentation for details. 92 | 93 | .. _model evaluation: 94 | 95 | Controlling model evaluation: array libraries and vectorization 96 | =============================================================== 97 | 98 | The underlying array library used by any model class can be controlled via the ``array_library`` 99 | instantiation argument. The model will store the underlying arrays using the specified library, and 100 | use this library to evaluate the model. See the :mod:`.arraylias` submodule API documentation for a 101 | list of ``array_library`` options. If unspecified, the model will use the general dispatching rules 102 | of the configured aliases in :mod:`.arraylias` to determine which library to use based on how the 103 | operators are specified. 104 | 105 | Additionally, the :class:`.LindbladModel` class can be set to store and evaluate the Lindblad 106 | equation in a matrix-vector vectorized format using the ``vectorized`` instatiation argument. 107 | 108 | .. note:: 109 | 110 | When setting a rotating frame, models internally store their operators in the basis in which the 111 | frame operator is diagonal. In general, sparsity of an operator is not perserved by basis 112 | transformations. Hence, preserving internal sparsity with rotating frames requires more 113 | restrictive choice of frames. For example, diagonal frame operators exactly preserve sparsity. 114 | 115 | 116 | Model classes 117 | ============= 118 | 119 | .. autosummary:: 120 | :toctree: ../stubs/ 121 | 122 | HamiltonianModel 123 | LindbladModel 124 | GeneratorModel 125 | 126 | Model transformations 127 | ===================== 128 | 129 | .. autosummary:: 130 | :toctree: ../stubs/ 131 | 132 | RotatingFrame 133 | rotating_wave_approximation 134 | """ 135 | 136 | from .rotating_frame import RotatingFrame 137 | from .generator_model import BaseGeneratorModel, GeneratorModel 138 | from .hamiltonian_model import HamiltonianModel 139 | from .lindblad_model import LindbladModel 140 | from .rotating_wave_approximation import rotating_wave_approximation 141 | -------------------------------------------------------------------------------- /qiskit_dynamics/perturbation/perturbation_utils.py: -------------------------------------------------------------------------------- 1 | # This code is part of Qiskit. 2 | # 3 | # (C) Copyright IBM 2022. 4 | # 5 | # This code is licensed under the Apache License, Version 2.0. You may 6 | # obtain a copy of this license in the LICENSE.txt file in the root directory 7 | # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # Any modifications or derivative works of this code must retain this 10 | # copyright notice, and modified files need to carry a notice indicating 11 | # that they have been altered from the originals. 12 | # pylint: disable=invalid-name 13 | 14 | """ 15 | Utility functions for perturbation module. 16 | """ 17 | 18 | from typing import List, Optional, Union 19 | from itertools import product 20 | 21 | from multiset import Multiset 22 | 23 | from qiskit import QiskitError 24 | 25 | from qiskit_dynamics.perturbation.multiset_utils import ( 26 | _validate_non_negative_ints, 27 | _clean_multisets, 28 | ) 29 | 30 | 31 | def _merge_multiset_expansion_order_labels( 32 | perturbation_labels: Union[List[int], List[Multiset]], 33 | expansion_order: Optional[int] = None, 34 | expansion_labels: Optional[List[Multiset]] = None, 35 | ) -> List[Multiset]: 36 | """Helper function for merging expansion_order and expansion_labels arguments in the multiset 37 | case for functions that require specifying expansion terms to compute. 38 | 39 | Generates a list of all Multisets of a given size given by expansion_order, and includes any 40 | additional multisets specified by expansion_labels. The elements of the multisets are drawn from 41 | perturbation_labels, which is either a list of ints, or a list of Multisets from which the ints 42 | are drawn. 43 | 44 | At least one of expansion_order or expansion_labels must be specified. Accepts only multisets 45 | and labels consisting of non-negative integers. 46 | 47 | Args: 48 | perturbation_labels: Specification of elements of the multisets to generate. 49 | expansion_order: Size of multisets to generate. 50 | expansion_labels: Additional multisets to keep. 51 | Returns: 52 | List of multisets merging expansion_order and expansion_labels. 53 | Raises: 54 | QiskitError: If neither expansion_order nor expansion_labels is specified. 55 | """ 56 | 57 | # validate 58 | if expansion_order is None and expansion_labels is None: 59 | raise QiskitError("At least one of expansion_order or expansion_labels must be specified.") 60 | 61 | # clean expansion_labels if specified 62 | if expansion_labels is not None: 63 | expansion_labels = _clean_multisets(expansion_labels) 64 | for label in expansion_labels: 65 | _validate_non_negative_ints(label) 66 | 67 | # if no expansion_order passed, just return expansion_labels 68 | if expansion_order is None: 69 | return expansion_labels 70 | 71 | # generate all multisets of size expansion_order with entries in perturbation_labels 72 | 73 | # get unique multiset elements 74 | unique_labels = set() 75 | for perturbation_label in perturbation_labels: 76 | if isinstance(perturbation_label, int): 77 | unique_labels = unique_labels.union({perturbation_label}) 78 | else: 79 | perturbation_label = Multiset(perturbation_label) 80 | _validate_non_negative_ints(perturbation_label) 81 | unique_labels = unique_labels.union(perturbation_label.distinct_elements()) 82 | unique_labels = list(unique_labels) 83 | unique_labels.sort() 84 | 85 | # get all possible counts for multisets of a given size with a given number of labels 86 | all_multiset_counts = _ordered_partitions(expansion_order, len(unique_labels)) 87 | 88 | # create all such multisets 89 | output_multisets = [] 90 | for multiset_count in all_multiset_counts: 91 | output_multisets.append(Multiset(dict(zip(unique_labels, multiset_count)))) 92 | 93 | if expansion_labels is not None: 94 | output_multisets = output_multisets + expansion_labels 95 | 96 | return _clean_multisets(output_multisets) 97 | 98 | 99 | def _merge_list_expansion_order_labels( 100 | perturbation_num: int, 101 | expansion_order: Optional[int] = None, 102 | expansion_labels: Optional[List[List[int]]] = None, 103 | ) -> List[int]: 104 | """Helper function for merging expansion_order and expansion_labels arguments in the list case 105 | for functions that require specifying expansion terms to compute. 106 | 107 | Generates a list of all lists of integers in [0, ..., perturbation_num - 1] of a given size 108 | given by expansion_order, and includes any additional lists specified by expansion_labels. 109 | 110 | At least one of expansion_order or expansion_labels must be specified. 111 | 112 | Args: 113 | perturbation_num: Number of perturbations. 114 | expansion_order: Size of lists to generate. 115 | expansion_labels: Additional lists to keep. 116 | Returns: 117 | List of multisets merging expansion_order and expansion_labels. 118 | Raises: 119 | QiskitError: If neither expansion_order nor expansion_labels is specified. 120 | """ 121 | 122 | # validate 123 | if expansion_order is None and expansion_labels is None: 124 | raise QiskitError("At least one of expansion_order or expansion_labels must be specified.") 125 | 126 | if expansion_order is None: 127 | return expansion_labels 128 | 129 | # generate all possible lists of length expansion_order with integers between 130 | # [0, ..., perturbation_num - 1] 131 | unique_indices = list(range(perturbation_num)) 132 | output_lists = list(map(list, product(unique_indices, repeat=expansion_order))) 133 | 134 | if expansion_labels is not None: 135 | for label in expansion_labels: 136 | if label not in output_lists: 137 | output_lists.append(label) 138 | 139 | output_lists.sort(key=str) 140 | output_lists.sort(key=len) 141 | 142 | return output_lists 143 | 144 | 145 | def _ordered_partitions(n: int, length: int) -> List[List[int]]: 146 | """Return the ordered integer partitions of n of a given length, including zeros. 147 | 148 | Args: 149 | n: Number to partition. 150 | length: Length of partitions. 151 | Returns: 152 | Ordered partitions. 153 | """ 154 | 155 | if length == 1: 156 | return [[n]] 157 | 158 | full_list = [] 159 | for k in range(n + 1): 160 | full_list = full_list + [[k] + part for part in _ordered_partitions(n - k, length - 1)] 161 | 162 | return full_list 163 | -------------------------------------------------------------------------------- /releasenotes/notes/0.4/0.4-summary-3de98711c3b7aa09.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | This release primarily focuses on tooling for simulating `Qiskit Pulse 4 | `__ schedules. The major changes related to 5 | this are: 6 | - This release adds the :class:`.DynamicsBackend` class, which provides a 7 | :class:`~qiskit.providers.BackendV2` interface for running simulations. Once constructed and 8 | configured, the :meth:`.DynamicsBackend.run` method can be used to simulate schedules or 9 | circuits at the pulse level, returning measurement results in the same way as a real 10 | backend. See the new :ref:`Simulating backends at the pulse-level with DynamicsBackend 11 | ` tutorial for how to set up the class for different applications. 12 | - This release modifies the schedule to signal converter :class:`.InstructionToSignals` to 13 | enable JAX transforms of simulations of pulse schedules built using 14 | :class:`~qiskit.pulse.library.ScalableSymbolicPulse`. See the user guide entry on the 15 | technicalities and limitiations of utilizing :ref:`JAX-compatible pulse schedules `, as well as the :ref:`tutorial on control sequence 17 | optimization `, which has been updated to include a section on 18 | gradient optimization of pulse schedules. This functionality requires ``qiskit-terra`` 19 | 0.23.0, which is now the minimum required version. 20 | 21 | As of this release, Qiskit Dynamics now supports Python 3.11, and has dropped support of Python 22 | 3.7. Additionally, higher-order versions of existing matrix-exponential solvers have been added, 23 | and some arguments and methods that were deprecated in version 0.3 have been removed. 24 | 25 | features: 26 | - | 27 | Adds Python 3.11 support. 28 | - | 29 | Adds new class :class:`.DynamicsBackend`, which provides a 30 | :class:`~qiskit.providers.backend.BackendV2` interface for running simulations with Qiskit 31 | Dynamics. This class can either be instantiated with a :class:`.Solver` instance configured for 32 | Pulse simulation, or through the :meth:`.DynamicsBackend.from_backend`, which builds an instance 33 | using the Hamiltonian description provided in an IBM backend. 34 | - | 35 | Adds the ``magnus_order`` argument to exponential based solvers ``"scipy_expm"`` and 36 | ``"jax_expm"``. Accepts values in ``[1, 2, 3]`` corresponding, respectively, to the 2nd, 4th, 37 | and 6th order methods described in Section 5.4 of :footcite:`blanes_magnus_2009`. 38 | - | 39 | The logic of :class:`.InstructionToSignals` has been updated to be JAX-compatible when 40 | converting pulse schedules containing only :class:`.ScalableSymbolicPulse` pulses. See the new 41 | user guide entry :ref:`JAX-compatible pulse schedules ` 42 | for details. 43 | issues: 44 | - | 45 | JAX release 0.4.4 modified the internals of ``jit`` compilation, resulting in errors when 46 | attempting to automatically differentiate and compile calls to :func:`.solve_lmde_perturbation`. 47 | This is a bug in JAX, and is discussed in `this discussion thread 48 | `__. Until this is fixed, if using this 49 | functionality, JAX can be reverted to the old mode of compilation by executing the following 50 | code before importing JAX: 51 | 52 | .. code-block:: python 53 | 54 | import os 55 | os.environ["JAX_JIT_PJIT_API_MERGE"] = "0" 56 | upgrade: 57 | - | 58 | Starting with this release, Python 3.7 is no longer supported. 59 | - | 60 | The minimum ``qiskit-terra`` version has been increased to 0.23.0. 61 | - | 62 | The deprecated ``list`` formatting for the ``carriers`` argument to 63 | :class:`.InstructionToSignals` is no longer supported. All code utilizing this argument must be 64 | converted to the dictionary input format. 65 | - | 66 | The deprecated :class:`.Solver` init arguments ``hamiltonian_signals`` and 67 | ``dissipator_signals`` have been removed. Signals in the model can now only be passed to the 68 | :meth:`.Solver.solve` method. 69 | - | 70 | The deprecated ``Solver.signals`` property has been removed. The model stored in a 71 | :class:`.Solver` instance no longer stores signals. 72 | - | 73 | The deprecated :meth:`.Solver.copy` method has been removed. There is no replacement for this 74 | functionality; the new intended mode of operation for :class:`.Solver` is to be immutable, and 75 | hence there is no longer any need to create copies. 76 | fixes: 77 | - | 78 | :class:`.InstructionToSignals` has been updated to fix issues with phase accumulation resulting 79 | from :class:`~qiskit.pulse.instructions.SetFrequency` and 80 | :class:`~qiskit.pulse.instructions.ShiftFrequency` instructions. A phase accumulation term has 81 | been added so that the digitized carrier frequency is continuous across these instructions. A 82 | related phase error when generating samples for channels subject to 83 | :class:`~qiskit.pulse.instructions.SetFrequency` and 84 | :class:`~qiskit.pulse.instructions.ShiftFrequency` instructions has also been fixed. (`#140 85 | `__) 86 | - | 87 | :class:`.InstructionToSignals` has been updated to fix an error when parsing schedules that 88 | include barrier instructions. `#202 `__) 89 | - | 90 | Fixes a bug in the automatic jit-compilation of :meth:`Solver.solve` when using the ``t_eval`` 91 | kwarg with a JAX method and ``Array.default_backend() == 'jax'``. The bug is fixed by updating 92 | the time-argument handling for the ``"jax_odeint"`` and Diffrax methods. The automatic jitting 93 | is now disabled for all other JAX methods. (`#122 94 | `__ and `#175 95 | `__) 96 | - | 97 | The ``jax.Array`` class has been added to the dispatcher for compatibility with JAX 0.4. 98 | (`#162 `__) 99 | - | 100 | Validation checks have been added to the :class:`.DysonSolver` and :class:`.MagnusSolver` 101 | classes to ensure that the ``operators``, ``carrier_freqs``, and ``chebyshev_orders`` arguments 102 | have compatible shapes. This fixes bugs in the behaviour of these classes when implicit 103 | assumptions about array shapes were violated. (`#160 104 | `__) 105 | 106 | -------------------------------------------------------------------------------- /releasenotes/notes/0.3/0.3-summary-8bdd0273e78a9795.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | prelude: > 3 | This release has several major changes: 4 | - Adds perturbation theory sub module for computing and utilizing the Dyson series 5 | and Magnus expansion in a multi-variable setting. These tools can be applied to both 6 | Hamiltonian and Lindblad dynamics, and more generally to any linear matrix differential 7 | equation. 8 | - Introduces functionality for simulating Qiskit Pulse schedules using the 9 | :class:`.Solver` class. 10 | - Adds new solver methods accessible through :func:`.solve_ode` and :func:`.solve_lmde`. 11 | features: 12 | - | 13 | Adds support for Python 3.10. Starting with this release 14 | Qiskit Dynamics now supports running with Python 3.7 through 3.10. 15 | - | 16 | The :meth:`.Solver.solve` method can now run multiple simulations in a single call by 17 | specifying one or more of the ``t_span``, ``y0``, and ``signals`` 18 | arguments as lists of valid single inputs. This supports specifying all as a list of the 19 | same length (with a different value for each to be used in each simulation), or a subset 20 | can be specified with lists of the same length, with the other arguments giving only a single 21 | value (to be used repeatedly while varying the other parameters). Results are returned as a 22 | list of the results of the individual simulations. The list of simulations is performed 23 | in a serial loop. 24 | - | 25 | The :class:`.Solver` class has been upgraded to enable Qiskit Pulse schedule simulation. 26 | :class:`.Solver` can be configured to interpet pulse schedules at instantiation, 27 | and schedules can be simulated by passing them via the ``signals`` argument to 28 | :meth:`.Solver.solve`. 29 | - | 30 | Adds function :func:`.solve_lmde_perturbation` for computing Dyson series and Magnus 31 | expansion terms, a results container class :class:`.PerturbationResults`, and the 32 | class :class:`.ArrayPolynomial` for evaluating and performing computations with 33 | multi-variable array-valued polynomials (used in the solver classes of the next item). 34 | - | 35 | Adds classes :class:`.DysonSolver` and :class:`.MagnusSolver` implementing perturbation 36 | expansion-based fixed-step solvers for linear matrix differential equations. 37 | - | 38 | Adds support for solvers from the `diffrax package 39 | `_. 40 | To call a diffrax solver, pass it as the ``method`` kwarg to :func:`solve_lmde`. For example:: 41 | 42 | from diffrax import Dopri5 43 | from qiskit-dynamics import solve_ode 44 | 45 | results = solve_ode(rhs=rhs, t_span=t_span, y0=y0, method=Dopri5()) 46 | - | 47 | Adds new methods ``'lanczos_diag'`` and the JAX version ``'jax_lanczos_diag'`` 48 | to `solve_lmde`. This is an approximate matrix-exponentiation method, based 49 | on diagonalizing an approximate projection of the generator to a small subspace 50 | (the Krylov Subspace), obtained via the Lanczos algorithm. The size of this 51 | subspace is determined by the parameter ``k_dim``, which controls the accuracy of the 52 | approximation. The method is recommended for sparse systems with large dimension. 53 | upgrade: 54 | - | 55 | The :class:`.Solver` API has been updated so that ``signals`` are passed 56 | as a kwarg to the :meth:`.Solver.solve` method rather than being set at 57 | instantiation. A new instantiation argument, ``rwa_carrier_freqs``, has been 58 | added to control behaviour of the rotating wave approximation in the absence 59 | of signals being specified. 60 | - | 61 | The ``InstructionsToSignals`` converter has been updated to allow channel 62 | filtering via an optional instantiation argument named ``channels``. 63 | If set, the converter only returns signals for those channels, specified by name such 64 | as ``"d0"``, in the same order in which they are given. Furthermore, the 65 | ``carriers`` argument is updated to a dictionary to make the mapping from 66 | channel names to carrier frequencies more obvious. 67 | - | 68 | The following deprecated imports from the dispatch module have have been 69 | removed from the package: 70 | ``qiskit_dynamics.dispatch.Array``, 71 | ``qiskit_dynamics.dispatch.wrap``, 72 | ``qiskit_dynamics.dispatch.set_default_backend``, 73 | ``qiskit_dynamics.dispatch.default_backend``, 74 | ``qiskit_dynamics.dispatch.available_backends``, 75 | ``qiskit_dynamics.dispatch.backend_types``. 76 | 77 | ``Array`` and ``wrap`` and must now be imported from the 78 | :mod:`qiskit_dynamics.array` module, and the other functions must be 79 | accessed as :class:`.Array` class methods. 80 | - | 81 | Adds ``multiset`` package as a dependency. 82 | deprecations: 83 | - | 84 | Passing the ``carriers`` argument to ``InstructionToSignals`` as a ``list`` 85 | has been deprecated and will be removed next release. The ``carriers`` argument 86 | should now be passed as a dictionary with channel names as keys and carrier 87 | frequencies as values. This option will be removed in the 0.4.0 release. 88 | - | 89 | The ``signals`` property and ``hamiltonian_signals`` and ``dissipator_signals`` init kwargs 90 | of :class:`.Solver` have been deprecated. Signals are now specified as a kwarg to the 91 | :meth:`.Solver.solve` method. The deprecated functionality is scheduled to be removed in the 92 | 0.4.0 release. 93 | - | 94 | The :meth:`.Solver.copy` method has been deprecated and is scheduled to be removed 95 | in the 0.4.0 release. 96 | fixes: 97 | - | 98 | Fixes the :meth:`.Solver.solve` method to preserve dimensionality data of the input 99 | states when producing output states. E.g. if ``y0`` is a ``Statevector``, the output 100 | states ``yf`` will satisfy ``yf.dims() == y0.dims()``. 101 | - | 102 | The ``rotating_wave_approximation`` function has been fixed in the case of 103 | the ``model`` argument being a ``LindbladModel`` with ``return_signal_map=True``. 104 | The returned signal mapping function was erroneously defined to take two inputs, 105 | one for Hamiltonian signals and one for dissipator signals. This behaviour has been updated 106 | to be consistent with the documentation, which states that, in general, this function accepts 107 | only a single argument (in this case a tuple storing both sets of signals). 108 | - | 109 | ``RotatingFrame.operator_into_frame_basis`` and ``RotatingFrame.operator_out_of_frame_basis`` 110 | were fixed to work on lists of scipy CSR matrices. 111 | other: 112 | - | 113 | The ``DiscreteSignal.envelope`` function has been changed so that if ``t`` is 114 | outside the interval defined by ``start_time`` and ``start_time + dt * len(samples)`` 115 | it will return ``0.0``. 116 | --------------------------------------------------------------------------------