From 50559678f5ab5f34e0127f6f4b1fdf06b838566b Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 11 Mar 2019 18:49:30 -0400 Subject: [PATCH 01/31] framechange --- CMakeLists.txt | 1 + qiskit/providers/aer/pulse/__init__.py | 6 ++ .../providers/aer/pulse/cython/CMakeLists.txt | 5 ++ qiskit/providers/aer/pulse/cython/__init__.py | 6 ++ .../aer/pulse/cython/frame_change.pyx | 64 +++++++++++++++++++ 5 files changed, 82 insertions(+) create mode 100644 qiskit/providers/aer/pulse/__init__.py create mode 100644 qiskit/providers/aer/pulse/cython/CMakeLists.txt create mode 100644 qiskit/providers/aer/pulse/cython/__init__.py create mode 100644 qiskit/providers/aer/pulse/cython/frame_change.pyx diff --git a/CMakeLists.txt b/CMakeLists.txt index 05be51457d..8a7854b4ea 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ set(AER_LIBRARIES # Cython build is only enabled if building through scikit-build. if(SKBUILD) # Terra Addon build add_subdirectory(qiskit/providers/aer/backends/wrappers) + add_subdirectory(qiskit/providers/aer/pulse/cython) else() # Standalone build add_executable(qasm_simulator ${AER_SIMULATOR_CPP_MAIN}) set_target_properties(qasm_simulator PROPERTIES diff --git a/qiskit/providers/aer/pulse/__init__.py b/qiskit/providers/aer/pulse/__init__.py new file mode 100644 index 0000000000..206e02adea --- /dev/null +++ b/qiskit/providers/aer/pulse/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt new file mode 100644 index 0000000000..5d43a3a140 --- /dev/null +++ b/qiskit/providers/aer/pulse/cython/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(Cython REQUIRED) + +# Frame change + +add_cython_target(frame_change frame_change.pyx CXX) diff --git a/qiskit/providers/aer/pulse/cython/__init__.py b/qiskit/providers/aer/pulse/cython/__init__.py new file mode 100644 index 0000000000..206e02adea --- /dev/null +++ b/qiskit/providers/aer/pulse/cython/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. diff --git a/qiskit/providers/aer/pulse/cython/frame_change.pyx b/qiskit/providers/aer/pulse/cython/frame_change.pyx new file mode 100644 index 0000000000..473ebb6043 --- /dev/null +++ b/qiskit/providers/aer/pulse/cython/frame_change.pyx @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython + +cdef extern from "" namespace "std" nogil: + double complex exp(double complex x) + +@cython.cdivision(True) +@cython.boundscheck(False) +cdef void compute_fc_value(double t, int chan_idx, double[::1] fc_array, + complex[::1] cnt_fc, + unsigned int[::1] fc_idx, + unsigned int[::1] register): + """ + Computes the frame change value at time `t` on the + channel labeled by `chan_idx`. The result is stored + in the current frame change array at `cnt_fc[chan_idx]`. + + Args: + t (double): Current time. + chan_idx (int): Index of channel. + fc_array (ndarray): Frame change array of doubles. + cnt_fc (ndarray): Complex values for current frame change values. + fc_idx (ndarray): Ints for frame change index. + register (ndarray): Classical register of ints. + + """ + cdef unsigned int arr_len = fc_array.shape[0] + cdef unsigned int do_fc + + if fc_idx[chan_idx] < arr_len: + while t >= fc_array[fc_idx[chan_idx]]: + do_fc = 1 + # Check if FC is conditioned on register + if fc_array[fc_idx[chan_idx]+2] >= 0: + # If condition not satisfied no do FC + if not register[fc_array[fc_idx[chan_idx]+2]]: + do_fc = 0 + if do_fc: + # Update the frame change value + cnt_fc[chan_idx] *= exp(1j*fc_array[fc_idx[chan_idx]+1]) + # update the index + fc_idx[chan_idx] += 3 + # check if we hit the end + if fc_idx[chan_idx] == arr_len: + break + + +def check_fc_compute(double t, int chan_idx, double[::1] fc_array, + complex[::1] cnt_fc, + unsigned int[::1] fc_idx, + unsigned int[::1] register): + """ + Python function to check the compute_fc_value is working. + """ + compute_fc_value(t, chan_idx, fc_array, cnt_fc, fc_idx, register) \ No newline at end of file From 6deb19bb9a0cceab5fc3da0b25d8515a01c504af Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 12 Mar 2019 16:21:22 -0400 Subject: [PATCH 02/31] updates --- qiskit/providers/aer/pulse/cython/CMakeLists.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt index 5d43a3a140..7f147b4707 100644 --- a/qiskit/providers/aer/pulse/cython/CMakeLists.txt +++ b/qiskit/providers/aer/pulse/cython/CMakeLists.txt @@ -1,5 +1,17 @@ find_package(Cython REQUIRED) +find_package(PythonLibs REQUIRED) +find_package(NumPy REQUIRED) -# Frame change +message(STATUS "Numpy include dir: ${NumPy_INCLUDE_DIRS}") +# Frame change add_cython_target(frame_change frame_change.pyx CXX) + +add_library(frame_change MODULE ${frame_change}) +target_link_libraries(frame_change + ${PYTHON_LIBRARIES}) +set_target_properties(frame_change PROPERTIES + LINKER_LANGUAGE CXX + CXX_STANDARD 11) +target_include_directories(frame_change +PRIVATE ${PYTHON_INCLUDE_DIRS}) \ No newline at end of file From 72c434a314466054e19a48f54486a7baae4c590e Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 12 Mar 2019 16:39:26 -0400 Subject: [PATCH 03/31] updates --- .../providers/aer/pulse/cython/CMakeLists.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt index 7f147b4707..5e75ab1c86 100644 --- a/qiskit/providers/aer/pulse/cython/CMakeLists.txt +++ b/qiskit/providers/aer/pulse/cython/CMakeLists.txt @@ -1,4 +1,5 @@ find_package(Cython REQUIRED) +find_package(PythonExtensions REQUIRED) find_package(PythonLibs REQUIRED) find_package(NumPy REQUIRED) @@ -14,4 +15,18 @@ set_target_properties(frame_change PROPERTIES LINKER_LANGUAGE CXX CXX_STANDARD 11) target_include_directories(frame_change -PRIVATE ${PYTHON_INCLUDE_DIRS}) \ No newline at end of file +PRIVATE ${PYTHON_INCLUDE_DIRS}) + +python_extension_module(frame_change + FORWARD_DECL_MODULES_VAR fdecl_module_list) + +python_modules_header(modules + FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) +include_directories(${modules_INCLUDE_DIRS}) + +if(APPLE) + set_target_properties(frame_change PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + unset(PYTHON_LIBRARIES) +endif() + +install(TARGETS frame_change LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) \ No newline at end of file From 720bdefcf4a60dab7f60aec00322cc3ca19e7699 Mon Sep 17 00:00:00 2001 From: Juan Gomez Date: Tue, 12 Mar 2019 22:04:55 +0100 Subject: [PATCH 04/31] Fixing CMake file for pulse simulator --- .../providers/aer/pulse/cython/CMakeLists.txt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt index 5e75ab1c86..a5d47ce676 100644 --- a/qiskit/providers/aer/pulse/cython/CMakeLists.txt +++ b/qiskit/providers/aer/pulse/cython/CMakeLists.txt @@ -7,26 +7,26 @@ message(STATUS "Numpy include dir: ${NumPy_INCLUDE_DIRS}") # Frame change add_cython_target(frame_change frame_change.pyx CXX) - add_library(frame_change MODULE ${frame_change}) -target_link_libraries(frame_change - ${PYTHON_LIBRARIES}) set_target_properties(frame_change PROPERTIES LINKER_LANGUAGE CXX - CXX_STANDARD 11) + CXX_STANDARD 14) +if(APPLE) + set_target_properties(frame_change PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + unset(PYTHON_LIBRARIES) +endif() +target_link_libraries(frame_change + ${PYTHON_LIBRARIES}) + target_include_directories(frame_change -PRIVATE ${PYTHON_INCLUDE_DIRS}) + PRIVATE ${PYTHON_INCLUDE_DIRS}) python_extension_module(frame_change FORWARD_DECL_MODULES_VAR fdecl_module_list) python_modules_header(modules FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) -include_directories(${modules_INCLUDE_DIRS}) -if(APPLE) - set_target_properties(frame_change PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - unset(PYTHON_LIBRARIES) -endif() +include_directories(${modules_INCLUDE_DIRS}) install(TARGETS frame_change LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) \ No newline at end of file From 30432c2e29f6bfba4fe6c01cc644ca65c3c967ac Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 13 Mar 2019 06:23:19 -0400 Subject: [PATCH 05/31] updates --- .../providers/aer/pulse/cython/CMakeLists.txt | 30 +++++++++++++-- qiskit/providers/aer/pulse/cython/solver.pyx | 33 ++++++++++++++++ qiskit/providers/aer/pulse/cython/utils.pyx | 38 +++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 qiskit/providers/aer/pulse/cython/solver.pyx create mode 100644 qiskit/providers/aer/pulse/cython/utils.pyx diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt index a5d47ce676..12eb183907 100644 --- a/qiskit/providers/aer/pulse/cython/CMakeLists.txt +++ b/qiskit/providers/aer/pulse/cython/CMakeLists.txt @@ -3,8 +3,6 @@ find_package(PythonExtensions REQUIRED) find_package(PythonLibs REQUIRED) find_package(NumPy REQUIRED) -message(STATUS "Numpy include dir: ${NumPy_INCLUDE_DIRS}") - # Frame change add_cython_target(frame_change frame_change.pyx CXX) add_library(frame_change MODULE ${frame_change}) @@ -29,4 +27,30 @@ python_modules_header(modules include_directories(${modules_INCLUDE_DIRS}) -install(TARGETS frame_change LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) \ No newline at end of file +install(TARGETS frame_change LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) + +# Solver +add_cython_target(solver solver.pyx CXX) +add_library(solver MODULE ${solver}) +set_target_properties(solver PROPERTIES + LINKER_LANGUAGE CXX + CXX_STANDARD 14) +if(APPLE) + set_target_properties(solver PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + unset(PYTHON_LIBRARIES) +endif() +target_link_libraries(solver + ${PYTHON_LIBRARIES}) + +target_include_directories(solver + PRIVATE ${PYTHON_INCLUDE_DIRS}) + +python_extension_module(solver + FORWARD_DECL_MODULES_VAR fdecl_module_list) + +python_modules_header(modules + FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) + +include_directories(${modules_INCLUDE_DIRS}) + +install(TARGETS solver LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) \ No newline at end of file diff --git a/qiskit/providers/aer/pulse/cython/solver.pyx b/qiskit/providers/aer/pulse/cython/solver.pyx new file mode 100644 index 0000000000..7cdd10f823 --- /dev/null +++ b/qiskit/providers/aer/pulse/cython/solver.pyx @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython +from libc.math cimport floor + +@cython.cdivision(True) +cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): + """ + Computes the index corresponding to time `t` in [`start`, 'stop') for + an array of length `len_arr`. + + Args: + t (double): Time. + start (double): Start time. + stop (double): Stop time. + len_arr (int): Length of array. + + Returns: + int: Array index. + """ + return floor(((t-start)/(stop-start)*(len_arr-1))) + + +def test_get_arr_idx(double t, double start, double stop, int len_arr): + return get_arr_idx(t, start, stop, len_arr) \ No newline at end of file diff --git a/qiskit/providers/aer/pulse/cython/utils.pyx b/qiskit/providers/aer/pulse/cython/utils.pyx new file mode 100644 index 0000000000..04a728a8cf --- /dev/null +++ b/qiskit/providers/aer/pulse/cython/utils.pyx @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython + +@cython.boundscheck(False) +@cython.wraparound(False) +def oplist_to_array(list A, complex[::1] B, + int start_idx=0): + """Takes a list of complex numbers represented by a list + of pairs of floats, and inserts them into a complex NumPy + array at a given starting index. + + Parameters: + A (list): A nested-list of [re, im] pairs. + B(ndarray): Array for storing complex numbers from list A. + start_idx (int): The starting index at which to insert elements. + + Notes: + This is ~5x faster than doing it in Python. + """ + cdef size_t kk + cdef int lenA = len(A) + cdef list temp + + if (start_idx+lenA) > B.shape[0]: + raise Exception('Input list does not fit into array if start_idx is {}.'.format(start_idx)) + + for kk in range(lenA): + temp = A[kk] + B[start_idx+kk] = temp[0]+1j*temp[1] \ No newline at end of file From a50f7e84bd6cc6f828f9ede08dc43ace2cb7748b Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 3 Apr 2019 06:25:25 -0400 Subject: [PATCH 06/31] update openpulse filex --- .gitignore | 5 + .../aer/{pulse => openpulse}/__init__.py | 0 .../{pulse => openpulse}/cython/__init__.py | 0 .../aer/openpulse/cython/channel_value.pxd | 17 + .../aer/openpulse/cython/channel_value.pyx | 96 +++++ .../aer/openpulse/cython/measure.pyx | 55 +++ .../providers/aer/openpulse/cython/memory.pyx | 37 ++ .../providers/aer/openpulse/cython/setup.py | 49 +++ .../aer/{pulse => openpulse}/cython/utils.pyx | 2 +- .../providers/aer/openpulse/qobj/__init__.py | 6 + qiskit/providers/aer/openpulse/qobj/digest.py | 400 +++++++++++++++++ .../providers/aer/openpulse/qobj/op_qobj.py | 200 +++++++++ .../providers/aer/openpulse/qobj/op_system.py | 41 ++ .../providers/aer/openpulse/qobj/operators.py | 200 +++++++++ .../providers/aer/openpulse/qobj/opparse.py | 403 ++++++++++++++++++ .../aer/openpulse/solver/__init__.py | 6 + .../providers/aer/openpulse/solver/codegen.py | 219 ++++++++++ .../aer/openpulse/solver/data_config.py | 91 ++++ .../aer/openpulse/solver/monte_carlo.py | 151 +++++++ .../providers/aer/openpulse/solver/opsolve.py | 168 ++++++++ .../providers/aer/openpulse/solver/options.py | 102 +++++ .../aer/openpulse/solver/rhs_utils.py | 37 ++ .../aer/openpulse/solver/settings.py | 9 + .../providers/aer/openpulse/solver/unitary.py | 91 ++++ .../providers/aer/openpulse/solver/zvode.py | 30 ++ .../providers/aer/pulse/cython/CMakeLists.txt | 56 --- .../aer/pulse/cython/frame_change.pyx | 64 --- qiskit/providers/aer/pulse/cython/solver.pyx | 33 -- 28 files changed, 2414 insertions(+), 154 deletions(-) rename qiskit/providers/aer/{pulse => openpulse}/__init__.py (100%) rename qiskit/providers/aer/{pulse => openpulse}/cython/__init__.py (100%) create mode 100644 qiskit/providers/aer/openpulse/cython/channel_value.pxd create mode 100644 qiskit/providers/aer/openpulse/cython/channel_value.pyx create mode 100644 qiskit/providers/aer/openpulse/cython/measure.pyx create mode 100644 qiskit/providers/aer/openpulse/cython/memory.pyx create mode 100644 qiskit/providers/aer/openpulse/cython/setup.py rename qiskit/providers/aer/{pulse => openpulse}/cython/utils.pyx (96%) create mode 100644 qiskit/providers/aer/openpulse/qobj/__init__.py create mode 100644 qiskit/providers/aer/openpulse/qobj/digest.py create mode 100644 qiskit/providers/aer/openpulse/qobj/op_qobj.py create mode 100644 qiskit/providers/aer/openpulse/qobj/op_system.py create mode 100644 qiskit/providers/aer/openpulse/qobj/operators.py create mode 100644 qiskit/providers/aer/openpulse/qobj/opparse.py create mode 100644 qiskit/providers/aer/openpulse/solver/__init__.py create mode 100644 qiskit/providers/aer/openpulse/solver/codegen.py create mode 100644 qiskit/providers/aer/openpulse/solver/data_config.py create mode 100644 qiskit/providers/aer/openpulse/solver/monte_carlo.py create mode 100644 qiskit/providers/aer/openpulse/solver/opsolve.py create mode 100644 qiskit/providers/aer/openpulse/solver/options.py create mode 100644 qiskit/providers/aer/openpulse/solver/rhs_utils.py create mode 100644 qiskit/providers/aer/openpulse/solver/settings.py create mode 100644 qiskit/providers/aer/openpulse/solver/unitary.py create mode 100644 qiskit/providers/aer/openpulse/solver/zvode.py delete mode 100644 qiskit/providers/aer/pulse/cython/CMakeLists.txt delete mode 100644 qiskit/providers/aer/pulse/cython/frame_change.pyx delete mode 100644 qiskit/providers/aer/pulse/cython/solver.pyx diff --git a/.gitignore b/.gitignore index 772607eaed..deb380c221 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,8 @@ contrib/standalone/version.hpp *.log *.env *.idea/ +.DS_Store +qiskit/providers/aer/openpulse/cython/channel_value.cpp +qiskit/providers/aer/openpulse/cython/measure.cpp +qiskit/providers/aer/openpulse/cython/memory.cpp +qiskit/providers/aer/openpulse/cython/utils.cpp diff --git a/qiskit/providers/aer/pulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py similarity index 100% rename from qiskit/providers/aer/pulse/__init__.py rename to qiskit/providers/aer/openpulse/__init__.py diff --git a/qiskit/providers/aer/pulse/cython/__init__.py b/qiskit/providers/aer/openpulse/cython/__init__.py similarity index 100% rename from qiskit/providers/aer/pulse/cython/__init__.py rename to qiskit/providers/aer/openpulse/cython/__init__.py diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pxd b/qiskit/providers/aer/openpulse/cython/channel_value.pxd new file mode 100644 index 0000000000..3207c26c30 --- /dev/null +++ b/qiskit/providers/aer/openpulse/cython/channel_value.pxd @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cdef complex channel_value(double t, + unsigned int chan_num, + double[::1] chan_pulse_times, + complex[::1] pulse_array, + unsigned int[::1] pulse_ints, + double[::1] fc_array, + unsigned char[::1] register) \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pyx b/qiskit/providers/aer/openpulse/cython/channel_value.pyx new file mode 100644 index 0000000000..bd9c6c7e57 --- /dev/null +++ b/qiskit/providers/aer/openpulse/cython/channel_value.pyx @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython + +cimport cython +from libc.math cimport floor + +cdef extern from "" namespace "std" nogil: + double complex exp(double complex x) + +@cython.cdivision(True) +cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): + """Computes the array index value for sampling a pulse in pulse_array. + + Args: + t (double): The current simulation time. + start (double): Start time of pulse in question. + stop (double): Stop time of pulse. + len_arr (int): Length of the pulse sample array. + + Returns: + int: The array index value. + """ + return floor(((t-start)/(stop-start)*len_arr)) + +@cython.boundscheck(False) +cdef complex channel_value(double t, + unsigned int chan_num, + double[::1] chan_pulse_times, + complex[::1] pulse_array, + unsigned int[::1] pulse_ints, + double[::1] fc_array, + unsigned char[::1] register): + """Computes the value of a given channel at time `t`. + + Args: + t (double): Current time. + chan_num (int): The int that labels the channel. + chan_pulse_times (int array): Array containing + start_time, stop_time, pulse_int, conditional for + each pulse on the channel. + pulse_array (complex array): The array containing all the + pulse data in the passed pulse qobj. + pulse_ints (int array): Array that tells you where to start + indexing pulse_array for a given pulse labeled by + chan_pulse_times[4*kk+2]. + current_pulse_idx (int array): + """ + cdef size_t kk + cdef double start_time, stop_time, phase=0 + cdef int start_idx, stop_idx, offset_idx, temp_int, cond + cdef complex out = 0 + # This is because each entry has four values: + # start_time, stop_time, pulse_int, conditional + cdef unsigned int num_times = chan_pulse_times.shape[0] // 4 + + for kk in range(num_times): + # the time is overlapped with the kkth pulse + start_time = chan_pulse_times[4*kk] + stop_time = chan_pulse_times[4*kk+1] + if start_time <= t < stop_time: + cond = chan_pulse_times[4*kk+3] + if cond < 0 or register[cond]: + temp_int = chan_pulse_times[4*kk+2] + start_idx = pulse_ints[temp_int] + stop_idx = pulse_ints[temp_int+1] + offset_idx = get_arr_idx(t, start_time, stop_time, stop_idx-start_idx) + out = pulse_array[start_idx+offset_idx] + break + # Compute the frame change up to time t + if out != 0: + num_times = fc_array.shape[0] + for kk in range(num_times): + if t >= fc_array[3*kk]: + do_fc = 1 + # Check if FC is conditioned on register + if fc_array[3*kk+2] >= 0: + # If condition not satisfied no do FC + if not register[fc_array[3*kk+2]]: + do_fc = 0 + if do_fc: + # Update the frame change value + phase += fc_array[3*kk+1] + else: + break + if phase != 0: + out *= exp(1j*phase) + return out \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cython/measure.pyx b/qiskit/providers/aer/openpulse/cython/measure.pyx new file mode 100644 index 0000000000..a8786d1ee2 --- /dev/null +++ b/qiskit/providers/aer/openpulse/cython/measure.pyx @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython +import numpy as np +cimport numpy as np +from qutip.cy.spmatfuncs import cy_expect_psi_csr + +@cython.boundscheck(False) +def occ_probabilities(unsigned int[::1] qubits, complex[::1] state, list meas_ops): + """Computes the occupation probabilities of the specifed qubits for + the given state. + + Args: + qubits (int array): Ints labelling which qubits are to be measured. + """ + + cdef unsigned int num_qubits = qubits.shape[0] + cdef np.ndarray[double, ndim=1, mode="c"] probs = np.zeros(qubits.shape[0], dtype=float) + + cdef size_t kk + cdef object oper + + for kk in range(num_qubits): + oper = meas_ops[qubits[kk]] + probs[kk] = cy_expect_psi_csr(oper.data.data, + oper.data.indices, + oper.data.indptr, + state, 1) + + return probs + +@cython.boundscheck(False) +def write_shots_memory(unsigned char[:, ::1] mem, + unsigned int[::1] mem_slots, + double[::1] probs, + double[::1] rand_vals): + + cdef unsigned int nrows = mem.shape[0] + cdef unsigned int nprobs = probs.shape[0] + + cdef size_t ii, jj + cdef unsigned char temp + for ii in range(nrows): + for jj in range(nprobs): + temp = (probs[jj] < rand_vals[nprobs*ii+jj]) + if temp: + mem[ii,mem_slots[jj]] = temp \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cython/memory.pyx b/qiskit/providers/aer/openpulse/cython/memory.pyx new file mode 100644 index 0000000000..b20d1c61dc --- /dev/null +++ b/qiskit/providers/aer/openpulse/cython/memory.pyx @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +#!python +#cython: language_level = 3 +#distutils: language = c++ + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +cimport cython + +@cython.boundscheck(False) +def write_memory(unsigned char[:, ::1] mem, + unsigned int[::1] memory_slots, + double[::1] probs, + double[::1] rand_vals): + """Writes the results of measurements to memory + in place. + + Args: + mem (int8 array): 2D array of memory of size (shots*memory_slots). + memory_slots (uint32 array): Array of ints for memory_slots to write too. + probs (double array): Probability of being in excited state for + each qubit in `qubits`. + rand_vals (double array): Random values of len = len(qubits)*shots + """ + cdef unsigned int nrows = mem.shape[0] + cdef unsigned int nprobs = probs.shape[0] + + cdef size_t ii, jj + cdef unsigned char temp + for ii in range(nrows): + for jj in range(nprobs): + temp = (probs[jj] < rand_vals[nprobs*ii+jj]) + if temp: + mem[ii,memory_slots[jj]] = temp \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cython/setup.py b/qiskit/providers/aer/openpulse/cython/setup.py new file mode 100644 index 0000000000..de7b621e2f --- /dev/null +++ b/qiskit/providers/aer/openpulse/cython/setup.py @@ -0,0 +1,49 @@ + +import distutils.sysconfig +import os +import sys +import numpy as np +from setuptools import setup, Extension +from Cython.Build import cythonize +from Cython.Distutils import build_ext + +INCLUDE_DIRS = [np.get_include()] +# Add Cython extensions here +cython_exts = ['channel_value', 'measure', + 'memory', 'utils'] + +# Extra link args +_link_flags = [] +# If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) +if (sys.platform == 'win32' and int(str(sys.version_info[0])+str(sys.version_info[1])) >= 35 + and os.environ.get('MSYSTEM') is None): + _compiler_flags = [] +# Everything else +else: + _compiler_flags = ['-O2', '-funroll-loops'] + if sys.platform == 'darwin': + # These are needed for compiling on OSX 10.14+ + _compiler_flags.append('-mmacosx-version-min=10.9') + _link_flags.append('-mmacosx-version-min=10.9') + +# Remove -Wstrict-prototypes from cflags +cfg_vars = distutils.sysconfig.get_config_vars() +if "CFLAGS" in cfg_vars: + cfg_vars["CFLAGS"] = cfg_vars["CFLAGS"].replace("-Wstrict-prototypes", "") + + +EXT_MODULES = [] +# Add Cython files from qutip/cy +for ext in cython_exts: + _mod = Extension(ext, + sources=[ext+'.pyx'], + include_dirs=[np.get_include()], + extra_compile_args=_compiler_flags, + extra_link_args=_link_flags, + language='c++') + EXT_MODULES.append(_mod) + + +setup(name='OpenPulse', + ext_modules=cythonize(EXT_MODULES) + ) diff --git a/qiskit/providers/aer/pulse/cython/utils.pyx b/qiskit/providers/aer/openpulse/cython/utils.pyx similarity index 96% rename from qiskit/providers/aer/pulse/cython/utils.pyx rename to qiskit/providers/aer/openpulse/cython/utils.pyx index 04a728a8cf..06eef4ec19 100644 --- a/qiskit/providers/aer/pulse/cython/utils.pyx +++ b/qiskit/providers/aer/openpulse/cython/utils.pyx @@ -27,7 +27,7 @@ def oplist_to_array(list A, complex[::1] B, This is ~5x faster than doing it in Python. """ cdef size_t kk - cdef int lenA = len(A) + cdef unsigned int lenA = len(A) cdef list temp if (start_idx+lenA) > B.shape[0]: diff --git a/qiskit/providers/aer/openpulse/qobj/__init__.py b/qiskit/providers/aer/openpulse/qobj/__init__.py new file mode 100644 index 0000000000..206e02adea --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py new file mode 100644 index 0000000000..a05870d724 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import numpy as np +from collections import OrderedDict +from qiskit.qiskiterror import QiskitError +from openpulse.qobj.op_system import OPSystem +from openpulse.solver.options import OPoptions +from openpulse.qobj.opparse import HamiltonianParser, NoiseParser +from openpulse.qobj.operators import init_fock_state, qubit_occ_oper +# pylint: disable=no-name-in-module,import-error +from openpulse.cython.utils import oplist_to_array + +"""A module of routines for digesting a PULSE qobj +into something we can actually use. +""" + +def digest_pulse_obj(qobj): + """Takes an input PULSE obj an disgests it into + things we can actually make use of. + + Args: + qobj (Qobj): Qobj of PULSE type. + + Returns: + OPSystem: The parsed qobj. + """ + # Output data object + out = OPSystem() + + config_keys = qobj['config'].keys() + + # Look for config keys + out.global_data['shots'] = 1024 + if 'shots' in config_keys: + out.global_data['shots'] = qobj['config']['shots'] + + out.global_data['seed'] = None + if 'seed' in config_keys: + out.global_data['seed'] = int(qobj['config']['seed']) + + if 'memory_slots' in config_keys: + out.global_data['memory_slots'] = qobj['config']['memory_slots'] + else: + err_str = 'Number of memory_slots must be specific in Qobj config' + raise QiskitError(err_str) + + if 'memory' in config_keys: + out.global_data['memory'] = qobj['config']['memory'] + else: + out.global_data['memory'] = False + + out.global_data['n_registers'] = 0 + if 'n_registers' in config_keys: + out.global_data['n_registers'] = qobj['config']['n_registers'] + + # Attach the ODE options + allowed_ode_options = ['atol', 'rtol', 'nsteps', 'max_step', + 'num_cpus', 'norm_tol', 'norm_steps', + 'rhs_reuse', 'rhs_filename'] + user_set_ode_options = {} + if 'ode_options' in config_keys: + for key, val in qobj['config']['ode_options'].items(): + if key not in allowed_ode_options: + raise Exception('Invalid ode_option: {}'.format(key)) + else: + user_set_ode_options[key] = val + out.ode_options = OPoptions(**user_set_ode_options) + + + # Step #1: Parse hamiltonian representation + if 'hamiltonian' not in config_keys: + raise QiskitError('Qobj must have hamiltonian in config to simulate.') + else: + ham = qobj['config']['hamiltonian'] + + out.vars = OrderedDict(ham['vars']) + out.global_data['vars'] = list(out.vars.values()) + + # Get qubit subspace dimensions + if 'qub' in ham.keys(): + dim_qub = ham['qub'] + _dim_qub = {} + # Convert str keys to int keys + for key, val in ham['qub'].items(): + _dim_qub[int(key)] = val + dim_qub = _dim_qub + else: + dim_qub = {}.fromkeys(range(qobj['config']['n_qubits']), 2) + + # Get oscillator subspace dimensions + if 'osc' in ham.keys(): + dim_osc = ham['osc'] + _dim_osc = {} + # Convert str keys to int keys + for key, val in dim_osc.items(): + _dim_osc[int(key)] = val + dim_osc = _dim_osc + else: + dim_osc = {} + + # Parse the Hamiltonian + system = HamiltonianParser(h_str=ham['h_str'], + dim_osc=dim_osc, + dim_qub=dim_qub) + system.parse() + out.system = system.compiled + + if 'noise' in qobj['config'].keys(): + noise = NoiseParser(noise_dict=qobj['config']['noise'], + dim_osc=dim_osc, dim_qub=dim_qub) + noise.parse() + + out.noise = noise.compiled + out.can_sample = False + else: + out.noise = None + + # Set initial state + out.initial_state = init_fock_state(dim_osc, dim_qub) + + # Step #2: Get Hamiltonian channels + out.channels = get_hamiltonian_channels(ham['h_str']) + + # Step #3: Build pulse arrays + pulses, pulses_idx, pulse_dict = build_pulse_arrays(qobj) + + out.global_data['pulse_array'] = pulses + out.global_data['pulse_indices'] = pulses_idx + out.pulse_to_int = pulse_dict + + # Step #4: Get dt + if 'dt' not in qobj['config'].keys(): + raise QiskitError('Qobj must have a dt value to simulate.') + else: + out.dt = qobj['config']['dt'] + + + # Set the ODE solver max step to be the half the width of the smallest pulse + min_width = np.iinfo(np.int32).max + for key, val in out.pulse_to_int.items(): + if key != 'pv': + stop = out.global_data['pulse_indices'][val+1] + start = out.global_data['pulse_indices'][val] + min_width = min(min_width, stop-start) + out.ode_options.max_step = min_width/2 * out.dt + + # Step #6: Convert experiments to data structures. + + out.global_data['measurement_ops'] = [None]*qobj['config']['n_qubits'] + + for exp in qobj['experiments']: + exp_struct = experiment_to_structs(exp, + out.channels, + out.global_data['pulse_indices'], + out.pulse_to_int, + out.dt) + if len(exp_struct['acquire']) > 0: + for acq in exp_struct['acquire']: + for jj in acq[1]: + if not out.global_data['measurement_ops'][jj]: + out.global_data['measurement_ops'][jj] = \ + qubit_occ_oper(jj, + h_osc=dim_osc, + h_qub=dim_qub, + level=1 + ) + + out.experiments.append(exp_struct) + if not exp_struct['can_sample']: + out.can_sample = False + return out + +def get_hamiltonian_channels(hamstring): + """ Get all the qubit channels D_i and U_i in the string + representation of a system Hamiltonian. + + Parameters: + hamstring (list): A list holding strings giving the Hamiltonian + of a specific quantum system. + + Returns: + list: A list of all channels in Hamiltonian string. + + Raises: + Exception: Missing index on channel. + """ + out_channels = [] + for ham_str in hamstring: + chan_idx = [i for i, letter in enumerate(ham_str) if letter in ['D', 'U']] + for ch in chan_idx: + if (ch+1) == len(ham_str) or not ham_str[ch+1].isdigit(): + raise Exception('Channel name must include an integer labeling the qubit.') + for kk in chan_idx: + done = False + offset = 0 + while not done: + offset += 1 + if not ham_str[kk+offset].isdigit(): + done = True + # In case we hit the end of the string + elif (kk+offset+1) == len(ham_str): + done = True + offset += 1 + temp_chan = ham_str[kk:kk+offset] + if temp_chan not in out_channels: + out_channels.append(temp_chan) + out_channels.sort(key=lambda x: (int(x[1:]), x[0])) + + out_dict = OrderedDict() + for idx, val in enumerate(out_channels): + out_dict[val] = idx + + return out_dict + + +def build_pulse_arrays(qobj): + """ Build pulses and pulse_idx arrays, and a pulse_dict + used in simulations and mapping of experimental pulse + sequencies to pulse_idx sequencies and timings. + + Parameters: + qobj (Qobj): A pulse-qobj instance. + + Returns: + ndarray, ndarray, dict: Returns all pulses in one array, + an array of start indices for pulses, and dict that + maps pulses to the index at which the pulses start. + """ + qobj_pulses = qobj['config']['pulse_library'] + pulse_dict = {} + total_pulse_length = 0 + for kk, pulse in enumerate(qobj_pulses): + pulse_dict[pulse['name']] = kk + total_pulse_length += len(pulse['samples']) + + idx = kk+1 + #now go through experiments looking for PV gates + pv_pulses = [] + for exp in qobj['experiments']: + for pulse in exp['instructions']: + if pulse['name'] == 'pv': + if pulse['val'] not in [pval[1] for pval in pv_pulses] and pulse['val'] != 0: + pv_pulses.append((pulse['val'], idx)) + idx += 1 + total_pulse_length += 1 + + pulse_dict['pv'] = pv_pulses + + pulses = np.empty(total_pulse_length, dtype=complex) + pulses_idx = np.zeros(idx+1, dtype=np.uint32) + + stop = 0 + ind = 1 + for kk, pulse in enumerate(qobj_pulses): + stop = pulses_idx[ind-1] + len(pulse['samples']) + pulses_idx[ind] = stop + oplist_to_array(pulse['samples'], pulses, pulses_idx[ind-1]) + ind += 1 + + for pv in pv_pulses: + stop = pulses_idx[ind-1] + 1 + pulses_idx[ind] = stop + oplist_to_array([pv[0]], pulses, pulses_idx[ind-1]) + ind += 1 + + return pulses, pulses_idx, pulse_dict + +def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): + max_time = 0 + structs = {} + structs['channels'] = OrderedDict() + for chan_name in ham_chans: + structs['channels'][chan_name] = [[], []] + structs['acquire'] = [] + structs['cond'] = [] + structs['snapshot'] = [] + structs['tlist'] = [] + structs['can_sample'] = True + # This is a list that tells us whether the last PV pulse on a channel needs to + # be assigned a final time based on the next pulse on that channel + pv_needs_tf = [0]*len(ham_chans) + + # The instructions are time-ordered so just loop through them. + for inst in experiment['instructions']: + # Do D and U channels + if 'ch' in inst.keys() and inst['ch'][0] in ['d', 'u']: + chan_name = inst['ch'].upper() + if chan_name not in ham_chans.keys(): + raise ValueError('Channel {} is not in Hamiltonian model'.format(inst['ch'])) + + # If last pulse on channel was a PV then need to set + # its final time to be start time of current pulse + if pv_needs_tf[ham_chans[chan_name]]: + structs['channels'][chan_name][0][-3] = inst['t0'] + pv_needs_tf[ham_chans[chan_name]] = 0 + + # Get condtional info + if 'conditional' in inst.keys(): + cond = inst['conditional'] + else: + cond = -1 + # PV's + if inst['name'] == 'pv': + # Get PV index + for pv in pulse_to_int['pv']: + if pv[0] == inst['val']: + index = pv[1] + break + structs['channels'][chan_name][0].extend([inst['t0'], None, index, cond]) + pv_needs_tf[ham_chans[chan_name]] = 1 + + # Frame changes + elif inst['name'] == 'fc': + structs['channels'][chan_name][1].extend([inst['t0'],inst['phase'], cond]) + + # A standard pulse + else: + start = inst['t0'] + pulse_int = pulse_to_int[inst['name']] + pulse_width = (pulse_inds[pulse_int+1]-pulse_inds[pulse_int])*dt + stop = start + pulse_width + structs['channels'][chan_name][0].extend([start, stop, pulse_int, cond]) + + max_time = max(max_time, stop) + + # Take care of acquires and snapshots (bfuncs added ) + else: + # measurements + if inst['name'] == 'acquire': + acq_vals = [inst['t0'], + np.asarray(inst['qubits'], dtype=np.uint32), + np.asarray(inst['memory_slot'], dtype=np.uint32) + ] + if 'register_slot' in inst.keys(): + acq_vals.append(np.asarray(inst['register_slot'], dtype=np.uint32)) + else: + acq_vals.append(None) + structs['acquire'].append(acq_vals) + + #update max_time + max_time = max(max_time, inst['t0']+dt*inst['duration']) + + # Add time to tlist + if inst['t0'] not in structs['tlist']: + structs['tlist'].append(inst['t0']) + + # conditionals + elif inst['name'] == 'bfunc': + bfun_vals = [inst['t0'], inst['mask'], inst['relation'], + inst['val'], inst['register']] + if 'memory' in inst.keys(): + bfun_vals.append(inst['memory']) + else: + bfun_vals.append(None) + + structs['cond'].append(acq_vals) + + #update max_time + max_time = max(max_time, inst['t0']) + + # Add time to tlist + if inst['t0'] not in structs['tlist']: + structs['tlist'].append(inst['t0']) + + # snapshots + elif inst['name'] == 'snapshot': + if inst['type'] != 'state': + raise QiskitError("Snapshots must be of type 'state'") + structs['snapshot'].append([inst['t0'], inst['label']]) + + # Add time to tlist + if inst['t0'] not in structs['tlist']: + structs['tlist'].append(inst['t0']) + + #update max_time + max_time = max(max_time, inst['t0']) + + # If any PVs still need time then they are at the end + # and should just go til final time + ham_keys = list(ham_chans.keys()) + for idx, pp in enumerate(pv_needs_tf): + if pp: + structs['channels'][ham_keys[idx]][0][-3] = max_time + pv_needs_tf[idx] = 0 + + # Convert lists to numpy arrays + for key in structs['channels'].keys(): + structs['channels'][key][0] = np.asarray(structs['channels'][key][0], dtype=float) + structs['channels'][key][1] = np.asarray(structs['channels'][key][1], dtype=float) + + structs['tlist'] = np.asarray([0]+structs['tlist'], dtype=float) + + if len(structs['acquire']) > 1 or structs['tlist'][-1] > structs['acquire'][-1][0]: + structs['can_sample'] = False + + return structs \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py new file mode 100644 index 0000000000..d5035daa8c --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +# pylint: disable=invalid-name + +import numpy as np +import qutip as qt + +from qutip.cy.spmatfuncs import (spmv_csr, cy_expect_psi_csr) +from qutip.fastsparse import fast_csr_matrix + + +def sigmax(dim=2): + """Qiskit wrapper of sigma-X operator. + """ + if dim == 2: + return qt.sigmax() + else: + raise Exception('Invalid level specification of the qubit subspace') + + +def sigmay(dim=2): + """Qiskit wrapper of sigma-Y operator. + """ + if dim == 2: + return qt.sigmay() + else: + raise Exception('Invalid level specification of the qubit subspace') + + +def sigmaz(dim=2): + """Qiskit wrapper of sigma-Z operator. + """ + if dim == 2: + return qt.sigmaz() + else: + raise Exception('Invalid level specification of the qubit subspace') + + +def sigmap(dim=2): + """Qiskit wrapper of sigma-plus operator. + """ + return qt.create(dim) + + +def sigmam(dim=2): + """Qiskit wrapper of sigma-minus operator. + """ + return qt.destroy(dim) + + +def create(dim): + """Qiskit wrapper of creation operator. + """ + return qt.create(dim) + + +def destroy(dim): + """Qiskit wrapper of annihilation operator. + """ + return qt.destroy(dim) + + +def num(dim): + """Qiskit wrapper of number operator. + """ + return qt.num(dim) + + +def qeye(dim): + """Qiskit wrapper of identity operator. + """ + return qt.qeye(dim) + + +def project(dim, states): + """Qiskit wrapper of projection operator. + """ + ket, bra = states + if ket in range(dim) and bra in range(dim): + return qt.basis(dim, ket) * qt.basis(dim, bra).dag() + else: + raise Exception('States are specified on the outside of Hilbert space %s', states) + + +def tensor(list_qobj): + """ Qiskit wrapper of tensor product + """ + return qt.tensor(list_qobj) + + +def conj(val): + """ Qiskit wrapper of conjugate + """ + if isinstance(val, qt.qobj.Qobj): + return val.conj() + else: + return np.conj(val) + + +def sin(val): + """ Qiskit wrapper of sine function + """ + if isinstance(val, qt.qobj.Qobj): + return val.sinm() + else: + return np.sin(val) + + +def cos(val): + """ Qiskit wrapper of cosine function + """ + if isinstance(val, qt.qobj.Qobj): + return val.cosm() + else: + return np.cos(val) + + +def exp(val): + """ Qiskit wrapper of exponential function + """ + if isinstance(val, qt.qobj.Qobj): + return val.expm() + else: + return np.exp(val) + + +def sqrt(val): + """ Qiskit wrapper of square root + """ + if isinstance(val, qt.qobj.Qobj): + return val.sqrtm() + else: + return np.sqrt(val) + + +def dag(qobj): + """ Qiskit wrapper of adjoint + """ + return qobj.dag() + + +def dammy(qobj): + """ Return given quantum object + """ + return qobj + + +def basis(level, pos): + """ Qiskit wrapper of basis + """ + return qt.basis(level, pos) + + +def fock_dm(level, eigv): + """ Qiskit wrapper of fock_dm + """ + return qt.fock_dm(level, eigv) + + +def opr_prob(opr, state_vec): + """ Measure probability of operator in given quantum state + """ + return cy_expect_psi_csr(opr.data.data, + opr.data.indices, + opr.data.indptr, + state_vec, 1) + + +def opr_apply(opr, state_vec): + """ Apply operator to given quantum state + """ + return spmv_csr(opr.data.data, + opr.data.indices, + opr.data.indptr, + state_vec) + + +def get_oper(name, *args): + """ Return quantum operator of given name + """ + return __operdict.get(name, qeye)(*args) + + +def get_func(name, qobj): + """ Apply function of given name + """ + return __funcdict.get(name, dammy)(qobj) + + +__operdict = {'X': sigmax, 'Y': sigmay, 'Z': sigmaz, + 'Sp': sigmap, 'Sm': sigmam, 'I': qeye, + 'O': num, 'P': project, 'A': destroy, + 'C': create, 'N': num} + +__funcdict = {'cos': cos, 'sin': sin, 'exp': exp, + 'sqrt': sqrt, 'conj': conj, 'dag': dag} diff --git a/qiskit/providers/aer/openpulse/qobj/op_system.py b/qiskit/providers/aer/openpulse/qobj/op_system.py new file mode 100644 index 0000000000..0d6081f3e5 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/op_system.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +# pylint: disable=invalid-name + +class OPSystem(): + """ A Class that holds all the information + needed to simulate a given PULSE qobj + """ + def __init__(self): + # The system Hamiltonian in numerical format + self.system = None + # The noise (if any) in numerical format + self.noise = None + # System variables + self.vars = None + # The initial state of the system + self.initial_state = None + # Channels in the Hamiltonian string + # these tell the order in which the channels + # are evaluated in the RHS solver. + self.channels = None + # options of the ODE solver + self.ode_options = None + # time between pulse sample points. + self.dt = None + # Array containing all pulse samples + self.pulse_array = None + # Array of indices indicating where a pulse starts in the self.pulse_array + self.pulse_indices = None + # A dict that translates pulse names to integers for use in self.pulse_indices + self.pulse_to_int = None + # Holds the parsed experiments + self.experiments = [] + # Can experiments be simulated once then sampled + self.can_sample = True + # holds global data + self.global_data = {} \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py new file mode 100644 index 0000000000..21ac51dc0c --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -0,0 +1,200 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import numpy as np +import scipy.linalg as la +from openpulse.qobj import op_qobj as op + + +def gen_oper(opname, index, h_osc, h_qub, states=None): + """Generate quantum operators. + + Parameters + ---------- + opname (str): Name of the operator to be returned. + index (int): Index of operator. + h_osc (dict): Dimension of oscillator subspace + h_qub (dict): Dimension of qubit subspace + states (tuple): State indices of projection operator. + + Returns + ------- + out_oper (qutip.Qobj): quantum operator for target qubit. + """ + + # get number of levels in Hilbert space + if opname in ['X', 'Y', 'Z', 'Sp', 'Sm', 'I', 'O', 'P']: + is_qubit = True + dim = h_qub.get(index, 2) + else: + is_qubit = False + dim = h_osc.get(index, 5) + + if opname == 'P': + opr_tmp = op.get_oper(opname, dim, states) + else: + opr_tmp = op.get_oper(opname, dim) + + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + opers = [] + for ii, dd in rev_h_osc: + if ii == index and not is_qubit: + opers.append(opr_tmp) + else: + opers.append(op.qeye(dd)) + for ii, dd in rev_h_qub: + if ii == index and is_qubit: + opers.append(opr_tmp) + else: + opers.append(op.qeye(dd)) + + return op.tensor(opers) + + +def qubit_occ_oper(target_qubit, h_osc, h_qub, level=1): + """Builds the occupation number operator for a target qubit + in a qubit oscillator system, where the oscillator are the first + subsystems, and the qubit last. + + Parameters + ---------- + target_qubit (int): Qubit for which operator is built. + h_osc (dict): Dict of number of levels in each oscillator. + h_qub (dict): Dict of number of levels in each qubit system. + level (int): Level of qubit system to be measured. + + Returns + ------- + out_oper (qutip.Qobj): Occupation number operator for target qubit. + """ + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + opers = [] + for ii, dd in rev_h_osc: + opers.append(op.qeye(dd)) + for ii, dd in rev_h_qub: + if ii == target_qubit: + opers.append(op.fock_dm(h_qub.get(target_qubit, 2), level)) + else: + opers.append(op.qeye(dd)) + + return op.tensor(opers) + + +def measure_outcomes(measured_qubits, state_vector, measure_ops, + seed=None): + """Generate measurement outcomes for a given set of qubits and state vector. + + Parameters: + measured_qubits (array_like): Qubits to be measured. + state_vector(ndarray): State vector. + measure_ops (list): List of measurement operator + seed (int): Optional seed to RandomState for reproducibility. + + Returns: + outcomes (str): String of binaries representing measured qubit values. + """ + outcome_len = max(measured_qubits)+1 + # Create random generator with given seed (if any). + rng_gen = np.random.RandomState(seed) + rnds = rng_gen.rand(outcome_len) + outcomes = '' + for kk in range(outcome_len): + if kk in measured_qubits: + excited_prob = op.opr_prob(measure_ops[kk], state_vector) + if excited_prob >= rnds[kk]: + outcomes += '1' + else: + outcomes += '0' + else: + # We need a string for all the qubits up to the last + # one being measured for the mask operation to work + # Here, we default to unmeasured qubits in the grnd state. + outcomes += '0' + return outcomes + + +def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): + """Builds and applies the projection operator associated + with a given qubit measurement result onto a state vector. + + Parameters + ---------- + measured_qubits (list): measured qubit indices. + results (list): results of qubit measurements. + h_qub (dict): Dict of number of levels in each qubit system. + h_osc (dict): Dict of number of levels in each oscillator. + state_vector (ndarray): State vector. + + Returns: + ---------- + proj_state (qutip.Qobj): State vector after projector applied, and normalized. + """ + + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + opers = [] + for ii, dd in rev_h_osc: + opers.append(op.qeye(dd)) + for ii, dd in rev_h_qub: + if ii in measured_qubits: + opers.append(op.fock_dm(dd, results[ii])) + else: + opers.append(op.qeye(dd)) + + proj_oper = op.tensor(opers) + psi = op.opr_apply(proj_oper, state_vector) + psi /= la.norm(psi) + + return psi + + +def init_fock_state(h_osc, h_qub, noise_dict={}): + """ Generate initial Fock state, in the number state + basis, for an oscillator in a thermal state defined + by the expectation value of number of particles. + + Parameters: + h_osc (dict): Dimension of oscillator subspace + h_qub (dict): Dimension of qubit subspace + noise_dict (dict): Dictionary of thermal particles for each oscillator subspace + Returns: + qutip.Qobj: State vector + """ + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + sub_state_vecs = [] + for ii, dd in rev_h_osc: + n_thermal = noise_dict['oscillator']['n_th'].get(str(ii), 0) + if n_thermal == 0: + # no thermal particles + idx = 0 + else: + # consider finite thermal excitation + levels = np.arange(dd) + beta = np.log(1.0 / n_thermal + 1.0) + diags = np.exp(-beta * levels) + diags /= np.sum(diags) + cum_sum = np.cumsum(diags) + idx = np.where(np.random.random() < cum_sum)[0][0] + sub_state_vecs.append(op.basis(dd, idx)) + for ii, dd in rev_h_qub: + sub_state_vecs.append(op.basis(dd, 0)) + + return op.tensor(sub_state_vecs) diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py new file mode 100644 index 0000000000..6f31723a59 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -0,0 +1,403 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +# pylint: disable=invalid-name + +import re +import copy +from collections import namedtuple, OrderedDict +import numpy as np +from openpulse.qobj.operators import gen_oper +from openpulse.qobj import op_qobj as op + + +Token = namedtuple('Token', ('type', 'name')) + +ham_elements = OrderedDict( + QubOpr=re.compile(r"(?PO|Sp|Sm|X|Y|Z)(?P[0-9]+)"), + PrjOpr=re.compile(r"P(?P[0-9]+),(?P[0-9]+),(?P[0-9]+)"), + CavOpr=re.compile(r"(?PA|C|N)(?P[0-9]+)"), + Func=re.compile(r"(?P[a-z]+)\("), + Ext=re.compile(r"\.(?Pdag)"), + Var=re.compile(r"[a-z]+[0-9]*"), + Num=re.compile(r"[0-9.]+"), + MathOrd0=re.compile(r"[*/]"), + MathOrd1=re.compile(r"[+-]"), + BrkL=re.compile(r"\("), + BrkR=re.compile(r"\)") +) + + +class HamiltonianParser: + """ Generate QuTip hamiltonian object from string + """ + def __init__(self, h_str, dim_osc, dim_qub): + """ Create new quantum operator generator + + Parameters: + h_str (list): list of Hamiltonian string + dim_osc (dict): dimension of oscillator subspace + dim_qub (dict): dimension of qubit subspace + """ + self.h_str = h_str + self.dim_osc = dim_osc + self.dim_qub = dim_qub + self.__td_hams = [] + self.__tc_hams = [] + self.__str2qopr = {} + + @property + def compiled(self): + """ Return Hamiltonian in OpenPulse handler format + """ + return self.__tc_hams + self.__td_hams + + def parse(self): + """ Parse and generate quantum class object + """ + self.__td_hams = [] + self.__tc_hams = [] + + # expand sum + self._expand_sum() + + # convert to reverse Polish notation + for ham in self.h_str: + if len(re.findall(r"\|\|", ham)) > 1: + raise Exception("Multiple time-dependent terms in %s" % ham) + p_td = re.search(r"(?P[\S]+)\|\|(?P[\S]+)", ham) + + # find time-dependent term + if p_td: + coef, token = self._tokenizer(p_td.group('opr')) + # combine coefficient to time-dependent term + if coef: + td = '*'.join([coef, p_td.group('ch')]) + else: + td = p_td.group('ch') + token = self._shunting_yard(token) + _td = [self._token2qobj(token), td] + + self.__td_hams.append(_td) + else: + coef, token = self._tokenizer(ham) + token = self._shunting_yard(token) + _tc = self._token2qobj(token), coef + + self.__tc_hams.append(_tc) + + def _expand_sum(self): + """ Takes a string-based Hamiltonian list and expands the _SUM action items out. + """ + sum_str = re.compile(r"_SUM\[(?P[a-z]),(?P[a-z\d{}+-]+),(?P[a-z\d{}+-]+),") + brk_str = re.compile(r"]") + + ham_list = copy.copy(self.h_str) + ham_out = [] + + while any(ham_list): + ham = ham_list.pop(0) + p_sums = [p for p in sum_str.finditer(ham)] + p_brks = [p for p in brk_str.finditer(ham)] + if len(p_sums) != len(p_brks): + raise Exception('Missing correct number of brackets in %s' % ham) + + # find correct sum-bracket correspondence + if len(p_sums) == 0: + ham_out.append(ham) + else: + itr = p_sums[0].group('itr') + _l = int(p_sums[0].group('l')) + _u = int(p_sums[0].group('u')) + for ii in range(len(p_sums) - 1): + if p_sums[ii + 1].end() > p_brks[ii].start(): + break + else: + ii = len(p_sums) - 1 + + # substitute iterator value + _temp = [] + for kk in range(_l, _u+1): + trg_s = ham[p_sums[0].end():p_brks[ii].start()] + # generate replacement pattern + pattern = {} + for p in re.finditer(r"\{(?P[a-z0-9*/+-]+)\}", trg_s): + if p.group() not in pattern: + sub = parse_binop(p.group('op_str'), operands={itr: str(kk)}) + if sub.isdecimal(): + pattern[p.group()] = sub + else: + pattern[p.group()] = "{%s}" % sub + for key, val in pattern.items(): + trg_s = trg_s.replace(key, val) + _temp.append(''.join([ham[:p_sums[0].start()], + trg_s, ham[p_brks[ii].end():]])) + ham_list.extend(_temp) + + self.h_str = ham_out + + return ham_out + + def _tokenizer(self, op_str): + """ Convert string to token and coefficient + """ + # generate token + _op_str = copy.copy(op_str) + token_list = [] + prev = 'none' + while any(_op_str): + for key, parser in ham_elements.items(): + p = parser.match(_op_str) + if p: + # find quantum operators + if key in ['QubOpr', 'CavOpr']: + _key = key + _name = p.group() + if p.group() not in self.__str2qopr.keys(): + idx = int(p.group('idx')) + name = p.group('opr') + opr = gen_oper(name, idx, self.dim_osc, self.dim_qub) + self.__str2qopr[p.group()] = opr + elif key == 'PrjOpr': + _key = key + _name = p.group() + if p.group() not in self.__str2qopr.keys(): + idx = int(p.group('idx')) + name = 'P' + lvs = int(p.group('ket')), int(p.group('bra')) + opr = gen_oper(name, idx, self.dim_osc, self.dim_qub, lvs) + self.__str2qopr[p.group()] = opr + elif key in ['Func', 'Ext']: + _name = p.group('name') + _key = key + elif key == 'MathOrd1': + _name = p.group() + if prev not in ['QubOpr', 'PrjOpr', 'CavOpr', 'Var', 'Num']: + _key = 'MathUnitary' + else: + _key = key + else: + _name = p.group() + _key = key + token_list.append(Token(_key, _name)) + _op_str = _op_str[p.end():] + prev = _key + break + else: + raise Exception('Invalid input string %s is found', op_str) + + # split coefficient + coef = '' + if any([k.type == 'Var' for k in token_list]): + for ii in range(len(token_list)): + if token_list[ii].name == '*': + if all([k.type != 'Var' for k in token_list[ii+1:]]): + coef = ''.join([k.name for k in token_list[:ii]]) + token_list = token_list[ii+1:] + break + else: + raise Exception('Invalid order of operators and coefficients in %s' % op_str) + + return coef, token_list + + def _shunting_yard(self, token_list): + """ Reformat token to reverse Polish notation + """ + stack = [] + queue = [] + while any(token_list): + token = token_list.pop(0) + if token.type in ['QubOpr', 'PrjOpr', 'CavOpr', 'Num']: + queue.append(token) + elif token.type in ['Func', 'Ext']: + stack.append(token) + elif token.type in ['MathUnitary', 'MathOrd0', 'MathOrd1']: + while stack and math_priority(token, stack[-1]): + queue.append(stack.pop(-1)) + stack.append(token) + elif token.type in ['BrkL']: + stack.append(token) + elif token.type in ['BrkR']: + while stack[-1].type not in ['BrkL', 'Func']: + queue.append(stack.pop(-1)) + if not any(stack): + raise Exception('Missing correct number of brackets') + pop = stack.pop(-1) + if pop.type == 'Func': + queue.append(pop) + else: + raise Exception('Invalid token %s is found' % token.name) + + while any(stack): + queue.append(stack.pop(-1)) + + return queue + + def _token2qobj(self, tokens): + """ Generate quantum class object from tokens + """ + stack = [] + for token in tokens: + if token.type in ['QubOpr', 'PrjOpr', 'CavOpr']: + stack.append(self.__str2qopr[token.name]) + elif token.type == 'Num': + stack.append(float(token.name)) + elif token.type in ['MathUnitary']: + if token.name == '-': + stack.append(-stack.pop(-1)) + elif token.type in ['MathOrd0', 'MathOrd1']: + op2 = stack.pop(-1) + op1 = stack.pop(-1) + if token.name == '+': + stack.append(op1 + op2) + elif token.name == '-': + stack.append(op1 - op2) + elif token.name == '*': + stack.append(op1 * op2) + elif token.name == '/': + stack.append(op1 / op2) + elif token.type in ['Func', 'Ext']: + stack.append(op.get_func(token.name, stack.pop(-1))) + else: + raise Exception('Invalid token %s is found' % token.name) + + if len(stack) > 1: + raise Exception('Invalid mathematical operation in ' % tokens) + + return stack[0] + + +class NoiseParser: + """ Generate QuTip noise object from dictionary + Qubit noise is given in the format of nested dictionary: + "qubit": { + "0": { + "Sm": 0.006 + } + } + and oscillator noise is given in the format of nested dictionary: + "oscillator": { + "n_th": { + "0": 0.001 + }, + "coupling": { + "0": 0.05 + } + } + these configurations are combined in the same dictionary + """ + def __init__(self, noise_dict, dim_osc, dim_qub): + """ Create new quantum operator generator + + Parameters: + noise_dict (dict): dictionary of noise configuration + dim_osc (dict): dimension of oscillator subspace + dim_qub (dict): dimension of qubit subspace + """ + self.noise_osc = noise_dict.get('oscillator', {'n_th': {}, 'coupling': {}}) + self.noise_qub = noise_dict.get('qubit', {}) + self.dim_osc = dim_osc + self.dim_qub = dim_qub + self.__c_list = [] + + @property + def compiled(self): + """ Return noise configuration in OpenPulse handler format + """ + return self.__c_list + + def parse(self): + """ Parse and generate quantum class object + """ + # Qubit noise + for index, config in self.noise_qub.items(): + for opname, coef in config.items(): + # TODO: support noise in multi-dimensional system + # TODO: support noise with math operation + if opname in ['X', 'Y', 'Z', 'Sp', 'Sm']: + opr = gen_oper(opname, int(index), self.dim_osc, self.dim_qub) + else: + raise Exception('Unsupported noise operator %s is given' % opname) + self.__c_list.append(np.sqrt(coef) * opr) + # Oscillator noise + ndic = self.noise_osc['n_th'] + cdic = self.noise_osc['coupling'] + for (n_ii, n_coef), (c_ii, c_coef) in zip(ndic.items(), cdic.items()): + if n_ii == c_ii: + if c_coef > 0: + opr = gen_oper('A', int(n_ii), self.dim_osc, self.dim_qub) + if n_coef: + self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr) + self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag()) + else: + self.__c_list.append(np.sqrt(c_coef) * opr) + else: + raise Exception('Invalid oscillator index in noise dictionary.') + + +def math_priority(o1, o2): + """ Check priority of given math operation + """ + rank = {'MathUnitary': 2, 'MathOrd0': 1, 'MathOrd1': 0} + diff_ops = rank.get(o1.type, -1) - rank.get(o2.type, -1) + + if diff_ops > 0: + return False + else: + return True + + +def parse_binop(op_str, operands={}, cast_str=True): + """ Calculate binary operation in string format + """ + oprs = OrderedDict( + sum=r"(?P[a-zA-Z0-9]+)\+(?P[a-zA-Z0-9]+)", + sub=r"(?P[a-zA-Z0-9]+)\-(?P[a-zA-Z0-9]+)", + mul=r"(?P[a-zA-Z0-9]+)\*(?P[a-zA-Z0-9]+)", + div=r"(?P[a-zA-Z0-9]+)\/(?P[a-zA-Z0-9]+)", + non=r"(?P[a-zA-Z0-9]+)" + ) + + for key, regr in oprs.items(): + p = re.match(regr, op_str) + if p: + val0 = operands.get(p.group('v0'), p.group('v0')) + if key == 'non': + # substitution + retv = val0 + else: + val1 = operands.get(p.group('v1'), p.group('v1')) + # binary operation + if key == 'sum': + if val0.isdecimal() and val1.isdecimal(): + retv = int(val0) + int(val1) + else: + retv = '+'.join(str(val0), str(val1)) + elif key == 'sub': + if val0.isdecimal() and val1.isdecimal(): + retv = int(val0) - int(val1) + else: + retv = '-'.join(str(val0), str(val1)) + elif key == 'mul': + if val0.isdecimal() and val1.isdecimal(): + retv = int(val0) * int(val1) + else: + retv = '*'.join(str(val0), str(val1)) + elif key == 'div': + if val0.isdecimal() and val1.isdecimal(): + retv = int(val0) / int(val1) + else: + retv = '/'.join(str(val0), str(val1)) + else: + retv = 0 + break + else: + raise Exception('Invalid string %s' % op_str) + + if cast_str: + return str(retv) + else: + return retv diff --git a/qiskit/providers/aer/openpulse/solver/__init__.py b/qiskit/providers/aer/openpulse/solver/__init__.py new file mode 100644 index 0000000000..206e02adea --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py new file mode 100644 index 0000000000..97a09c9fc5 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import os +import sys +import numpy as np +import qutip as qt +import openpulse.solver.settings as op_set + +_cython_path = os.path.abspath(qt.cy.__file__).replace('__init__.py', '') +_cython_path = _cython_path.replace("\\", "/") +_include_string = "'"+_cython_path+"complex_math.pxi'" + + +class OPCodegen(object): + """ + Class for generating cython code files at runtime. + """ + def __init__(self, op_system): + + sys.path.append(os.getcwd()) + + # Hamiltonian time-depdendent pieces + self.op_system = op_system + self.dt = op_system.dt + + self.num_ham_terms = len(self.op_system.system) + + # Code generator properties + self._file = None + self.code = [] # strings to be written to file + self.level = 0 # indent level + self.spline_count = 0 + + def write(self, string): + """write lines of code to self.code""" + self.code.append(" " * self.level + string + "\n") + + def file(self, filename): + """open file called filename for writing""" + self._file = open(filename, "w") + + def generate(self, filename="rhs.pyx"): + """generate the file""" + + for line in cython_preamble(): + self.write(line) + + # write function for Hamiltonian terms (there is always at least one + # term) + for line in cython_checks() + self.ODE_func_header(): + self.write(line) + self.indent() + for line in func_header(self.op_system): + self.write(line) + for line in self.channels(): + self.write(line) + for line in self.func_vars(): + self.write(line) + for line in self.func_end(): + self.write(line) + self.dedent() + + self.file(filename) + self._file.writelines(self.code) + self._file.close() + op_set.CGEN_NUM += 1 + + def indent(self): + """increase indention level by one""" + self.level += 1 + + def dedent(self): + """decrease indention level by one""" + if self.level == 0: + raise SyntaxError("Error in code generator") + self.level -= 1 + + def ODE_func_header(self): + """Creates function header for time-dependent ODE RHS.""" + func_name = "def cy_td_ode_rhs(" + # strings for time and vector variables + input_vars = ("\n double t" + + ",\n complex[::1] vec") + for k in range(self.num_ham_terms): + input_vars += (",\n " + + "complex[::1] data%d, " % k + + "int[::1] idx%d, " % k + + "int[::1] ptr%d" % k) + + #Add global vaiables + input_vars += (",\n " + "complex[::1] pulse_array") + input_vars += (",\n " + "unsigned int[::1] pulse_indices") + + #Add per experiment variables + for key in self.op_system.channels.keys(): + input_vars += (",\n " + "double[::1] %s_pulses" % key) + input_vars += (",\n " + "double[::1] %s_fc" % key) + + # add Hamiltonian variables + for key in self.op_system.vars.keys(): + input_vars += (",\n " + "complex %s" % key) + + # register + input_vars += (",\n " + "unsigned char[::1] register") + + + func_end = "):" + return [func_name + input_vars + func_end] + + + def channels(self): + """Write out the channels + """ + channel_lines = [""] + + channel_lines.append("# Compute complex channel values at time `t`") + for chan, idx in self.op_system.channels.items(): + chan_str = "%s = channel_value(t, %s, " %(chan, idx) + \ + "%s_pulses, pulse_array, pulse_indices, " % chan + \ + "%s_fc, register)" % chan + channel_lines.append(chan_str) + channel_lines.append('') + return channel_lines + + + def func_vars(self): + """Writes the variables and spmv parts""" + func_vars = [] + + func_vars.append("# Eval the time-dependent terms and do SPMV.") + for idx, term in enumerate(self.op_system.system): + if isinstance(term, list) or term[1]: + func_vars.append("td%s = %s" % (idx, term[1])) + func_vars.append("if abs(td%s) > 1e-15:" % idx) + + spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ + "&vec[0], td{i}, &out[0], num_rows)" + func_vars.append(" "+spmv_str.format(i=idx)) + + else: + spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ + "&vec[0], 1.0, &out[0], num_rows)" + func_vars.append(spmv_str.format(i=idx)) + + + return func_vars + + def func_end(self): + end_str = [""] + end_str.append("# Convert to NumPy array, grab ownership, and return.") + end_str.append("cdef np.npy_intp dims = num_rows") + + temp_str = "cdef np.ndarray[complex, ndim=1, mode='c'] arr_out = " + temp_str += "np.PyArray_SimpleNewFromData(1, &dims, np.NPY_COMPLEX128, out)" + end_str.append(temp_str) + end_str.append("PyArray_ENABLEFLAGS(arr_out, np.NPY_OWNDATA)") + end_str.append("return arr_out") + return end_str + +def func_header(op_system): + func_vars = ["", 'cdef size_t row', 'cdef unsigned int num_rows = vec.shape[0]', + "cdef double complex * " + + 'out = PyDataMem_NEW_ZEROED(num_rows,sizeof(complex))' + ] + func_vars.append("") + + for val in op_system.channels: + func_vars.append("cdef double complex %s" % val) + + for kk, item in enumerate(op_system.system): + if item[1]: + func_vars.append("cdef double complex td%s" % kk) + + return func_vars + +def cython_preamble(): + """ + Returns list of code segments for Cython preamble. + """ + preamble = ["""\ +#!python +#cython: language_level=3 +# This file is generated automatically by Qiskit. +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +import numpy as np +cimport numpy as np +cimport cython +np.import_array() +cdef extern from "numpy/arrayobject.h" nogil: + void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) + void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) + +from qutip.cy.spmatfuncs cimport spmvpy +from qutip.cy.math cimport erf +from libc.math cimport pi + +from openpulse.cython.channel_value cimport channel_value + +include """+_include_string+""" +"""] + return preamble + + +def cython_checks(): + """ + List of strings that turn off Cython checks. + """ + return ["""@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False)"""] + diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py new file mode 100644 index 0000000000..2eded9b8be --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import numpy as np + +def op_data_config(op_system): + """ Preps the data for the opsolver. + + Everything is stored in the passed op_system. + + Args: + op_system (OPSystem): An openpulse system. + """ + + num_h_terms = len(op_system.system) + H = [hpart[0] for hpart in op_system.system] + op_system.global_data['num_h_terms'] = num_h_terms + + # take care of collapse operators, if any + op_system.global_data['c_num'] = 0 + if op_system.noise: + op_system.global_data['c_num'] = len(op_system.noise) + + op_system.global_data['c_ops_data'] = [] + op_system.global_data['c_ops_ind'] = [] + op_system.global_data['c_ops_ptr'] = [] + op_system.global_data['n_ops_data'] = [] + op_system.global_data['n_ops_ind'] = [] + op_system.global_data['n_ops_ind'] = [] + + # if there are any collapse operators + for kk in range(op_system.global_data['c_num']): + c_op = op_system.noise[kk] + n_op = c_op.dag() * c_op + # collapse ops + op_system.global_data['c_ops_data'].append(c_op.data.data) + op_system.global_data['c_ops_ind'].append(c_op.data.indices) + op_system.global_data['c_ops_ptr'].append(c_op.data.indptr) + # norm ops + op_system.global_data['n_ops_data'].append(n_op.data.data) + op_system.global_data['n_ops_ind'].append(n_op.data.indices) + op_system.global_data['n_ops_ind'].append(n_op.data.indptr) + # Norm ops added to time-independent part of + # Hamiltonian to decrease norm + H[0] -= 0.5j * n_op + + # construct data sets + op_system.global_data['h_ops_data'] = [-1.0j* hpart.data.data for hpart in H] + op_system.global_data['h_ops_ind'] = [hpart.data.indices for hpart in H] + op_system.global_data['h_ops_ptr'] = [hpart.data.indptr for hpart in H] + + # setup ode args string + ode_var_str = "" + # Hamiltonian data + for kk in range(num_h_terms): + h_str = "global_data['h_ops_data'][%s], " % kk + h_str += "global_data['h_ops_ind'][%s], " % kk + h_str += "global_data['h_ops_ptr'][%s], " % kk + ode_var_str += h_str + + # Add pulse array and pulse indices + ode_var_str += "global_data['pulse_array'], " + ode_var_str += "global_data['pulse_indices'], " + + var_list = list(op_system.vars.keys()) + final_var = var_list[-1] + + # Now add channel variables + chan_list = list(op_system.channels.keys()) + final_chan = chan_list[-1] + for chan in chan_list: + ode_var_str += "exp['channels']['%s'][0], " % chan + ode_var_str += "exp['channels']['%s'][1]" % chan + if chan != final_chan or var_list: + ode_var_str+= ', ' + + #now do the variables + for idx, var in enumerate(var_list): + ode_var_str += "global_data['vars'][%s]" % idx + if var != final_var: + ode_var_str+= ', ' + # Add register + ode_var_str += ", register" + op_system.global_data['string'] = ode_var_str + + #Convert inital state to flat array in global_data + op_system.global_data['initial_state'] = \ + op_system.initial_state.full().ravel() \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py new file mode 100644 index 0000000000..8ce0fcc5c5 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +import numpy as np +from scipy.integrate import ode +from scipy.linalg.blas import get_blas_funcs +from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr +from openpulse.solver.zvode import qiskit_zvode + +dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) + +def monte_carlo(pid, ophandler, ode_options): + """ + Monte Carlo algorithm returning state-vector or expectation values + at times tlist for a single trajectory. + """ + + global _cy_rhs_func + + tlist = ophandler._data['tlist'] + memory = [ + [ + 0 for _l in range(ophandler.qobj_config['memory_slot_size']) + ] for _r in range(ophandler.qobj_config['memory_slots']) + ] + register = bytearray(ophandler.backend_config['n_registers']) + + opt = ophandler._options + + collapse_times = [] + collapse_operators = [] + + # SEED AND RNG AND GENERATE + prng = RandomState(ophandler._options.seeds[pid]) + # first rand is collapse norm, second is which operator + rand_vals = prng.rand(2) + + ODE = ode(_cy_rhs_func) + + _inst = 'ODE.set_f_params(%s)' % ophandler._data['string'] + code = compile(_inst, '', 'exec') + exec(code) + psi = ophandler._data['psi0'] + + # initialize ODE solver for RHS + ODE._integrator = qiskit_zvode(method=ode_options.method, + order=ode_options.order, + atol=ode_options.atol, + rtol=ode_options.rtol, + nsteps=ode_options.nsteps, + first_step=ode_options.first_step, + min_step=ode_options.min_step, + max_step=ode_options.max_step + ) + + if not len(ODE._y): + ODE.t = 0.0 + ODE._y = np.array([0.0], complex) + ODE._integrator.reset(len(ODE._y), ODE.jac is not None) + + # make array for collapse operator inds + cinds = np.arange(ophandler._data['c_num']) + n_dp = np.zeros(ophandler._data['c_num'], dtype=float) + + kk_prev = 0 + # RUN ODE UNTIL EACH TIME IN TLIST + for kk in tlist: + ODE.set_initial_value(psi, kk_prev) + # ODE WHILE LOOP FOR INTEGRATE UP TO TIME TLIST[k] + while ODE.t < kk: + t_prev = ODE.t + y_prev = ODE.y + norm2_prev = dznrm2(ODE._y) ** 2 + # integrate up to kk, one step at a time. + ODE.integrate(kk, step=1) + if not ODE.successful(): + raise Exception("ZVODE step failed!") + norm2_psi = dznrm2(ODE._y) ** 2 + if norm2_psi <= rand_vals[0]: + # collapse has occured: + # find collapse time to within specified tolerance + # ------------------------------------------------ + ii = 0 + t_final = ODE.t + while ii < ophandler._options.norm_steps: + ii += 1 + t_guess = t_prev + \ + math.log(norm2_prev / rand_vals[0]) / \ + math.log(norm2_prev / norm2_psi) * (t_final - t_prev) + ODE._y = y_prev + ODE.t = t_prev + ODE._integrator.call_args[3] = 1 + ODE.integrate(t_guess, step=0) + if not ODE.successful(): + raise Exception( + "ZVODE failed after adjusting step size!") + norm2_guess = dznrm2(ODE._y)**2 + if (abs(rand_vals[0] - norm2_guess) < + ophandler._options.norm_tol * rand_vals[0]): + break + elif (norm2_guess < rand_vals[0]): + # t_guess is still > t_jump + t_final = t_guess + norm2_psi = norm2_guess + else: + # t_guess < t_jump + t_prev = t_guess + y_prev = ODE.y + norm2_prev = norm2_guess + if ii > ophandler._options.norm_steps: + raise Exception("Norm tolerance not reached. " + + "Increase accuracy of ODE solver or " + + "Options.norm_steps.") + + collapse_times.append(ODE.t) + # all constant collapse operators. + for i in range(n_dp.shape[0]): + n_dp[i] = cy_expect_psi_csr(ophandler._data['n_ops_data'][i], + ophandler._data['n_ops_ind'][i], + ophandler._data['n_ops_ptr'][i], + ODE._y, 1) + + # determine which operator does collapse and store it + _p = np.cumsum(n_dp / np.sum(n_dp)) + j = cinds[_p >= rand_vals[1]][0] + collapse_operators.append(j) + + state = spmv_csr(ophandler._data['c_ops_data'][j], + ophandler._data['c_ops_ind'][j], + ophandler._data['c_ops_ptr'][j], + ODE._y) + + state /= dznrm2(state) + ODE._y = state + ODE._integrator.call_args[3] = 1 + rand_vals = prng.rand(2) + + # after while loop (Do measurement or conditional) + # ---------------- + out_psi = ODE._y / dznrm2(ODE._y) + + # measurement + psi = _proj_measurement(pid, ophandler, kk, out_psi, memory, register) + + kk_prev = kk + + return psi, memory \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py new file mode 100644 index 0000000000..3f0c774260 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import os +import sys +import math +import numpy as np +from numpy.random import RandomState, randint +from scipy.integrate import ode +import scipy.sparse as sp +from scipy.integrate._ode import zvode +from scipy.linalg.blas import get_blas_funcs +from collections import OrderedDict +import qutip as qt +from qutip.qobj import Qobj +from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr +from qutip.cy.utilities import _cython_build_cleanup +from openpulse.qobj.operators import apply_projector +import qutip.settings +from openpulse.solver.codegen import OPCodegen +from openpulse.solver.rhs_utils import _op_generate_rhs, _op_func_load +from openpulse.solver.data_config import op_data_config +import openpulse.solver.settings as settings +from openpulse.solver.unitary import unitary_evolution +from qiskit.tools.parallel import parallel_map + +dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) + +# +# Internal, global variables for storing references to dynamically loaded +# cython functions +# +_cy_rhs_func = None + +def opsolve(op_system): + """Opsolver + """ + + if not op_system.initial_state.isket: + raise Exception("Initial state must be a state vector.") + + # set num_cpus to the value given in qutip.settings if none in Options + if not op_system.ode_options.num_cpus: + op_system.ode_options.num_cpus = qutip.settings.num_cpus + + # compile Cython RHS + _op_generate_rhs(op_system) + # Load cython function + _op_func_load(op_system) + # build Hamiltonian data structures + op_data_config(op_system) + # load monte carlo class + mc = OP_mcwf(op_system) + # Run the simulation + out = mc.run() + # Results are stored in ophandler.result + return out + +# ----------------------------------------------------------------------------- +# MONTE CARLO CLASS +# ----------------------------------------------------------------------------- +class OP_mcwf(object): + """ + Private class for solving Monte Carlo evolution + """ + + def __init__(self, op_system): + + self.op_system = op_system + # set output variables, even if they are not used to simplify output + # code. + self.output = None + self.collapse_times = None + self.collapse_operator = None + + # FOR EVOLUTION WITH COLLAPSE OPERATORS + if not op_system.can_sample: + # preallocate ntraj arrays for state vectors, collapse times, and + # which operator + self.collapse_times = [[] for kk in + range(op_system.global_data['shots'])] + self.collapse_operators = [[] for kk in + range(op_system.global_data['shots'])] + # setup seeds array + if op_system.global_data['seed']: + prng = np.random.RandomState(op_system.global_data['seed']) + else: + prng = np.random.RandomState( + np.random.randint(np.iinfo(np.int32).max-1)) + for exp in op_system.experiments: + exp['seed'] = prng.randint(np.iinfo(np.int32).max-1) + + def run(self): + + # If no collapse terms, and only measurements at end + # can do a single shot. + map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} + + if self.op_system.can_sample: + results = parallel_map(unitary_evolution, + self.op_system.experiments, + task_args=(self.op_system.global_data, + self.op_system.ode_options + ), **map_kwargs + ) + + + _cython_build_cleanup(self.op_system.global_data['rhs_file_name']) + return results + + +# Measurement +def _proj_measurement(pid, ophandler, tt, state, memory, register=None): + """ + Projection measurement of quantum state + """ + prng = np.random.RandomState(np.random.randint(np.iinfo(np.int32).max-1)) + qubits = [] + results = [] + + for key, acq in ophandler._acqs.items(): + if pid < 0: + if len(acq.m_slot) > 0: + mem_slot_id = acq.m_slot[-1] + else: + continue + reg_slot_id = None + else: + if tt in acq.t1: + mem_slot_id = acq.m_slot[acq.t1.index(tt)] + reg_slot_id = acq.r_slot[acq.t1.index(tt)] + else: + continue + oper = ophandler._measure_ops[acq.name] + p_q = cy_expect_psi_csr(oper.data.data, + oper.data.indices, + oper.data.indptr, + state, + 1) + # level2 measurement + rnd = prng.rand() + if rnd <= p_q: + outcome = 1 + else: + outcome = 0 + memory[mem_slot_id][0] = outcome + if reg_slot_id is not None and register is not None: + register[reg_slot_id] = outcome + qubits.append(key) + results.append(outcome) + + # projection + if len(qubits) > 0: + psi_proj = apply_projector(qubits, results, ophandler.h_qub, ophandler.h_osc, state) + else: + psi_proj = state + + return psi_proj + + +# ----------------------------------------------------------------------------- +# single-trajectory for monte carlo +# ----------------------------------------------------------------------------- + + diff --git a/qiskit/providers/aer/openpulse/solver/options.py b/qiskit/providers/aer/openpulse/solver/options.py new file mode 100644 index 0000000000..1d32233250 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/options.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + + +class OPoptions(object): + """ + Class of options for opsolver. Options can be specified either as + arguments to the constructor:: + + opts = Options(order=10, ...) + + or by changing the class attributes after creation:: + + opts = Options() + opts.order = 10 + + Returns options class to be used as options in evolution solvers. + + Attributes: + atol (float, 1e-8): Absolute tolerance. + rtol (float, 1e-/6): Relative tolerance. + method (str, 'adams'): Integration method, 'adams' or 'bdf'. + order (int, 12): Order of integrator (<=12 'adams', <=5 'bdf'). + nsteps (int, 50000): Max. number of internal steps per time interval. + first_step (float, 0): Size of initial step (0 = automatic). + min_step (float, 0): Minimum step size (0 = automatic). + max_step (float, 0): Maximum step size (0 = automatic) + num_cpus (int): Number of cpus used by mcsolver (default = # of cpus). + norm_tol (float, 1e-3): Tolerance used when finding wavefunction norm. + norm_steps (int, 5): Max. number of steps used to find wavefunction norm + to within norm_tol + shots (int, 1024): Number of shots to run. + rhs_reuse (bool, False): Reuse RHS compiled function. + rhs_filename (str): Name of compiled Cython module. + seeds (ndarray, None): Array containing random number seeds for + repeatible shots. + reuse_seeds (bool, False): Reuse seeds, if already generated. + store_final_state (bool, False): Whether or not to store the final state + of the evolution. + """ + + def __init__(self, atol=1e-8, rtol=1e-6, method='adams', order=12, + nsteps=50000, first_step=0, max_step=0, min_step=0, + num_cpus=0, norm_tol=1e-3, norm_steps=5, + progress_bar=True, rhs_reuse=False, + rhs_filename=None, shots=1024, + store_final_state=False, seeds=None, + reuse_seeds=False): + + # Absolute tolerance (default = 1e-8) + self.atol = atol + # Relative tolerance (default = 1e-6) + self.rtol = rtol + # Integration method (default = 'adams', for stiff 'bdf') + self.method = method + # Max. number of internal steps/call + self.nsteps = nsteps + # Size of initial step (0 = determined by solver) + self.first_step = first_step + # Minimal step size (0 = determined by solver) + self.min_step = min_step + # Max step size (0 = determined by solver) + self.max_step = max_step + # Maximum order used by integrator (<=12 for 'adams', <=5 for 'bdf') + self.order = order + # Number of shots to run (default=500) + self.shots = shots + # Holds seeds for rand num gen + self.seeds = seeds + # reuse seeds + self.reuse_seeds = reuse_seeds + # Use preexisting RHS function for time-dependent solvers + self.rhs_reuse = rhs_reuse + # Track progress + self.progress_bar = progress_bar + # Use filename for preexisting RHS function (will default to last + # compiled function if None & rhs_exists=True) + self.rhs_filename = rhs_filename + # Number of processors to use + if num_cpus: + self.num_cpus = num_cpus + else: + self.num_cpus = 0 + # Tolerance for wavefunction norm (mcsolve only) + self.norm_tol = norm_tol + # Max. number of steps taken to find wavefunction norm to within + # norm_tol (mcsolve only) + self.norm_steps = norm_steps + # store final state? + self.store_final_state = store_final_state + + def __str__(self): + return str(vars(self)) + def __repr__(self): + return self.__str__() + + + diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py new file mode 100644 index 0000000000..800900bc37 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +import os +from openpulse.solver.codegen import OPCodegen +import openpulse.solver.settings as op_set + + +def _op_generate_rhs(op_system): + """ Generates the RHS Cython file for solving the sytem + described in op_system + + Args: + op_system (OPSystem): An OpenPulse system object. + """ + name = "rhs" + str(os.getpid()) + str(op_set.CGEN_NUM)+'_op' + op_system.global_data['rhs_file_name'] = name + cgen = OPCodegen(op_system) + cgen.generate(name + ".pyx") + +def _op_func_load(op_system): + """Loads the Cython function defined in the file + `rhs_file_name.pyx` where `rhs_file_name` is + stored in the op_system. + + Args: + op_system (OPSystem): An OpenPulse system object. + """ + global _cy_rhs_func + code = compile('from ' + op_system.global_data['rhs_file_name'] + + ' import cy_td_ode_rhs', '', 'exec') + exec(code, globals()) + # pylint: disable=undefined-variable + op_system.global_data['rhs_func'] = cy_td_ode_rhs diff --git a/qiskit/providers/aer/openpulse/solver/settings.py b/qiskit/providers/aer/openpulse/solver/settings.py new file mode 100644 index 0000000000..890ea8e43a --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/settings.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. + +# Code generation counter +CGEN_NUM = 0 diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py new file mode 100644 index 0000000000..50ff105276 --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +# pylint: disable = unused-variable, no-name-in-module + +import numpy as np +from scipy.integrate import ode +from scipy.linalg.blas import get_blas_funcs +from openpulse.cython.memory import write_memory +from openpulse.cython.measure import occ_probabilities, write_shots_memory + +dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) + +def unitary_evolution(exp, global_data, ode_options): + """ + Calculates evolution when there is no noise, + or any measurements that are not at the end + of the experiment. + + Args: + exp (dict): Dictionary of experimental pulse and fc + data. + global_data (dict): Data that applies to all experiments. + ode_options (OPoptions): Options for the underlying ODE solver. + + Returns: + Stuff + """ + cy_rhs_func = global_data['rhs_func'] + rng = np.random.RandomState(exp['seed']) + tlist = exp['tlist'] + snapshots = [] + shots = global_data['shots'] + memory = np.zeros((shots, global_data['memory_slots']), + dtype=np.uint8) + + register = np.zeros(global_data['n_registers'], dtype=np.uint8) + num_channels = len(exp['channels']) + chan_pulse_idx = np.zeros(num_channels, dtype=np.uint32) + chan_fc_idx = np.zeros(num_channels, dtype=np.uint32) + fc_values = np.ones(num_channels, dtype=complex) + + ODE = ode(cy_rhs_func) + ODE.set_integrator('zvode', + method=ode_options.method, + order=ode_options.order, + atol=ode_options.atol, + rtol=ode_options.rtol, + nsteps=ode_options.nsteps, + first_step=ode_options.first_step, + min_step=ode_options.min_step, + max_step=ode_options.max_step) + + _inst = 'ODE.set_f_params(%s)' % global_data['string'] + code = compile(_inst, '', 'exec') + exec(code) + + # Since all experiments are defined to start at zero time. + ODE.set_initial_value(global_data['initial_state'], 0) + for kk in tlist[1:]: + ODE.integrate(kk, step=0) + if ODE.successful(): + psi = ODE.y / dznrm2(ODE.y) + else: + err_msg = 'ZVODE exited with status: %s' % ODE.get_return_code() + raise Exception(err_msg) + + # Do any snapshots here + + + # Do final measurement at end + qubits = exp['acquire'][0][1] + memory_slots = exp['acquire'][0][2] + probs = occ_probabilities(qubits, psi, global_data['measurement_ops']) + rand_vals = rng.rand(memory_slots.shape[0]*shots) + write_shots_memory(memory, memory_slots, probs, rand_vals) + int_mem = memory.dot(np.power(2.0, + np.arange(memory.shape[1]-1,-1,-1))).astype(int) + hex_mem = [hex(val) for val in int_mem] + if global_data['memory']: + return hex_mem + # Get hex counts dict + unique = np.unique(int_mem, return_counts = True) + hex_dict = {} + for kk in range(unique[0].shape[0]): + key = hex(unique[0][kk]) + hex_dict[key] = unique[1][kk] + return hex_dict \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/solver/zvode.py b/qiskit/providers/aer/openpulse/solver/zvode.py new file mode 100644 index 0000000000..2204935c9b --- /dev/null +++ b/qiskit/providers/aer/openpulse/solver/zvode.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019, IBM. +# +# This source code is licensed under the Apache License, Version 2.0 found in +# the LICENSE.txt file in the root directory of this source tree. +# pylint: disable=no-value-for-parameter + +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. + +from scipy.integrate._ode import zvode + + +class qiskit_zvode(zvode): + """Modifies the stepper for ZVODE so that + it always stops at a given time in tlist. + + By default, it over shoots the time, which + of course is just stupid. + """ + def step(self, *args): + itask = self.call_args[2] + self.rwork[0] = args[4] + self.call_args[2] = 5 + r = self.run(*args) + self.call_args[2] = itask + return r diff --git a/qiskit/providers/aer/pulse/cython/CMakeLists.txt b/qiskit/providers/aer/pulse/cython/CMakeLists.txt deleted file mode 100644 index 12eb183907..0000000000 --- a/qiskit/providers/aer/pulse/cython/CMakeLists.txt +++ /dev/null @@ -1,56 +0,0 @@ -find_package(Cython REQUIRED) -find_package(PythonExtensions REQUIRED) -find_package(PythonLibs REQUIRED) -find_package(NumPy REQUIRED) - -# Frame change -add_cython_target(frame_change frame_change.pyx CXX) -add_library(frame_change MODULE ${frame_change}) -set_target_properties(frame_change PROPERTIES - LINKER_LANGUAGE CXX - CXX_STANDARD 14) -if(APPLE) - set_target_properties(frame_change PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - unset(PYTHON_LIBRARIES) -endif() -target_link_libraries(frame_change - ${PYTHON_LIBRARIES}) - -target_include_directories(frame_change - PRIVATE ${PYTHON_INCLUDE_DIRS}) - -python_extension_module(frame_change - FORWARD_DECL_MODULES_VAR fdecl_module_list) - -python_modules_header(modules - FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) - -include_directories(${modules_INCLUDE_DIRS}) - -install(TARGETS frame_change LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) - -# Solver -add_cython_target(solver solver.pyx CXX) -add_library(solver MODULE ${solver}) -set_target_properties(solver PROPERTIES - LINKER_LANGUAGE CXX - CXX_STANDARD 14) -if(APPLE) - set_target_properties(solver PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") - unset(PYTHON_LIBRARIES) -endif() -target_link_libraries(solver - ${PYTHON_LIBRARIES}) - -target_include_directories(solver - PRIVATE ${PYTHON_INCLUDE_DIRS}) - -python_extension_module(solver - FORWARD_DECL_MODULES_VAR fdecl_module_list) - -python_modules_header(modules - FORWARD_DECL_MODULES_LIST ${fdecl_module_list}) - -include_directories(${modules_INCLUDE_DIRS}) - -install(TARGETS solver LIBRARY DESTINATION qiskit/providers/aer/pulse/cython) \ No newline at end of file diff --git a/qiskit/providers/aer/pulse/cython/frame_change.pyx b/qiskit/providers/aer/pulse/cython/frame_change.pyx deleted file mode 100644 index 473ebb6043..0000000000 --- a/qiskit/providers/aer/pulse/cython/frame_change.pyx +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -#!python -#cython: language_level = 3 -#distutils: language = c++ - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -cimport cython - -cdef extern from "" namespace "std" nogil: - double complex exp(double complex x) - -@cython.cdivision(True) -@cython.boundscheck(False) -cdef void compute_fc_value(double t, int chan_idx, double[::1] fc_array, - complex[::1] cnt_fc, - unsigned int[::1] fc_idx, - unsigned int[::1] register): - """ - Computes the frame change value at time `t` on the - channel labeled by `chan_idx`. The result is stored - in the current frame change array at `cnt_fc[chan_idx]`. - - Args: - t (double): Current time. - chan_idx (int): Index of channel. - fc_array (ndarray): Frame change array of doubles. - cnt_fc (ndarray): Complex values for current frame change values. - fc_idx (ndarray): Ints for frame change index. - register (ndarray): Classical register of ints. - - """ - cdef unsigned int arr_len = fc_array.shape[0] - cdef unsigned int do_fc - - if fc_idx[chan_idx] < arr_len: - while t >= fc_array[fc_idx[chan_idx]]: - do_fc = 1 - # Check if FC is conditioned on register - if fc_array[fc_idx[chan_idx]+2] >= 0: - # If condition not satisfied no do FC - if not register[fc_array[fc_idx[chan_idx]+2]]: - do_fc = 0 - if do_fc: - # Update the frame change value - cnt_fc[chan_idx] *= exp(1j*fc_array[fc_idx[chan_idx]+1]) - # update the index - fc_idx[chan_idx] += 3 - # check if we hit the end - if fc_idx[chan_idx] == arr_len: - break - - -def check_fc_compute(double t, int chan_idx, double[::1] fc_array, - complex[::1] cnt_fc, - unsigned int[::1] fc_idx, - unsigned int[::1] register): - """ - Python function to check the compute_fc_value is working. - """ - compute_fc_value(t, chan_idx, fc_array, cnt_fc, fc_idx, register) \ No newline at end of file diff --git a/qiskit/providers/aer/pulse/cython/solver.pyx b/qiskit/providers/aer/pulse/cython/solver.pyx deleted file mode 100644 index 7cdd10f823..0000000000 --- a/qiskit/providers/aer/pulse/cython/solver.pyx +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -#!python -#cython: language_level = 3 -#distutils: language = c++ - -# Copyright 2019, IBM. -# -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. - -cimport cython -from libc.math cimport floor - -@cython.cdivision(True) -cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): - """ - Computes the index corresponding to time `t` in [`start`, 'stop') for - an array of length `len_arr`. - - Args: - t (double): Time. - start (double): Start time. - stop (double): Stop time. - len_arr (int): Length of array. - - Returns: - int: Array index. - """ - return floor(((t-start)/(stop-start)*(len_arr-1))) - - -def test_get_arr_idx(double t, double start, double stop, int len_arr): - return get_arr_idx(t, start, stop, len_arr) \ No newline at end of file From 0a095f73cd6e1ab5664d308ac0133781ceb6a05b Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 3 Apr 2019 06:27:24 -0400 Subject: [PATCH 07/31] ignore notebooks --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index deb380c221..bc455494f8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ qiskit/providers/aer/openpulse/cython/channel_value.cpp qiskit/providers/aer/openpulse/cython/measure.cpp qiskit/providers/aer/openpulse/cython/memory.cpp qiskit/providers/aer/openpulse/cython/utils.cpp +*.ipynb From ea42204e9f8164fa0a0b3537614937c4af423cec Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Thu, 23 May 2019 10:56:42 -0400 Subject: [PATCH 08/31] license --- qiskit/providers/aer/openpulse/__init__.py | 13 +++++-- .../aer/openpulse/cython/__init__.py | 13 +++++-- .../aer/openpulse/cython/channel_value.pxd | 13 +++++-- .../aer/openpulse/cython/channel_value.pyx | 13 +++++-- .../aer/openpulse/cython/measure.pyx | 15 ++++++-- .../providers/aer/openpulse/cython/memory.pyx | 13 +++++-- .../providers/aer/openpulse/cython/setup.py | 12 +++++++ .../providers/aer/openpulse/cython/utils.pyx | 13 +++++-- .../providers/aer/openpulse/qobj/__init__.py | 13 +++++-- qiskit/providers/aer/openpulse/qobj/digest.py | 14 ++++++-- .../providers/aer/openpulse/qobj/op_qobj.py | 13 +++++-- .../providers/aer/openpulse/qobj/op_system.py | 13 +++++-- .../providers/aer/openpulse/qobj/operators.py | 13 +++++-- .../providers/aer/openpulse/qobj/opparse.py | 13 +++++-- .../aer/openpulse/solver/__init__.py | 13 +++++-- .../providers/aer/openpulse/solver/codegen.py | 34 ++++++++++++++----- .../aer/openpulse/solver/data_config.py | 14 ++++++-- .../aer/openpulse/solver/monte_carlo.py | 19 +++++++++-- .../providers/aer/openpulse/solver/opsolve.py | 13 +++++-- .../providers/aer/openpulse/solver/options.py | 13 +++++-- .../aer/openpulse/solver/rhs_utils.py | 14 ++++++-- .../aer/openpulse/solver/settings.py | 13 +++++-- .../providers/aer/openpulse/solver/unitary.py | 24 ++++++++----- .../providers/aer/openpulse/solver/zvode.py | 16 ++++++--- 24 files changed, 277 insertions(+), 80 deletions(-) diff --git a/qiskit/providers/aer/openpulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py index 206e02adea..7909fc6dac 100644 --- a/qiskit/providers/aer/openpulse/__init__.py +++ b/qiskit/providers/aer/openpulse/__init__.py @@ -1,6 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/aer/openpulse/cython/__init__.py b/qiskit/providers/aer/openpulse/cython/__init__.py index 206e02adea..7909fc6dac 100644 --- a/qiskit/providers/aer/openpulse/cython/__init__.py +++ b/qiskit/providers/aer/openpulse/cython/__init__.py @@ -1,6 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pxd b/qiskit/providers/aer/openpulse/cython/channel_value.pxd index 3207c26c30..a3faf0e350 100644 --- a/qiskit/providers/aer/openpulse/cython/channel_value.pxd +++ b/qiskit/providers/aer/openpulse/cython/channel_value.pxd @@ -3,10 +3,17 @@ #cython: language_level = 3 #distutils: language = c++ -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. cdef complex channel_value(double t, unsigned int chan_num, diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pyx b/qiskit/providers/aer/openpulse/cython/channel_value.pyx index bd9c6c7e57..a98dd7935e 100644 --- a/qiskit/providers/aer/openpulse/cython/channel_value.pyx +++ b/qiskit/providers/aer/openpulse/cython/channel_value.pyx @@ -3,10 +3,17 @@ #cython: language_level = 3 #distutils: language = c++ -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. cimport cython diff --git a/qiskit/providers/aer/openpulse/cython/measure.pyx b/qiskit/providers/aer/openpulse/cython/measure.pyx index a8786d1ee2..3191d87e01 100644 --- a/qiskit/providers/aer/openpulse/cython/measure.pyx +++ b/qiskit/providers/aer/openpulse/cython/measure.pyx @@ -3,10 +3,19 @@ #cython: language_level = 3 #distutils: language = c++ -# Copyright 2019, IBM. +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. cimport cython import numpy as np diff --git a/qiskit/providers/aer/openpulse/cython/memory.pyx b/qiskit/providers/aer/openpulse/cython/memory.pyx index b20d1c61dc..6cbfbd9d38 100644 --- a/qiskit/providers/aer/openpulse/cython/memory.pyx +++ b/qiskit/providers/aer/openpulse/cython/memory.pyx @@ -3,10 +3,17 @@ #cython: language_level = 3 #distutils: language = c++ -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. cimport cython diff --git a/qiskit/providers/aer/openpulse/cython/setup.py b/qiskit/providers/aer/openpulse/cython/setup.py index de7b621e2f..d4abe2e767 100644 --- a/qiskit/providers/aer/openpulse/cython/setup.py +++ b/qiskit/providers/aer/openpulse/cython/setup.py @@ -1,4 +1,16 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + import distutils.sysconfig import os import sys diff --git a/qiskit/providers/aer/openpulse/cython/utils.pyx b/qiskit/providers/aer/openpulse/cython/utils.pyx index 06eef4ec19..fc624c6d37 100644 --- a/qiskit/providers/aer/openpulse/cython/utils.pyx +++ b/qiskit/providers/aer/openpulse/cython/utils.pyx @@ -3,10 +3,17 @@ #cython: language_level = 3 #distutils: language = c++ -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. cimport cython diff --git a/qiskit/providers/aer/openpulse/qobj/__init__.py b/qiskit/providers/aer/openpulse/qobj/__init__.py index 206e02adea..7909fc6dac 100644 --- a/qiskit/providers/aer/openpulse/qobj/__init__.py +++ b/qiskit/providers/aer/openpulse/qobj/__init__.py @@ -1,6 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index a05870d724..fe59a4791c 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -1,9 +1,17 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + import numpy as np from collections import OrderedDict from qiskit.qiskiterror import QiskitError diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py index d5035daa8c..a3d94eb111 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_qobj.py +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # pylint: disable=invalid-name import numpy as np diff --git a/qiskit/providers/aer/openpulse/qobj/op_system.py b/qiskit/providers/aer/openpulse/qobj/op_system.py index 0d6081f3e5..9c596de4bf 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_system.py +++ b/qiskit/providers/aer/openpulse/qobj/op_system.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # pylint: disable=invalid-name class OPSystem(): diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 21ac51dc0c..2edb9985e9 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. import numpy as np import scipy.linalg as la from openpulse.qobj import op_qobj as op diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index 6f31723a59..e8b79f353d 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # pylint: disable=invalid-name import re diff --git a/qiskit/providers/aer/openpulse/solver/__init__.py b/qiskit/providers/aer/openpulse/solver/__init__.py index 206e02adea..7909fc6dac 100644 --- a/qiskit/providers/aer/openpulse/solver/__init__.py +++ b/qiskit/providers/aer/openpulse/solver/__init__.py @@ -1,6 +1,13 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index 97a09c9fc5..f3e73600a9 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -1,9 +1,22 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. + import os import sys import numpy as np @@ -106,7 +119,6 @@ def ODE_func_header(self): # register input_vars += (",\n " + "unsigned char[::1] register") - func_end = "):" return [func_name + input_vars + func_end] @@ -184,11 +196,17 @@ def cython_preamble(): preamble = ["""\ #!python #cython: language_level=3 -# This file is generated automatically by Qiskit. -# Copyright 2019, IBM. +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. import numpy as np cimport numpy as np diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py index 2eded9b8be..87fea5aec8 100644 --- a/qiskit/providers/aer/openpulse/solver/data_config.py +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -1,9 +1,17 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + import numpy as np def op_data_config(op_system): diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index 8ce0fcc5c5..bc1d5cf8a7 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -1,9 +1,22 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. import numpy as np from scipy.integrate import ode diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index 3f0c774260..a21c707bb0 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. import os import sys import math diff --git a/qiskit/providers/aer/openpulse/solver/options.py b/qiskit/providers/aer/openpulse/solver/options.py index 1d32233250..019894f105 100644 --- a/qiskit/providers/aer/openpulse/solver/options.py +++ b/qiskit/providers/aer/openpulse/solver/options.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. class OPoptions(object): diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index 800900bc37..19ff3a5f24 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -1,9 +1,17 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + import os from openpulse.solver.codegen import OPCodegen import openpulse.solver.settings as op_set diff --git a/qiskit/providers/aer/openpulse/solver/settings.py b/qiskit/providers/aer/openpulse/solver/settings.py index 890ea8e43a..05445eed40 100644 --- a/qiskit/providers/aer/openpulse/solver/settings.py +++ b/qiskit/providers/aer/openpulse/solver/settings.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # Code generation counter CGEN_NUM = 0 diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index 50ff105276..68e2d851f8 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -1,9 +1,16 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # pylint: disable = unused-variable, no-name-in-module import numpy as np @@ -34,14 +41,13 @@ def unitary_evolution(exp, global_data, ode_options): tlist = exp['tlist'] snapshots = [] shots = global_data['shots'] + # Init memory memory = np.zeros((shots, global_data['memory_slots']), dtype=np.uint8) - + # Init register register = np.zeros(global_data['n_registers'], dtype=np.uint8) + num_channels = len(exp['channels']) - chan_pulse_idx = np.zeros(num_channels, dtype=np.uint32) - chan_fc_idx = np.zeros(num_channels, dtype=np.uint32) - fc_values = np.ones(num_channels, dtype=complex) ODE = ode(cy_rhs_func) ODE.set_integrator('zvode', @@ -69,6 +75,8 @@ def unitary_evolution(exp, global_data, ode_options): raise Exception(err_msg) # Do any snapshots here + + # set channel and frame change indexing arrays # Do final measurement at end @@ -79,8 +87,8 @@ def unitary_evolution(exp, global_data, ode_options): write_shots_memory(memory, memory_slots, probs, rand_vals) int_mem = memory.dot(np.power(2.0, np.arange(memory.shape[1]-1,-1,-1))).astype(int) - hex_mem = [hex(val) for val in int_mem] if global_data['memory']: + hex_mem = [hex(val) for val in int_mem] return hex_mem # Get hex counts dict unique = np.unique(int_mem, return_counts = True) diff --git a/qiskit/providers/aer/openpulse/solver/zvode.py b/qiskit/providers/aer/openpulse/solver/zvode.py index 2204935c9b..219a117dc4 100644 --- a/qiskit/providers/aer/openpulse/solver/zvode.py +++ b/qiskit/providers/aer/openpulse/solver/zvode.py @@ -1,16 +1,24 @@ # -*- coding: utf-8 -*- -# Copyright 2019, IBM. +# This code is part of Qiskit. # -# This source code is licensed under the Apache License, Version 2.0 found in -# the LICENSE.txt file in the root directory of this source tree. -# pylint: disable=no-value-for-parameter +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. # All rights reserved. +# pylint: disable=no-value-for-parameter + from scipy.integrate._ode import zvode From 208ddd735ea8f7ddf790f0fb995e9cbefe80b6b3 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 11 Jun 2019 13:14:44 -0400 Subject: [PATCH 09/31] get noise working --- .../aer/openpulse/cython/measure.pyx | 2 +- .../providers/aer/openpulse/cython/memory.pyx | 2 +- qiskit/providers/aer/openpulse/qobj/digest.py | 6 +- .../providers/aer/openpulse/qobj/operators.py | 2 +- .../providers/aer/openpulse/solver/codegen.py | 11 +- .../aer/openpulse/solver/data_config.py | 13 ++- .../aer/openpulse/solver/monte_carlo.py | 105 ++++++++++-------- .../providers/aer/openpulse/solver/opsolve.py | 31 +++++- .../providers/aer/openpulse/solver/unitary.py | 5 + 9 files changed, 115 insertions(+), 62 deletions(-) diff --git a/qiskit/providers/aer/openpulse/cython/measure.pyx b/qiskit/providers/aer/openpulse/cython/measure.pyx index 3191d87e01..0c5fda6be1 100644 --- a/qiskit/providers/aer/openpulse/cython/measure.pyx +++ b/qiskit/providers/aer/openpulse/cython/measure.pyx @@ -59,6 +59,6 @@ def write_shots_memory(unsigned char[:, ::1] mem, cdef unsigned char temp for ii in range(nrows): for jj in range(nprobs): - temp = (probs[jj] < rand_vals[nprobs*ii+jj]) + temp = (probs[jj] > rand_vals[nprobs*ii+jj]) if temp: mem[ii,mem_slots[jj]] = temp \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cython/memory.pyx b/qiskit/providers/aer/openpulse/cython/memory.pyx index 6cbfbd9d38..fe762ca543 100644 --- a/qiskit/providers/aer/openpulse/cython/memory.pyx +++ b/qiskit/providers/aer/openpulse/cython/memory.pyx @@ -39,6 +39,6 @@ def write_memory(unsigned char[:, ::1] mem, cdef unsigned char temp for ii in range(nrows): for jj in range(nprobs): - temp = (probs[jj] < rand_vals[nprobs*ii+jj]) + temp = (probs[jj] > rand_vals[nprobs*ii+jj]) if temp: mem[ii,memory_slots[jj]] = temp \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index fe59a4791c..1ccace4545 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -44,7 +44,7 @@ def digest_pulse_obj(qobj): # Look for config keys out.global_data['shots'] = 1024 if 'shots' in config_keys: - out.global_data['shots'] = qobj['config']['shots'] + out.global_data['shots'] = int(qobj['config']['shots']) out.global_data['seed'] = None if 'seed' in config_keys: @@ -123,7 +123,9 @@ def digest_pulse_obj(qobj): noise.parse() out.noise = noise.compiled - out.can_sample = False + if any(out.noise): + out.can_sample = False + out.global_data['c_num'] = len(out.noise) else: out.noise = None diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 2edb9985e9..b180128c45 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -65,7 +65,7 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): return op.tensor(opers) -def qubit_occ_oper(target_qubit, h_osc, h_qub, level=1): +def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): """Builds the occupation number operator for a target qubit in a qubit oscillator system, where the oscillator are the first subsystems, and the qubit last. diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index f3e73600a9..4c472b29a1 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -40,7 +40,7 @@ def __init__(self, op_system): self.op_system = op_system self.dt = op_system.dt - self.num_ham_terms = len(self.op_system.system) + self.num_ham_terms = self.op_system.global_data['num_h_terms'] # Code generator properties self._file = None @@ -157,6 +157,15 @@ def func_vars(self): spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ "&vec[0], 1.0, &out[0], num_rows)" func_vars.append(spmv_str.format(i=idx)) + + # There is a noise term + if len(self.op_system.system) < self.num_ham_terms: + spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ + "&vec[0], 1.0, &out[0], num_rows)" + func_vars.append("") + func_vars.append("# Noise term") + func_vars.append(spmv_str.format(i=idx+1)) + return func_vars diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py index 87fea5aec8..d526fa8fb7 100644 --- a/qiskit/providers/aer/openpulse/solver/data_config.py +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -31,15 +31,17 @@ def op_data_config(op_system): op_system.global_data['c_num'] = 0 if op_system.noise: op_system.global_data['c_num'] = len(op_system.noise) + op_system.global_data['num_h_terms'] += 1 op_system.global_data['c_ops_data'] = [] op_system.global_data['c_ops_ind'] = [] op_system.global_data['c_ops_ptr'] = [] op_system.global_data['n_ops_data'] = [] op_system.global_data['n_ops_ind'] = [] - op_system.global_data['n_ops_ind'] = [] + op_system.global_data['n_ops_ptr'] = [] # if there are any collapse operators + H_noise = 0 for kk in range(op_system.global_data['c_num']): c_op = op_system.noise[kk] n_op = c_op.dag() * c_op @@ -50,10 +52,13 @@ def op_data_config(op_system): # norm ops op_system.global_data['n_ops_data'].append(n_op.data.data) op_system.global_data['n_ops_ind'].append(n_op.data.indices) - op_system.global_data['n_ops_ind'].append(n_op.data.indptr) + op_system.global_data['n_ops_ptr'].append(n_op.data.indptr) # Norm ops added to time-independent part of # Hamiltonian to decrease norm - H[0] -= 0.5j * n_op + H_noise -= 0.5j * n_op + + if H_noise: + H = H + [H_noise] # construct data sets op_system.global_data['h_ops_data'] = [-1.0j* hpart.data.data for hpart in H] @@ -63,7 +68,7 @@ def op_data_config(op_system): # setup ode args string ode_var_str = "" # Hamiltonian data - for kk in range(num_h_terms): + for kk in range(op_system.global_data['num_h_terms']): h_str = "global_data['h_ops_data'][%s], " % kk h_str += "global_data['h_ops_ind'][%s], " % kk h_str += "global_data['h_ops_ptr'][%s], " % kk diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index bc1d5cf8a7..cf1c56f70f 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -18,46 +18,51 @@ # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. # All rights reserved. +from math import log import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from openpulse.solver.zvode import qiskit_zvode +from openpulse.cython.memory import write_memory +from openpulse.cython.measure import occ_probabilities, write_shots_memory dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) -def monte_carlo(pid, ophandler, ode_options): +def monte_carlo(seed, exp, global_data, ode_options): """ Monte Carlo algorithm returning state-vector or expectation values at times tlist for a single trajectory. """ - global _cy_rhs_func - - tlist = ophandler._data['tlist'] - memory = [ - [ - 0 for _l in range(ophandler.qobj_config['memory_slot_size']) - ] for _r in range(ophandler.qobj_config['memory_slots']) - ] - register = bytearray(ophandler.backend_config['n_registers']) - - opt = ophandler._options + cy_rhs_func = global_data['rhs_func'] + rng = np.random.RandomState(seed) + tlist = exp['tlist'] + snapshots = [] + # Init memory + memory = np.zeros((1, global_data['memory_slots']), dtype=np.uint8) + # Init register + register = np.zeros(global_data['n_registers'], dtype=np.uint8) + + # Get number of acquire, snapshots, and conditionals + num_acq = len(exp['acquire']) + acq_idx = 0 + num_snap = len(exp['snapshot']) + snap_idx = 0 + num_cond = len(exp['cond']) + cond_idx = 0 collapse_times = [] collapse_operators = [] - # SEED AND RNG AND GENERATE - prng = RandomState(ophandler._options.seeds[pid]) # first rand is collapse norm, second is which operator - rand_vals = prng.rand(2) + rand_vals = rng.rand(2) - ODE = ode(_cy_rhs_func) + ODE = ode(cy_rhs_func) - _inst = 'ODE.set_f_params(%s)' % ophandler._data['string'] + _inst = 'ODE.set_f_params(%s)' % global_data['string'] code = compile(_inst, '', 'exec') exec(code) - psi = ophandler._data['psi0'] # initialize ODE solver for RHS ODE._integrator = qiskit_zvode(method=ode_options.method, @@ -69,27 +74,27 @@ def monte_carlo(pid, ophandler, ode_options): min_step=ode_options.min_step, max_step=ode_options.max_step ) - + # Forces complex ODE solving if not len(ODE._y): ODE.t = 0.0 ODE._y = np.array([0.0], complex) ODE._integrator.reset(len(ODE._y), ODE.jac is not None) + + ODE.set_initial_value(global_data['initial_state'], 0) # make array for collapse operator inds - cinds = np.arange(ophandler._data['c_num']) - n_dp = np.zeros(ophandler._data['c_num'], dtype=float) + cinds = np.arange(global_data['c_num']) + n_dp = np.zeros(global_data['c_num'], dtype=float) - kk_prev = 0 # RUN ODE UNTIL EACH TIME IN TLIST - for kk in tlist: - ODE.set_initial_value(psi, kk_prev) + for stop_time in tlist: # ODE WHILE LOOP FOR INTEGRATE UP TO TIME TLIST[k] - while ODE.t < kk: + while ODE.t < stop_time: t_prev = ODE.t y_prev = ODE.y norm2_prev = dznrm2(ODE._y) ** 2 - # integrate up to kk, one step at a time. - ODE.integrate(kk, step=1) + # integrate up to stop_time, one step at a time. + ODE.integrate(stop_time, step=1) if not ODE.successful(): raise Exception("ZVODE step failed!") norm2_psi = dznrm2(ODE._y) ** 2 @@ -99,11 +104,11 @@ def monte_carlo(pid, ophandler, ode_options): # ------------------------------------------------ ii = 0 t_final = ODE.t - while ii < ophandler._options.norm_steps: + while ii < ode_options.norm_steps: ii += 1 t_guess = t_prev + \ - math.log(norm2_prev / rand_vals[0]) / \ - math.log(norm2_prev / norm2_psi) * (t_final - t_prev) + log(norm2_prev / rand_vals[0]) / \ + log(norm2_prev / norm2_psi) * (t_final - t_prev) ODE._y = y_prev ODE.t = t_prev ODE._integrator.call_args[3] = 1 @@ -113,7 +118,7 @@ def monte_carlo(pid, ophandler, ode_options): "ZVODE failed after adjusting step size!") norm2_guess = dznrm2(ODE._y)**2 if (abs(rand_vals[0] - norm2_guess) < - ophandler._options.norm_tol * rand_vals[0]): + ode_options.norm_tol * rand_vals[0]): break elif (norm2_guess < rand_vals[0]): # t_guess is still > t_jump @@ -124,7 +129,7 @@ def monte_carlo(pid, ophandler, ode_options): t_prev = t_guess y_prev = ODE.y norm2_prev = norm2_guess - if ii > ophandler._options.norm_steps: + if ii > ode_options.norm_steps: raise Exception("Norm tolerance not reached. " + "Increase accuracy of ODE solver or " + "Options.norm_steps.") @@ -132,9 +137,9 @@ def monte_carlo(pid, ophandler, ode_options): collapse_times.append(ODE.t) # all constant collapse operators. for i in range(n_dp.shape[0]): - n_dp[i] = cy_expect_psi_csr(ophandler._data['n_ops_data'][i], - ophandler._data['n_ops_ind'][i], - ophandler._data['n_ops_ptr'][i], + n_dp[i] = cy_expect_psi_csr(global_data['n_ops_data'][i], + global_data['n_ops_ind'][i], + global_data['n_ops_ptr'][i], ODE._y, 1) # determine which operator does collapse and store it @@ -142,23 +147,29 @@ def monte_carlo(pid, ophandler, ode_options): j = cinds[_p >= rand_vals[1]][0] collapse_operators.append(j) - state = spmv_csr(ophandler._data['c_ops_data'][j], - ophandler._data['c_ops_ind'][j], - ophandler._data['c_ops_ptr'][j], + state = spmv_csr(global_data['c_ops_data'][j], + global_data['c_ops_ind'][j], + global_data['c_ops_ptr'][j], ODE._y) state /= dznrm2(state) ODE._y = state ODE._integrator.call_args[3] = 1 - rand_vals = prng.rand(2) + rand_vals = rng.rand(2) # after while loop (Do measurement or conditional) - # ---------------- + # ------------------------------------------------ out_psi = ODE._y / dznrm2(ODE._y) - - # measurement - psi = _proj_measurement(pid, ophandler, kk, out_psi, memory, register) - - kk_prev = kk - - return psi, memory \ No newline at end of file + for aind in range(acq_idx, num_acq): + if exp['acquire'][aind][0] == stop_time: + current_acq = exp['acquire'][aind] + qubits = current_acq[1] + memory_slots = current_acq[2] + probs = occ_probabilities(qubits, out_psi, global_data['measurement_ops']) + rand_vals = rng.rand(memory_slots.shape[0]) + write_shots_memory(memory, memory_slots, probs, rand_vals) + int_mem = memory.dot(np.power(2.0, + np.arange(memory.shape[1]-1,-1,-1))).astype(int) + acq_idx += 1 + + return int_mem \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index a21c707bb0..28c4de3a55 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -32,6 +32,7 @@ from openpulse.solver.data_config import op_data_config import openpulse.solver.settings as settings from openpulse.solver.unitary import unitary_evolution +from openpulse.solver.monte_carlo import monte_carlo from qiskit.tools.parallel import parallel_map dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -53,12 +54,12 @@ def opsolve(op_system): if not op_system.ode_options.num_cpus: op_system.ode_options.num_cpus = qutip.settings.num_cpus + # build Hamiltonian data structures + op_data_config(op_system) # compile Cython RHS _op_generate_rhs(op_system) # Load cython function _op_func_load(op_system) - # build Hamiltonian data structures - op_data_config(op_system) # load monte carlo class mc = OP_mcwf(op_system) # Run the simulation @@ -102,10 +103,10 @@ def __init__(self, op_system): def run(self): + map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} + # If no collapse terms, and only measurements at end # can do a single shot. - map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} - if self.op_system.can_sample: results = parallel_map(unitary_evolution, self.op_system.experiments, @@ -113,7 +114,27 @@ def run(self): self.op_system.ode_options ), **map_kwargs ) - + + # need to simulate each trajectory, so shots*len(experiments) times + # Do a for-loop over experiments, and do shots in parallel_map + else: + results = [] + for exp in self.op_system.experiments: + rng = np.random.RandomState(exp['seed']) + seeds = rng.randint(np.iinfo(np.int32).max-1, + size=self.op_system.global_data['shots']) + exp_res = parallel_map(monte_carlo, + seeds, + task_args=(exp, self.op_system.global_data, + self.op_system.ode_options + ), **map_kwargs + ) + unique = np.unique(exp_res, return_counts=True) + hex_dict = {} + for kk in range(unique[0].shape[0]): + key = hex(unique[0][kk]) + hex_dict[key] = unique[1][kk] + results.append(hex_dict) _cython_build_cleanup(self.op_system.global_data['rhs_file_name']) return results diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index 68e2d851f8..fc84784c07 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -64,6 +64,11 @@ def unitary_evolution(exp, global_data, ode_options): code = compile(_inst, '', 'exec') exec(code) + if not len(ODE._y): + ODE.t = 0.0 + ODE._y = np.array([0.0], complex) + ODE._integrator.reset(len(ODE._y), ODE.jac is not None) + # Since all experiments are defined to start at zero time. ODE.set_initial_value(global_data['initial_state'], 0) for kk in tlist[1:]: From 494dcf2ce9bbbdedfe328b2a2717af27d7795d41 Mon Sep 17 00:00:00 2001 From: nkanazawa <39517270+nkanazawa1989@users.noreply.github.com> Date: Mon, 24 Jun 2019 13:12:08 +0900 Subject: [PATCH 10/31] change dim dict to list, change noise config (#2) --- .../providers/aer/openpulse/qobj/operators.py | 83 +++++++++---------- .../providers/aer/openpulse/qobj/opparse.py | 76 ++++++++--------- 2 files changed, 74 insertions(+), 85 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index b180128c45..5546e7f795 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -23,8 +23,8 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): ---------- opname (str): Name of the operator to be returned. index (int): Index of operator. - h_osc (dict): Dimension of oscillator subspace - h_qub (dict): Dimension of qubit subspace + h_osc (list): Dimension of oscillator subspace + h_qub (list): Dimension of qubit subspace states (tuple): State indices of projection operator. Returns @@ -35,34 +35,32 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): # get number of levels in Hilbert space if opname in ['X', 'Y', 'Z', 'Sp', 'Sm', 'I', 'O', 'P']: is_qubit = True - dim = h_qub.get(index, 2) + dim = h_qub[index] else: is_qubit = False - dim = h_osc.get(index, 5) + dim = h_osc[index] if opname == 'P': opr_tmp = op.get_oper(opname, dim, states) else: opr_tmp = op.get_oper(opname, dim) - # reverse sort by index - rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] - rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - - # osc_n * … * osc_0 * qubit_n * … * qubit_0 + # qubit_0 * … * qubit_n * osc_0 * … * osc_n opers = [] - for ii, dd in rev_h_osc: - if ii == index and not is_qubit: + for ii, dd in enumerate(h_qub): + if ii == index and is_qubit: opers.append(opr_tmp) else: opers.append(op.qeye(dd)) - for ii, dd in rev_h_qub: - if ii == index and is_qubit: + for ii, dd in enumerate(h_osc): + if ii == index and not is_qubit: opers.append(opr_tmp) else: opers.append(op.qeye(dd)) - return op.tensor(opers) + # return in reverse order + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + return op.tensor(opers[::-1]) def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): @@ -73,29 +71,28 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): Parameters ---------- target_qubit (int): Qubit for which operator is built. - h_osc (dict): Dict of number of levels in each oscillator. - h_qub (dict): Dict of number of levels in each qubit system. + h_osc (list): Dimension of oscillator subspace + h_qub (list): Dimension of qubit subspace level (int): Level of qubit system to be measured. Returns ------- out_oper (qutip.Qobj): Occupation number operator for target qubit. """ - # reverse sort by index - rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] - rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - # osc_n * … * osc_0 * qubit_n * … * qubit_0 + # qubit_0 * … * qubit_n * osc_0 * … * osc_n opers = [] - for ii, dd in rev_h_osc: - opers.append(op.qeye(dd)) - for ii, dd in rev_h_qub: + for ii, dd in enumerate(h_qub): if ii == target_qubit: - opers.append(op.fock_dm(h_qub.get(target_qubit, 2), level)) + opers.append(op.fock_dm(dd, level)) else: opers.append(op.qeye(dd)) + for ii, dd in enumerate(h_osc): + opers.append(op.qeye(dd)) - return op.tensor(opers) + # return in reverse order + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + return op.tensor(opers[::-1]) def measure_outcomes(measured_qubits, state_vector, measure_ops, @@ -139,8 +136,8 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): ---------- measured_qubits (list): measured qubit indices. results (list): results of qubit measurements. - h_qub (dict): Dict of number of levels in each qubit system. - h_osc (dict): Dict of number of levels in each oscillator. + h_osc (list): Dimension of oscillator subspace + h_qub (list): Dimension of qubit subspace state_vector (ndarray): State vector. Returns: @@ -148,21 +145,20 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): proj_state (qutip.Qobj): State vector after projector applied, and normalized. """ - # reverse sort by index - rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] - rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - - # osc_n * … * osc_0 * qubit_n * … * qubit_0 + # qubit_0 * … * qubit_n * osc_0 * … * osc_n opers = [] - for ii, dd in rev_h_osc: - opers.append(op.qeye(dd)) - for ii, dd in rev_h_qub: + for ii, dd in enumerate(h_qub): if ii in measured_qubits: opers.append(op.fock_dm(dd, results[ii])) else: opers.append(op.qeye(dd)) + for ii, dd in enumerate(h_osc): + opers.append(op.qeye(dd)) + + # return in reverse order + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + proj_oper = op.tensor(opers[::-1]) - proj_oper = op.tensor(opers) psi = op.opr_apply(proj_oper, state_vector) psi /= la.norm(psi) @@ -181,13 +177,12 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): Returns: qutip.Qobj: State vector """ - # reverse sort by index - rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] - rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - # osc_n * … * osc_0 * qubit_n * … * qubit_0 + # qubit_0 * … * qubit_n * osc_0 * … * osc_n sub_state_vecs = [] - for ii, dd in rev_h_osc: + for ii, dd in enumerate(h_qub): + sub_state_vecs.append(op.basis(dd, 0)) + for ii, dd in enumerate(h_osc): n_thermal = noise_dict['oscillator']['n_th'].get(str(ii), 0) if n_thermal == 0: # no thermal particles @@ -201,7 +196,7 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): cum_sum = np.cumsum(diags) idx = np.where(np.random.random() < cum_sum)[0][0] sub_state_vecs.append(op.basis(dd, idx)) - for ii, dd in rev_h_qub: - sub_state_vecs.append(op.basis(dd, 0)) - return op.tensor(sub_state_vecs) + # return in reverse order + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + return op.tensor(sub_state_vecs[::-1]) diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index e8b79f353d..9541c99010 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -41,15 +41,15 @@ class HamiltonianParser: """ Generate QuTip hamiltonian object from string """ - def __init__(self, h_str, dim_osc, dim_qub): + def __init__(self, hamiltonian, dim_osc, dim_qub): """ Create new quantum operator generator Parameters: - h_str (list): list of Hamiltonian string - dim_osc (dict): dimension of oscillator subspace - dim_qub (dict): dimension of qubit subspace + hamiltonian (list): list of Hamiltonian string + dim_osc (list): dimension of oscillator subspace + dim_qub (list): dimension of qubit subspace """ - self.h_str = h_str + self.hamiltonian = hamiltonian self.dim_osc = dim_osc self.dim_qub = dim_qub self.__td_hams = [] @@ -72,7 +72,7 @@ def parse(self): self._expand_sum() # convert to reverse Polish notation - for ham in self.h_str: + for ham in self.hamiltonian: if len(re.findall(r"\|\|", ham)) > 1: raise Exception("Multiple time-dependent terms in %s" % ham) p_td = re.search(r"(?P[\S]+)\|\|(?P[\S]+)", ham) @@ -102,7 +102,7 @@ def _expand_sum(self): sum_str = re.compile(r"_SUM\[(?P[a-z]),(?P[a-z\d{}+-]+),(?P[a-z\d{}+-]+),") brk_str = re.compile(r"]") - ham_list = copy.copy(self.h_str) + ham_list = copy.copy(self.hamiltonian) ham_out = [] while any(ham_list): @@ -144,7 +144,7 @@ def _expand_sum(self): trg_s, ham[p_brks[ii].end():]])) ham_list.extend(_temp) - self.h_str = ham_out + self.hamiltonian = ham_out return ham_out @@ -279,21 +279,18 @@ def _token2qobj(self, tokens): class NoiseParser: """ Generate QuTip noise object from dictionary - Qubit noise is given in the format of nested dictionary: - "qubit": { - "0": { - "Sm": 0.006 - } - } - and oscillator noise is given in the format of nested dictionary: - "oscillator": { - "n_th": { - "0": 0.001 - }, - "coupling": { - "0": 0.05 - } - } + Qubit noise is given in the format of list of dictionary: + + "qubit": [ + {"Sm": 0.006} + ] + + and oscillator noise is given in the nested list of (n_th, coupling): + + "oscillator": [ + [0.001, 0.05] + ] + these configurations are combined in the same dictionary """ def __init__(self, noise_dict, dim_osc, dim_qub): @@ -301,11 +298,11 @@ def __init__(self, noise_dict, dim_osc, dim_qub): Parameters: noise_dict (dict): dictionary of noise configuration - dim_osc (dict): dimension of oscillator subspace - dim_qub (dict): dimension of qubit subspace + dim_osc (list): dimension of oscillator subspace + dim_qub (list): dimension of qubit subspace """ - self.noise_osc = noise_dict.get('oscillator', {'n_th': {}, 'coupling': {}}) - self.noise_qub = noise_dict.get('qubit', {}) + self.noise_osc = noise_dict.get('oscillator', []) + self.noise_qub = noise_dict.get('qubit', []) self.dim_osc = dim_osc self.dim_qub = dim_qub self.__c_list = [] @@ -320,7 +317,7 @@ def parse(self): """ Parse and generate quantum class object """ # Qubit noise - for index, config in self.noise_qub.items(): + for index, config in enumerate(self.noise_qub): for opname, coef in config.items(): # TODO: support noise in multi-dimensional system # TODO: support noise with math operation @@ -330,19 +327,16 @@ def parse(self): raise Exception('Unsupported noise operator %s is given' % opname) self.__c_list.append(np.sqrt(coef) * opr) # Oscillator noise - ndic = self.noise_osc['n_th'] - cdic = self.noise_osc['coupling'] - for (n_ii, n_coef), (c_ii, c_coef) in zip(ndic.items(), cdic.items()): - if n_ii == c_ii: - if c_coef > 0: - opr = gen_oper('A', int(n_ii), self.dim_osc, self.dim_qub) - if n_coef: - self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr) - self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag()) - else: - self.__c_list.append(np.sqrt(c_coef) * opr) - else: - raise Exception('Invalid oscillator index in noise dictionary.') + ndic = [nconf[0] for nconf in self.noise_osc] + cdic = [nconf[1] for nconf in self.noise_osc] + for ii, (n_coef, c_coef) in enumerate(zip(ndic, cdic)): + if c_coef > 0: + opr = gen_oper('A', int(ii), self.dim_osc, self.dim_qub) + if n_coef: + self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr) + self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag()) + else: + self.__c_list.append(np.sqrt(c_coef) * opr) def math_priority(o1, o2): From 77f77b706c91c8fc6d9dd5c5a206d4f0d6cbd91d Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 25 Jun 2019 07:09:53 -0700 Subject: [PATCH 11/31] Revert "change dim dict to list, change noise config (#2)" (#4) This reverts commit 494dcf2ce9bbbdedfe328b2a2717af27d7795d41. --- .../providers/aer/openpulse/qobj/operators.py | 83 ++++++++++--------- .../providers/aer/openpulse/qobj/opparse.py | 76 +++++++++-------- 2 files changed, 85 insertions(+), 74 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 5546e7f795..b180128c45 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -23,8 +23,8 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): ---------- opname (str): Name of the operator to be returned. index (int): Index of operator. - h_osc (list): Dimension of oscillator subspace - h_qub (list): Dimension of qubit subspace + h_osc (dict): Dimension of oscillator subspace + h_qub (dict): Dimension of qubit subspace states (tuple): State indices of projection operator. Returns @@ -35,32 +35,34 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): # get number of levels in Hilbert space if opname in ['X', 'Y', 'Z', 'Sp', 'Sm', 'I', 'O', 'P']: is_qubit = True - dim = h_qub[index] + dim = h_qub.get(index, 2) else: is_qubit = False - dim = h_osc[index] + dim = h_osc.get(index, 5) if opname == 'P': opr_tmp = op.get_oper(opname, dim, states) else: opr_tmp = op.get_oper(opname, dim) - # qubit_0 * … * qubit_n * osc_0 * … * osc_n + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 opers = [] - for ii, dd in enumerate(h_qub): - if ii == index and is_qubit: + for ii, dd in rev_h_osc: + if ii == index and not is_qubit: opers.append(opr_tmp) else: opers.append(op.qeye(dd)) - for ii, dd in enumerate(h_osc): - if ii == index and not is_qubit: + for ii, dd in rev_h_qub: + if ii == index and is_qubit: opers.append(opr_tmp) else: opers.append(op.qeye(dd)) - # return in reverse order - # osc_n * … * osc_0 * qubit_n * … * qubit_0 - return op.tensor(opers[::-1]) + return op.tensor(opers) def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): @@ -71,28 +73,29 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): Parameters ---------- target_qubit (int): Qubit for which operator is built. - h_osc (list): Dimension of oscillator subspace - h_qub (list): Dimension of qubit subspace + h_osc (dict): Dict of number of levels in each oscillator. + h_qub (dict): Dict of number of levels in each qubit system. level (int): Level of qubit system to be measured. Returns ------- out_oper (qutip.Qobj): Occupation number operator for target qubit. """ + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - # qubit_0 * … * qubit_n * osc_0 * … * osc_n + # osc_n * … * osc_0 * qubit_n * … * qubit_0 opers = [] - for ii, dd in enumerate(h_qub): + for ii, dd in rev_h_osc: + opers.append(op.qeye(dd)) + for ii, dd in rev_h_qub: if ii == target_qubit: - opers.append(op.fock_dm(dd, level)) + opers.append(op.fock_dm(h_qub.get(target_qubit, 2), level)) else: opers.append(op.qeye(dd)) - for ii, dd in enumerate(h_osc): - opers.append(op.qeye(dd)) - # return in reverse order - # osc_n * … * osc_0 * qubit_n * … * qubit_0 - return op.tensor(opers[::-1]) + return op.tensor(opers) def measure_outcomes(measured_qubits, state_vector, measure_ops, @@ -136,8 +139,8 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): ---------- measured_qubits (list): measured qubit indices. results (list): results of qubit measurements. - h_osc (list): Dimension of oscillator subspace - h_qub (list): Dimension of qubit subspace + h_qub (dict): Dict of number of levels in each qubit system. + h_osc (dict): Dict of number of levels in each oscillator. state_vector (ndarray): State vector. Returns: @@ -145,20 +148,21 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): proj_state (qutip.Qobj): State vector after projector applied, and normalized. """ - # qubit_0 * … * qubit_n * osc_0 * … * osc_n + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 opers = [] - for ii, dd in enumerate(h_qub): + for ii, dd in rev_h_osc: + opers.append(op.qeye(dd)) + for ii, dd in rev_h_qub: if ii in measured_qubits: opers.append(op.fock_dm(dd, results[ii])) else: opers.append(op.qeye(dd)) - for ii, dd in enumerate(h_osc): - opers.append(op.qeye(dd)) - - # return in reverse order - # osc_n * … * osc_0 * qubit_n * … * qubit_0 - proj_oper = op.tensor(opers[::-1]) + proj_oper = op.tensor(opers) psi = op.opr_apply(proj_oper, state_vector) psi /= la.norm(psi) @@ -177,12 +181,13 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): Returns: qutip.Qobj: State vector """ + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] - # qubit_0 * … * qubit_n * osc_0 * … * osc_n + # osc_n * … * osc_0 * qubit_n * … * qubit_0 sub_state_vecs = [] - for ii, dd in enumerate(h_qub): - sub_state_vecs.append(op.basis(dd, 0)) - for ii, dd in enumerate(h_osc): + for ii, dd in rev_h_osc: n_thermal = noise_dict['oscillator']['n_th'].get(str(ii), 0) if n_thermal == 0: # no thermal particles @@ -196,7 +201,7 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): cum_sum = np.cumsum(diags) idx = np.where(np.random.random() < cum_sum)[0][0] sub_state_vecs.append(op.basis(dd, idx)) + for ii, dd in rev_h_qub: + sub_state_vecs.append(op.basis(dd, 0)) - # return in reverse order - # osc_n * … * osc_0 * qubit_n * … * qubit_0 - return op.tensor(sub_state_vecs[::-1]) + return op.tensor(sub_state_vecs) diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index 9541c99010..e8b79f353d 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -41,15 +41,15 @@ class HamiltonianParser: """ Generate QuTip hamiltonian object from string """ - def __init__(self, hamiltonian, dim_osc, dim_qub): + def __init__(self, h_str, dim_osc, dim_qub): """ Create new quantum operator generator Parameters: - hamiltonian (list): list of Hamiltonian string - dim_osc (list): dimension of oscillator subspace - dim_qub (list): dimension of qubit subspace + h_str (list): list of Hamiltonian string + dim_osc (dict): dimension of oscillator subspace + dim_qub (dict): dimension of qubit subspace """ - self.hamiltonian = hamiltonian + self.h_str = h_str self.dim_osc = dim_osc self.dim_qub = dim_qub self.__td_hams = [] @@ -72,7 +72,7 @@ def parse(self): self._expand_sum() # convert to reverse Polish notation - for ham in self.hamiltonian: + for ham in self.h_str: if len(re.findall(r"\|\|", ham)) > 1: raise Exception("Multiple time-dependent terms in %s" % ham) p_td = re.search(r"(?P[\S]+)\|\|(?P[\S]+)", ham) @@ -102,7 +102,7 @@ def _expand_sum(self): sum_str = re.compile(r"_SUM\[(?P[a-z]),(?P[a-z\d{}+-]+),(?P[a-z\d{}+-]+),") brk_str = re.compile(r"]") - ham_list = copy.copy(self.hamiltonian) + ham_list = copy.copy(self.h_str) ham_out = [] while any(ham_list): @@ -144,7 +144,7 @@ def _expand_sum(self): trg_s, ham[p_brks[ii].end():]])) ham_list.extend(_temp) - self.hamiltonian = ham_out + self.h_str = ham_out return ham_out @@ -279,18 +279,21 @@ def _token2qobj(self, tokens): class NoiseParser: """ Generate QuTip noise object from dictionary - Qubit noise is given in the format of list of dictionary: - - "qubit": [ - {"Sm": 0.006} - ] - - and oscillator noise is given in the nested list of (n_th, coupling): - - "oscillator": [ - [0.001, 0.05] - ] - + Qubit noise is given in the format of nested dictionary: + "qubit": { + "0": { + "Sm": 0.006 + } + } + and oscillator noise is given in the format of nested dictionary: + "oscillator": { + "n_th": { + "0": 0.001 + }, + "coupling": { + "0": 0.05 + } + } these configurations are combined in the same dictionary """ def __init__(self, noise_dict, dim_osc, dim_qub): @@ -298,11 +301,11 @@ def __init__(self, noise_dict, dim_osc, dim_qub): Parameters: noise_dict (dict): dictionary of noise configuration - dim_osc (list): dimension of oscillator subspace - dim_qub (list): dimension of qubit subspace + dim_osc (dict): dimension of oscillator subspace + dim_qub (dict): dimension of qubit subspace """ - self.noise_osc = noise_dict.get('oscillator', []) - self.noise_qub = noise_dict.get('qubit', []) + self.noise_osc = noise_dict.get('oscillator', {'n_th': {}, 'coupling': {}}) + self.noise_qub = noise_dict.get('qubit', {}) self.dim_osc = dim_osc self.dim_qub = dim_qub self.__c_list = [] @@ -317,7 +320,7 @@ def parse(self): """ Parse and generate quantum class object """ # Qubit noise - for index, config in enumerate(self.noise_qub): + for index, config in self.noise_qub.items(): for opname, coef in config.items(): # TODO: support noise in multi-dimensional system # TODO: support noise with math operation @@ -327,16 +330,19 @@ def parse(self): raise Exception('Unsupported noise operator %s is given' % opname) self.__c_list.append(np.sqrt(coef) * opr) # Oscillator noise - ndic = [nconf[0] for nconf in self.noise_osc] - cdic = [nconf[1] for nconf in self.noise_osc] - for ii, (n_coef, c_coef) in enumerate(zip(ndic, cdic)): - if c_coef > 0: - opr = gen_oper('A', int(ii), self.dim_osc, self.dim_qub) - if n_coef: - self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr) - self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag()) - else: - self.__c_list.append(np.sqrt(c_coef) * opr) + ndic = self.noise_osc['n_th'] + cdic = self.noise_osc['coupling'] + for (n_ii, n_coef), (c_ii, c_coef) in zip(ndic.items(), cdic.items()): + if n_ii == c_ii: + if c_coef > 0: + opr = gen_oper('A', int(n_ii), self.dim_osc, self.dim_qub) + if n_coef: + self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr) + self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag()) + else: + self.__c_list.append(np.sqrt(c_coef) * opr) + else: + raise Exception('Invalid oscillator index in noise dictionary.') def math_priority(o1, o2): From 090502b19c26833c361de371a3be173f1c315610 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 25 Jun 2019 10:34:04 -0400 Subject: [PATCH 12/31] updates --- qiskit/providers/aer/aerjob.py | 5 +- qiskit/providers/aer/openpulse/__init__.py | 13 +++++ .../aer/openpulse/qutip_lite/pyxbuilder.py | 52 +++++++++++++++++++ .../aer/openpulse/solver/rhs_utils.py | 1 - 4 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py diff --git a/qiskit/providers/aer/aerjob.py b/qiskit/providers/aer/aerjob.py index e4285f1d80..7489a044e8 100644 --- a/qiskit/providers/aer/aerjob.py +++ b/qiskit/providers/aer/aerjob.py @@ -46,10 +46,7 @@ class AerJob(BaseJob): _executor (futures.Executor): executor to handle asynchronous jobs """ - if sys.platform in ['darwin', 'win32']: - _executor = futures.ThreadPoolExecutor() - else: - _executor = futures.ProcessPoolExecutor() + _executor = futures.ThreadPoolExecutor() def __init__(self, backend, job_id, fn, qobj, *args): super().__init__(backend, job_id) diff --git a/qiskit/providers/aer/openpulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py index 7909fc6dac..e4ff34314a 100644 --- a/qiskit/providers/aer/openpulse/__init__.py +++ b/qiskit/providers/aer/openpulse/__init__.py @@ -11,3 +11,16 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. + +import numpy as np + +# Remove -Wstrict-prototypes from cflags +import distutils.sysconfig +cfg_vars = distutils.sysconfig.get_config_vars() +if "CFLAGS" in cfg_vars: + cfg_vars["CFLAGS"] = cfg_vars["CFLAGS"].replace("-Wstrict-prototypes", "") + +# Setup pyximport +import qutip_lite.pyxbuilder as pbldr +pbldr.install(setup_args={'include_dirs': [np.get_include()]}) +del pbldr \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py b/qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py new file mode 100644 index 0000000000..57c72d2e69 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py @@ -0,0 +1,52 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import sys, os +import pyximport +from pyximport import install + +old_get_distutils_extension = pyximport.pyximport.get_distutils_extension + +def new_get_distutils_extension(modname, pyxfilename, language_level=None): + extension_mod, setup_args = old_get_distutils_extension(modname, pyxfilename, language_level) + extension_mod.language='c++' + # If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) + if sys.platform == 'win32' and int(str(sys.version_info[0])+str(sys.version_info[1])) >= 35 and os.environ.get('MSYSTEM') is None: + extension_mod.extra_compile_args = ['/w', '/O1'] + else: + extension_mod.extra_compile_args = ['-w', '-O1'] + if sys.platform == 'darwin': + extension_mod.extra_compile_args.append('-mmacosx-version-min=10.9') + extension_mod.extra_link_args = ['-mmacosx-version-min=10.9'] + return extension_mod,setup_args + +pyximport.pyximport.get_distutils_extension = new_get_distutils_extension diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index 19ff3a5f24..fd147f3916 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -37,7 +37,6 @@ def _op_func_load(op_system): Args: op_system (OPSystem): An OpenPulse system object. """ - global _cy_rhs_func code = compile('from ' + op_system.global_data['rhs_file_name'] + ' import cy_td_ode_rhs', '', 'exec') exec(code, globals()) From 070bce71df40d9329611082e9c32b1d79826884b Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 25 Jun 2019 10:38:41 -0400 Subject: [PATCH 13/31] fixed --- qiskit/providers/aer/openpulse/__init__.py | 7 ++++--- .../providers/aer/openpulse/qutip_lite/__init__.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 qiskit/providers/aer/openpulse/qutip_lite/__init__.py diff --git a/qiskit/providers/aer/openpulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py index e4ff34314a..1b64695c11 100644 --- a/qiskit/providers/aer/openpulse/__init__.py +++ b/qiskit/providers/aer/openpulse/__init__.py @@ -12,15 +12,16 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. + +import distutils.sysconfig import numpy as np +from .qutip_lite import pyxbuilder as pbldr # Remove -Wstrict-prototypes from cflags -import distutils.sysconfig cfg_vars = distutils.sysconfig.get_config_vars() if "CFLAGS" in cfg_vars: cfg_vars["CFLAGS"] = cfg_vars["CFLAGS"].replace("-Wstrict-prototypes", "") # Setup pyximport -import qutip_lite.pyxbuilder as pbldr pbldr.install(setup_args={'include_dirs': [np.get_include()]}) -del pbldr \ No newline at end of file +del pbldr diff --git a/qiskit/providers/aer/openpulse/qutip_lite/__init__.py b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py new file mode 100644 index 0000000000..7909fc6dac --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. From 99ff3cf222d453eddae0e532207cdfb1a3d666dc Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 1 Jul 2019 16:28:38 -0400 Subject: [PATCH 14/31] add qutip lite --- .../aer/openpulse/{cython => cy}/__init__.py | 0 .../{cython => cy}/channel_value.pxd | 0 .../{cython => cy}/channel_value.pyx | 0 .../aer/openpulse/{cython => cy}/measure.pyx | 0 .../aer/openpulse/{cython => cy}/memory.pyx | 0 .../aer/openpulse/{cython => cy}/utils.pyx | 0 .../aer/openpulse/qutip_lite/__init__.py | 39 +- .../aer/openpulse/qutip_lite/cy/__init__.py | 0 .../openpulse/qutip_lite/cy/complex_math.pxi | 24 + .../openpulse/qutip_lite/cy/graph_utils.pyx | 430 +++ .../aer/openpulse/qutip_lite/cy/math.pxd | 36 + .../aer/openpulse/qutip_lite/cy/math.pyx | 125 + .../openpulse/qutip_lite/cy/parameters.pxi | 14 + .../aer/openpulse/qutip_lite/cy/ptrace.pyx | 262 ++ .../qutip_lite/{ => cy}/pyxbuilder.py | 0 .../qutip_lite/cy/sparse_routines.pxi | 644 +++++ .../qutip_lite/cy/sparse_structs.pxd | 59 + .../openpulse/qutip_lite/cy/sparse_utils.pyx | 375 +++ .../aer/openpulse/qutip_lite/cy/spconvert.pxd | 39 + .../aer/openpulse/qutip_lite/cy/spconvert.pyx | 303 +++ .../openpulse/qutip_lite/cy/spmatfuncs.pxd | 115 + .../openpulse/qutip_lite/cy/spmatfuncs.pyx | 572 ++++ .../aer/openpulse/qutip_lite/cy/spmath.pxd | 59 + .../aer/openpulse/qutip_lite/cy/spmath.pyx | 764 ++++++ .../aer/openpulse/qutip_lite/cy/src/zspmv.cpp | 172 ++ .../aer/openpulse/qutip_lite/cy/src/zspmv.hpp | 53 + .../aer/openpulse/qutip_lite/cy/utilities.py | 53 + .../aer/openpulse/qutip_lite/expect.py | 174 ++ .../aer/openpulse/qutip_lite/fastsparse.py | 406 +++ .../aer/openpulse/qutip_lite/graph.py | 295 ++ .../aer/openpulse/qutip_lite/hardware_info.py | 135 + .../aer/openpulse/qutip_lite/operators.py | 976 +++++++ .../aer/openpulse/qutip_lite/permute.py | 174 ++ .../aer/openpulse/qutip_lite/propagator.py | 320 +++ .../aer/openpulse/qutip_lite/qobj.py | 2409 +++++++++++++++++ .../aer/openpulse/qutip_lite/settings.py | 86 + .../aer/openpulse/qutip_lite/sparse.py | 591 ++++ .../aer/openpulse/qutip_lite/states.py | 1213 +++++++++ .../aer/openpulse/qutip_lite/superoperator.py | 408 +++ .../aer/openpulse/qutip_lite/tensor.py | 387 +++ .../aer/openpulse/{cython => }/setup.py | 27 +- 41 files changed, 11722 insertions(+), 17 deletions(-) rename qiskit/providers/aer/openpulse/{cython => cy}/__init__.py (100%) rename qiskit/providers/aer/openpulse/{cython => cy}/channel_value.pxd (100%) rename qiskit/providers/aer/openpulse/{cython => cy}/channel_value.pyx (100%) rename qiskit/providers/aer/openpulse/{cython => cy}/measure.pyx (100%) rename qiskit/providers/aer/openpulse/{cython => cy}/memory.pyx (100%) rename qiskit/providers/aer/openpulse/{cython => cy}/utils.pyx (100%) mode change 100644 => 100755 qiskit/providers/aer/openpulse/qutip_lite/__init__.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/__init__.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/complex_math.pxi create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/math.pxd create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/math.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/parameters.pxi create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx rename qiskit/providers/aer/openpulse/qutip_lite/{ => cy}/pyxbuilder.py (100%) mode change 100644 => 100755 create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_structs.pxd create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pxd create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.hpp create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/expect.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/graph.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/operators.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/permute.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/propagator.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/qobj.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/settings.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/sparse.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/states.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/superoperator.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/tensor.py rename qiskit/providers/aer/openpulse/{cython => }/setup.py (69%) diff --git a/qiskit/providers/aer/openpulse/cython/__init__.py b/qiskit/providers/aer/openpulse/cy/__init__.py similarity index 100% rename from qiskit/providers/aer/openpulse/cython/__init__.py rename to qiskit/providers/aer/openpulse/cy/__init__.py diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pxd b/qiskit/providers/aer/openpulse/cy/channel_value.pxd similarity index 100% rename from qiskit/providers/aer/openpulse/cython/channel_value.pxd rename to qiskit/providers/aer/openpulse/cy/channel_value.pxd diff --git a/qiskit/providers/aer/openpulse/cython/channel_value.pyx b/qiskit/providers/aer/openpulse/cy/channel_value.pyx similarity index 100% rename from qiskit/providers/aer/openpulse/cython/channel_value.pyx rename to qiskit/providers/aer/openpulse/cy/channel_value.pyx diff --git a/qiskit/providers/aer/openpulse/cython/measure.pyx b/qiskit/providers/aer/openpulse/cy/measure.pyx similarity index 100% rename from qiskit/providers/aer/openpulse/cython/measure.pyx rename to qiskit/providers/aer/openpulse/cy/measure.pyx diff --git a/qiskit/providers/aer/openpulse/cython/memory.pyx b/qiskit/providers/aer/openpulse/cy/memory.pyx similarity index 100% rename from qiskit/providers/aer/openpulse/cython/memory.pyx rename to qiskit/providers/aer/openpulse/cy/memory.pyx diff --git a/qiskit/providers/aer/openpulse/cython/utils.pyx b/qiskit/providers/aer/openpulse/cy/utils.pyx similarity index 100% rename from qiskit/providers/aer/openpulse/cython/utils.pyx rename to qiskit/providers/aer/openpulse/cy/utils.pyx diff --git a/qiskit/providers/aer/openpulse/qutip_lite/__init__.py b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py old mode 100644 new mode 100755 index 7909fc6dac..b751727579 --- a/qiskit/providers/aer/openpulse/qutip_lite/__init__.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py @@ -1,13 +1,32 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. +# This file is part of QuTiP: Quantum Toolbox in Python. # -# (C) Copyright IBM 2018, 2019. +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. # -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: # -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/__init__.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/complex_math.pxi b/qiskit/providers/aer/openpulse/qutip_lite/cy/complex_math.pxi new file mode 100755 index 0000000000..43f0fd1793 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/complex_math.pxi @@ -0,0 +1,24 @@ +cdef extern from "" namespace "std" nogil: + double abs(double complex x) + double complex acos(double complex x) + double complex acosh(double complex x) + double arg(double complex x) + double complex asin(double complex x) + double complex asinh(double complex x) + double complex atan(double complex x) + double complex atanh(double complex x) + double complex conj(double complex x) + double complex cos(double complex x) + double complex cosh(double complex x) + double complex exp(double complex x) + double imag(double complex x) + double complex log(double complex x) + double complex log10(double complex x) + double norm(double complex x) + double complex proj(double complex x) + double real(double complex x) + double complex sin(double complex x) + double complex sinh(double complex x) + double complex sqrt(double complex x) + double complex tan(double complex x) + double complex tanh(double complex x) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx new file mode 100755 index 0000000000..f87988a301 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx @@ -0,0 +1,430 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +cimport numpy as cnp +cimport cython +from libcpp.algorithm cimport sort +from libcpp.vector cimport vector +cnp.import_array() + +include "parameters.pxi" + +cdef extern from "numpy/arrayobject.h" nogil: + void PyArray_ENABLEFLAGS(cnp.ndarray arr, int flags) + void PyDataMem_FREE(void * ptr) + void PyDataMem_RENEW(void * ptr, size_t size) + void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) + void PyDataMem_NEW(size_t size) + +#Struct used for arg sorting +cdef struct _int_pair: + int data + int idx + +ctypedef _int_pair int_pair +ctypedef int (*cfptr)(int_pair, int_pair) + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int int_sort(int_pair x, int_pair y): + return x.data < y.data + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int * int_argsort(int * x, int nrows): + cdef vector[int_pair] pairs + cdef cfptr cfptr_ = &int_sort + cdef size_t kk + pairs.resize(nrows) + for kk in range(nrows): + pairs[kk].data = x[kk] + pairs[kk].idx = kk + + sort(pairs.begin(),pairs.end(),cfptr_) + cdef int * out = PyDataMem_NEW(nrows *sizeof(int)) + for kk in range(nrows): + out[kk] = pairs[kk].idx + return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef int[::1] _node_degrees(int[::1] ind, int[::1] ptr, + unsigned int num_rows): + + cdef size_t ii, jj + cdef int[::1] degree = np.zeros(num_rows, dtype=np.int32) + + for ii in range(num_rows): + degree[ii] = ptr[ii + 1] - ptr[ii] + for jj in range(ptr[ii], ptr[ii + 1]): + if ind[jj] == ii: + # add one if the diagonal is in row ii + degree[ii] += 1 + break + + return degree + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _breadth_first_search( + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ind, + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptr, + int num_rows, int seed): + """ + Does a breath first search (BSF) of a graph in sparse CSR format matrix + form starting at a given seed node. + """ + + cdef unsigned int i, j, ii, jj, N = 1 + cdef unsigned int level_start = 0 + cdef unsigned int level_end = N + cdef unsigned int current_level = 1 + cdef cnp.ndarray[ITYPE_t] order = -1 * np.ones(num_rows, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] level = -1 * np.ones(num_rows, dtype=ITYPE) + + level[seed] = 0 + order[0] = seed + + while level_start < level_end: + # for nodes of the last level + for ii in range(level_start, level_end): + i = order[ii] + # add unvisited neighbors to queue + for jj in range(ptr[i], ptr[i + 1]): + j = ind[jj] + if level[j] == -1: + order[N] = j + level[j] = current_level + N += 1 + + level_start = level_end + level_end = N + current_level += 1 + + return order, level + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _reverse_cuthill_mckee(int[::1] ind, int[::1] ptr, int num_rows): + """ + Reverse Cuthill-McKee ordering of a sparse csr or csc matrix. + """ + cdef unsigned int N = 0, N_old, seed, level_start, level_end + cdef unsigned int zz, i, j, ii, jj, kk, ll, level_len, temp, temp2 + cdef cnp.ndarray[int, ndim=1] order = np.zeros(num_rows, dtype=np.int32) + cdef int[::1] degree = _node_degrees(ind, ptr, num_rows) + cdef int * inds = int_argsort(°ree[0], num_rows) + cdef int * rev_inds = int_argsort(inds, num_rows) + cdef int * temp_degrees = NULL + + # loop over zz takes into account possible disconnected graph. + for zz in range(num_rows): + if inds[zz] != -1: # Do BFS with seed=inds[zz] + seed = inds[zz] + order[N] = seed + N += 1 + inds[rev_inds[seed]] = -1 + level_start = N - 1 + level_end = N + + while level_start < level_end: + for ii in range(level_start, level_end): + i = order[ii] + N_old = N + + # add unvisited neighbors + for jj in range(ptr[i], ptr[i + 1]): + # j is node number connected to i + j = ind[jj] + if inds[rev_inds[j]] != -1: + inds[rev_inds[j]] = -1 + order[N] = j + N += 1 + + # Add values to temp_degrees array for insertion sort + temp_degrees = PyDataMem_RENEW(temp_degrees, (N-N_old)*sizeof(int)) + level_len = 0 + for kk in range(N_old, N): + temp_degrees[level_len] = degree[order[kk]] + level_len += 1 + + # Do insertion sort for nodes from lowest to highest degree + for kk in range(1, level_len): + temp = temp_degrees[kk] + temp2 = order[N_old+kk] + ll = kk + while (ll > 0) and (temp < temp_degrees[ll-1]): + temp_degrees[ll] = temp_degrees[ll-1] + order[N_old+ll] = order[N_old+ll-1] + ll -= 1 + temp_degrees[ll] = temp + order[N_old+ll] = temp2 + + # set next level start and end ranges + level_start = level_end + level_end = N + + if N == num_rows: + break + PyDataMem_FREE(inds) + PyDataMem_FREE(rev_inds) + PyDataMem_FREE(temp_degrees) + # return reversed order for RCM ordering + return order[::-1] + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _pseudo_peripheral_node( + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ind, + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptr, + int num_rows): + """ + Find a pseudo peripheral node of a graph represented by a sparse + csr_matrix. + """ + + cdef unsigned int ii, jj, delta, flag, node, start + cdef int maxlevel, minlevel, minlastnodesdegree + cdef cnp.ndarray[cnp.intp_t] lastnodes + cdef cnp.ndarray[cnp.intp_t] lastnodesdegree + cdef cnp.ndarray[cnp.intp_t] degree = np.zeros(num_rows, dtype=ITYPE) + + degree = _node_degrees(ind, ptr, num_rows).astype(ITYPE) + start = 0 + delta = 0 + flag = 1 + + while flag: + # do a level-set traversal from x + order, level = _breadth_first_search(ind, ptr, num_rows, start) + + # select node in last level with min degree + maxlevel = max(level) + lastnodes = np.where(level == maxlevel)[0] + lastnodesdegree = degree[lastnodes] + minlastnodesdegree = min(lastnodesdegree) + node = np.where(lastnodesdegree == minlastnodesdegree)[0][0] + node = lastnodes[node] + + # if d(x,y) > delta, set, and do another BFS from this minimal node + if level[node] > delta: + start = node + delta = level[node] + else: + flag = 0 + + return start, order, level + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _maximum_bipartite_matching( + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] inds, + cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptrs, + int n): + + cdef cnp.ndarray[ITYPE_t] visited = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] queue = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] previous = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] match = -1 * np.ones(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] row_match = -1 * np.ones(n, dtype=ITYPE) + cdef int queue_ptr, queue_col, ptr, i, j, queue_size + cdef int row, col, temp, eptr, next_num = 1 + + for i in range(n): + if match[i] == -1 and (ptrs[i] != ptrs[i + 1]): + queue[0] = i + queue_ptr = 0 + queue_size = 1 + while (queue_size > queue_ptr): + queue_col = queue[queue_ptr] + queue_ptr += 1 + eptr = ptrs[queue_col + 1] + for ptr in range(ptrs[queue_col], eptr): + row = inds[ptr] + temp = visited[row] + if (temp != next_num and temp != -1): + previous[row] = queue_col + visited[row] = next_num + col = row_match[row] + if (col == -1): + while (row != -1): + col = previous[row] + temp = match[col] + match[col] = row + row_match[row] = col + row = temp + next_num += 1 + queue_size = 0 + break + else: + queue[queue_size] = col + queue_size += 1 + + if match[i] == -1: + for j in range(1, queue_size): + visited[match[queue[j]]] = -1 + + return match + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _max_row_weights( + double[::1] data, + int[::1] inds, + int[::1] ptrs, + int ncols): + """ + Finds the largest abs value in each matrix column + and the max. total number of elements in the cols (given by weights[-1]). + + Here we assume that the user already took the ABS value of the data. + This keeps us from having to call abs over and over. + + """ + cdef cnp.ndarray[DTYPE_t] weights = np.zeros(ncols + 1, dtype=DTYPE) + cdef int ln, mx, ii, jj + cdef DTYPE_t weight, current + + mx = 0 + for jj in range(ncols): + ln = (ptrs[jj + 1] - ptrs[jj]) + if ln > mx: + mx = ln + + weight = data[ptrs[jj]] + for ii in range(ptrs[jj] + 1, ptrs[jj + 1]): + current = data[ii] + if current > weight: + weight = current + + weights[jj] = weight + + weights[ncols] = mx + return weights + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _weighted_bipartite_matching( + double[::1] data, + int[::1] inds, + int[::1] ptrs, + int n): + """ + Here we assume that the user already took the ABS value of the data. + This keeps us from having to call abs over and over. + """ + + cdef cnp.ndarray[ITYPE_t] visited = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] queue = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] previous = np.zeros(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] match = -1 * np.ones(n, dtype=ITYPE) + cdef cnp.ndarray[ITYPE_t] row_match = -1 * np.ones(n, dtype=ITYPE) + cdef cnp.ndarray[DTYPE_t] weights = _max_row_weights(data, inds, ptrs, n) + cdef cnp.ndarray[ITYPE_t] order = np.argsort(-weights[0:n]).astype(ITYPE) + cdef cnp.ndarray[ITYPE_t] row_order = np.zeros(int(weights[n]), dtype=ITYPE) + cdef cnp.ndarray[DTYPE_t] temp_weights = np.zeros(int(weights[n]), dtype=DTYPE) + cdef int queue_ptr, queue_col, queue_size, next_num + cdef int i, j, zz, ll, kk, row, col, temp, eptr, temp2 + + next_num = 1 + for i in range(n): + zz = order[i] # cols with largest abs values first + if (match[zz] == -1 and (ptrs[zz] != ptrs[zz + 1])): + queue[0] = zz + queue_ptr = 0 + queue_size = 1 + + while (queue_size > queue_ptr): + queue_col = queue[queue_ptr] + queue_ptr += 1 + eptr = ptrs[queue_col + 1] + + # get row inds in current column + temp = ptrs[queue_col] + for kk in range(eptr - ptrs[queue_col]): + row_order[kk] = inds[temp] + temp_weights[kk] = data[temp] + temp += 1 + + # linear sort by row weight + for kk in range(1, (eptr - ptrs[queue_col])): + val = temp_weights[kk] + row_val = row_order[kk] + ll = kk - 1 + while (ll >= 0) and (temp_weights[ll] > val): + temp_weights[ll + 1] = temp_weights[ll] + row_order[ll + 1] = row_order[ll] + ll -= 1 + + temp_weights[ll + 1] = val + row_order[ll + 1] = row_val + + # go through rows by decending weight + temp2 = (eptr - ptrs[queue_col]) - 1 + for kk in range(eptr - ptrs[queue_col]): + row = row_order[temp2 - kk] + temp = visited[row] + if temp != next_num and temp != -1: + previous[row] = queue_col + visited[row] = next_num + col = row_match[row] + if col == -1: + while row != -1: + col = previous[row] + temp = match[col] + match[col] = row + row_match[row] = col + row = temp + + next_num += 1 + queue_size = 0 + break + else: + queue[queue_size] = col + queue_size += 1 + + if match[zz] == -1: + for j in range(1, queue_size): + visited[match[queue[j]]] = -1 + + return match diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pxd new file mode 100755 index 0000000000..d75351dd13 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pxd @@ -0,0 +1,36 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +cdef double complex erf(double complex Z) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pyx new file mode 100755 index 0000000000..661cc2a219 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/math.pyx @@ -0,0 +1,125 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +cimport cython +from libc.math cimport (fabs, sinh, cosh, exp, pi, sqrt, cos, sin, copysign) + +cdef extern from "" namespace "std" nogil: + double real(double complex x) + double imag(double complex x) + +# +# Shanjie Zhang and Jianming Jin +# +# Copyrighted but permission granted to use code in programs. +# Buy their book "Computation of Special Functions", 1996, John Wiley & Sons, Inc. + +@cython.cdivision(True) +@cython.boundscheck(False) +cdef double complex erf(double complex Z): + """ + Parameters + ---------- + Z : double complex + Input parameter. + X : double + Real part of Z. + Y : double + Imag part of Z. + + Returns + ------- + erf(z) : double complex + """ + + cdef double EPS = 1e-12 + cdef double X = real(Z) + cdef double Y = imag(Z) + cdef double X2 = X * X + + cdef double ER, R, W, C0, ER0, ERR, ERI, CS, SS, ER1, EI1, ER2, W1 + + cdef size_t K, N + + if X < 3.5: + ER = 1.0 + R = 1.0 + W = 0.0 + for K in range(1, 100): + R = R*X2/(K+0.5) + ER = ER+R + if (fabs(ER-W) < EPS*fabs(ER)): + break + W = ER + C0 = 2.0/sqrt(pi)*X*exp(-X2) + ER0 = C0*ER + else: + ER = 1.0 + R=1.0 + for K in range(1, 12): + R = -R*(K-0.5)/X2 + ER = ER+R + C0 = exp(-X2)/(X*sqrt(pi)) + ER0 = 1.0-C0*ER + + if Y == 0.0: + ERR = ER0 + ERI = 0.0 + else: + CS = cos(2.0*X*Y) + SS = sin(2.0*X*Y) + ER1 = exp(-X2)*(1.0-CS)/(2.0*pi*X) + EI1 = exp(-X2)*SS/(2.0*pi*X) + ER2 = 0.0 + W1 = 0.0 + + for N in range(1,100): + ER2 = ER2+exp(-.25*N*N)/(N*N+4.0*X2)*(2.0*X-2.0*X*cosh(N*Y)*CS+N*sinh(N*Y)*SS) + if (fabs((ER2-W1)/ER2) < EPS): + break + W1 = ER2 + + C0 = 2.0*exp(-X2)/pi + ERR = ER0+ER1+C0*ER2 + EI2 = 0.0 + W2 = 0.0 + + for N in range(1,100): + EI2 = EI2+exp(-.25*N*N)/(N*N+4.0*X2)*(2.0*X*cosh(N*Y)*SS+N*sinh(N*Y)*CS) + if (fabs((EI2-W2)/EI2) < EPS): + break + W2 = EI2 + ERI = EI1+C0*EI2 + + return ERR + 1j*ERI \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/parameters.pxi b/qiskit/providers/aer/openpulse/qutip_lite/cy/parameters.pxi new file mode 100755 index 0000000000..66705ba064 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/parameters.pxi @@ -0,0 +1,14 @@ +import numpy as np +cimport numpy as cnp + +DTYPE = np.float64 +ctypedef cnp.float64_t DTYPE_t + +ITYPE = np.int32 +ctypedef cnp.int32_t ITYPE_t + +CTYPE = np.complex128 +ctypedef cnp.complex128_t CTYPE_t + +CTYPE = np.int64 +ctypedef cnp.int64_t LTYPE_t diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx new file mode 100755 index 0000000000..866c55d01e --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx @@ -0,0 +1,262 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +from qutip.cy.spconvert import zcsr_reshape +from qutip.cy.spmath import zcsr_mult +from qutip.fastsparse import fast_csr_matrix, csr2fast +cimport numpy as cnp +cimport cython +from libc.math cimport floor, trunc +import scipy.sparse as sp + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +def _ptrace_legacy(object rho, _sel): + """ + Private function calculating the partial trace. + """ + if np.prod(rho.dims[1]) == 1: + rho = rho * rho.dag() + + cdef size_t mm, ii + cdef int _tmp + cdef cnp.ndarray[int, ndim=1, mode='c'] drho = np.asarray(rho.dims[0], dtype=np.int32).ravel() + + if isinstance(_sel, int): + _sel = np.array([_sel], dtype=np.int32) + else: + _sel = np.asarray(_sel, dtype = np.int32) + + cdef int[::1] sel = _sel + + for mm in range(sel.shape[0]): + if (sel[mm] < 0) or (sel[mm] >= drho.shape[0]): + raise TypeError("Invalid selection index in ptrace.") + + cdef int[::1] rest = np.delete(np.arange(drho.shape[0],dtype=np.int32),sel) + cdef int N = np.prod(drho) + cdef int M = np.prod(drho.take(sel)) + cdef int R = np.prod(drho.take(rest)) + + cdef int[:,::1] ilistsel = _select(sel, drho, M) + cdef int[::1] indsel = _list2ind(ilistsel, drho) + cdef int[:,::1] ilistrest = _select(rest, drho, R) + cdef int[::1] indrest = _list2ind(ilistrest, drho) + + for mm in range(indrest.shape[0]): + _tmp = indrest[mm] * N + indrest[mm]-1 + indrest[mm] = _tmp + + cdef cnp.ndarray[int, ndim=1, mode='c'] ind = np.zeros(M**2*indrest.shape[0],dtype=np.int32) + for mm in range(M**2): + for ii in range(indrest.shape[0]): + ind[mm*indrest.shape[0]+ii] = indrest[ii] + \ + N*indsel[floor(mm / M)] + \ + indsel[(mm % M)]+1 + + data = np.ones_like(ind,dtype=complex) + ptr = np.arange(0,(M**2+1)*indrest.shape[0],indrest.shape[0], dtype=np.int32) + perm = fast_csr_matrix((data,ind,ptr),shape=(M * M, N * N)) + # No need to sort here, will be sorted in reshape + rhdata = zcsr_mult(perm, zcsr_reshape(rho.data, np.prod(rho.shape), 1), sorted=0) + rho1_data = zcsr_reshape(rhdata, M, M) + dims_kept0 = np.asarray(rho.dims[0], dtype=np.int32).take(sel) + rho1_dims = [dims_kept0.tolist(), dims_kept0.tolist()] + rho1_shape = [np.prod(dims_kept0), np.prod(dims_kept0)] + return rho1_data, rho1_dims, rho1_shape + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cpdef cnp.ndarray[int, ndim=1, mode='c'] _list2ind(int[:,::1] ilist, int[::1] dims): + """! + Private function returning indicies + """ + cdef size_t kk, ll + cdef int[::1] fact = np.ones(dims.shape[0],dtype=np.int32) + for kk in range(dims.shape[0]): + for ll in range(kk+1,dims.shape[0]): + fact[kk] *= dims[ll] + # If we make ilist a csr_matrix, then this is just spmv then sort + return np.sort(np.dot(ilist, fact), 0) + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cpdef cnp.ndarray[int, ndim=2, mode='c'] _select(int[::1] sel, int[::1] dims, int M): + """ + Private function finding selected components + """ + cdef size_t ii, jj, kk + cdef int _sel, _prd + cdef cnp.ndarray[int, ndim=2, mode='c'] ilist = np.zeros((M, dims.shape[0]), dtype=np.int32) + for jj in range(sel.shape[0]): + _sel = sel[jj] + _prd = 1 + for kk in range(jj+1,sel.shape[0]): + _prd *= dims[sel[kk]] + for ii in range(M): + ilist[ii, _sel] = (trunc(ii / _prd) % dims[_sel]) + return ilist + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int _in(int val, int[::1] vec): + # val in vec in pure cython + cdef int ii + for ii in range(vec.shape[0]): + if val == vec[ii]: + return 1 + return 0 + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cdef void _i2_k_t(int N, + int[:, ::1] tensor_table, + int[::1] out): + # indices determining function for ptrace + cdef int ii, t1, t2 + out[0] = 0 + out[1] = 0 + for ii in range(tensor_table.shape[1]): + t1 = tensor_table[0, ii] + t2 = N / t1 + N = N % t1 + out[0] += tensor_table[1, ii] * t2 + out[1] += tensor_table[2, ii] * t2 + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +def _ptrace(object rho, sel): # work for N<= 26 on 16G Ram + cdef int[::1] _sel + cdef object _oper + cdef size_t ii + cdef size_t factor_keep = 1, factor_trace = 1, factor_tensor = 1 + cdef cnp.ndarray[int, ndim=1, mode='c'] drho = np.asarray(rho.dims[0], dtype=np.int32).ravel() + cdef int num_dims = drho.shape[0] + cdef int[:, ::1] tensor_table = np.zeros((3, num_dims), dtype=np.int32) + + if isinstance(sel, int): + _sel = np.array([sel], dtype=np.int32) + else: + _sel = np.asarray(sel, dtype=np.int32) + + for ii in range(_sel.shape[0]): + if _sel[ii] < 0 or _sel[ii] >= num_dims: + raise TypeError("Invalid selection index in ptrace.") + + if np.prod(rho.shape[1]) == 1: + _oper = (rho * rho.dag()).data + else: + _oper = rho.data + + for ii in range(num_dims-1,-1,-1): + tensor_table[0, ii] = factor_tensor + factor_tensor *= drho[ii] + if _in(ii, _sel): + tensor_table[1, ii] = factor_keep + factor_keep *= drho[ii] + else: + tensor_table[2, ii] = factor_trace + factor_trace *= drho[ii] + + dims_kept0 = drho.take(_sel).tolist() + rho1_dims = [dims_kept0, dims_kept0] + rho1_shape = [np.prod(dims_kept0), np.prod(dims_kept0)] + + # Try to evaluate how sparse the result will be. + if factor_keep*factor_keep > _oper.nnz: + return csr2fast(_ptrace_core_sp(_oper, tensor_table, factor_keep)), rho1_dims, rho1_shape + else: + return csr2fast(_ptrace_core_dense(_oper, tensor_table, factor_keep)), rho1_dims, rho1_shape + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cdef object _ptrace_core_sp(rho, int[:, ::1] tensor_table, int num_sel_dims): + cdef int p = 0, nnz = rho.nnz, ii, jj, nrow = rho.shape[0] + cdef int[::1] pos_c = np.empty(2, dtype=np.int32) + cdef int[::1] pos_r = np.empty(2, dtype=np.int32) + cdef cnp.ndarray[complex, ndim=1, mode='c'] new_data = np.zeros(nnz, dtype=complex) + cdef cnp.ndarray[int, ndim=1, mode='c'] new_col = np.zeros(nnz, dtype=np.int32) + cdef cnp.ndarray[int, ndim=1, mode='c'] new_row = np.zeros(nnz, dtype=np.int32) + cdef cnp.ndarray[complex, ndim=1, mode='c'] data = rho.data + cdef cnp.ndarray[int, ndim=1, mode='c'] ptr = rho.indptr + cdef cnp.ndarray[int, ndim=1, mode='c'] ind = rho.indices + + for ii in range(nrow): + for jj in range(ptr[ii], ptr[ii+1]): + _i2_k_t(ind[jj], tensor_table, pos_c) + _i2_k_t(ii, tensor_table, pos_r) + if pos_c[1] == pos_r[1]: + new_data[p] = data[jj] + new_row[p] = (pos_r[0]) + new_col[p] = (pos_c[0]) + p += 1 + + return sp.coo_matrix((new_data, [new_row, new_col]), + shape=(num_sel_dims,num_sel_dims)).tocsr() + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +cdef object _ptrace_core_dense(rho, int[:, ::1] tensor_table, int num_sel_dims): + cdef int nnz = rho.nnz, ii, jj, nrow = rho.shape[0] + cdef int[::1] pos_c = np.empty(2, dtype=np.int32) + cdef int[::1] pos_r = np.empty(2, dtype=np.int32) + cdef cnp.ndarray[complex, ndim=1, mode='c'] data = rho.data + cdef cnp.ndarray[int, ndim=1, mode='c'] ptr = rho.indptr + cdef cnp.ndarray[int, ndim=1, mode='c'] ind = rho.indices + cdef complex[:, ::1] data_mat = np.zeros((num_sel_dims, num_sel_dims), + dtype=complex) + + for ii in range(nrow): + for jj in range(ptr[ii], ptr[ii+1]): + _i2_k_t(ind[jj], tensor_table, pos_c) + _i2_k_t(ii, tensor_table, pos_r) + if pos_c[1] == pos_r[1]: + data_mat[pos_r[0], pos_c[0]] += data[jj] + + return sp.coo_matrix(data_mat).tocsr() diff --git a/qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py old mode 100644 new mode 100755 similarity index 100% rename from qiskit/providers/aer/openpulse/qutip_lite/pyxbuilder.py rename to qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi new file mode 100755 index 0000000000..0564c3b8cb --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi @@ -0,0 +1,644 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +from scipy.sparse import coo_matrix +from qutip.fastsparse import fast_csr_matrix +cimport numpy as np +cimport cython +from libcpp.algorithm cimport sort +from libcpp.vector cimport vector +from qutip.cy.sparse_structs cimport CSR_Matrix, COO_Matrix +np.import_array() + +cdef extern from "numpy/arrayobject.h" nogil: + void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) + void PyDataMem_FREE(void * ptr) + void PyDataMem_RENEW(void * ptr, size_t size) + void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) + void PyDataMem_NEW(size_t size) + + +#Struct used for CSR indices sorting +cdef struct _data_ind_pair: + double complex data + int ind + +ctypedef _data_ind_pair data_ind_pair +ctypedef int (*cfptr)(data_ind_pair, data_ind_pair) + + +cdef void raise_error_CSR(int E, CSR_Matrix * C = NULL): + if not C.numpy_lock and C != NULL: + free_CSR(C) + if E == -1: + raise MemoryError('Could not allocate memory.') + elif E == -2: + raise Exception('Error manipulating CSR_Matrix structure.') + elif E == -3: + raise Exception('CSR_Matrix is not initialized.') + elif E == -4: + raise Exception('NumPy already has lock on data.') + elif E == -5: + raise Exception('Cannot expand data structures past max_length.') + elif E == -6: + raise Exception('CSR_Matrix cannot be expanded.') + elif E == -7: + raise Exception('Data length cannot be larger than max_length') + else: + raise Exception('Error in Cython code.') + + +cdef void raise_error_COO(int E, COO_Matrix * C = NULL): + if not C.numpy_lock and C != NULL: + free_COO(C) + if E == -1: + raise MemoryError('Could not allocate memory.') + elif E == -2: + raise Exception('Error manipulating COO_Matrix structure.') + elif E == -3: + raise Exception('COO_Matrix is not initialized.') + elif E == -4: + raise Exception('NumPy already has lock on data.') + elif E == -5: + raise Exception('Cannot expand data structures past max_length.') + elif E == -6: + raise Exception('COO_Matrix cannot be expanded.') + elif E == -7: + raise Exception('Data length cannot be larger than max_length') + else: + raise Exception('Error in Cython code.') + + +cdef inline int int_min(int a, int b) nogil: + return b if b < a else a + +cdef inline int int_max(int a, int b) nogil: + return a if a > b else b + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void init_CSR(CSR_Matrix * mat, int nnz, int nrows, int ncols = 0, + int max_length = 0, int init_zeros = 1): + """ + Initialize CSR_Matrix struct. Matrix is assumed to be square with + shape nrows x nrows. Manually set mat.ncols otherwise + + Parameters + ---------- + mat : CSR_Matrix * + Pointer to struct. + nnz : int + Length of data and indices arrays. Also number of nonzero elements + nrows : int + Number of rows in matrix. Also gives length + of indptr array (nrows+1). + ncols : int (default = 0) + Number of cols in matrix. Default is ncols = nrows. + max_length : int (default = 0) + Maximum length of data and indices arrays. Used for resizing. + Default value of zero indicates no resizing. + """ + if max_length == 0: + max_length = nnz + if nnz > max_length: + raise_error_CSR(-7, mat) + if init_zeros: + mat.data = PyDataMem_NEW_ZEROED(nnz, sizeof(double complex)) + else: + mat.data = PyDataMem_NEW(nnz * sizeof(double complex)) + if mat.data == NULL: + raise_error_CSR(-1, mat) + if init_zeros: + mat.indices = PyDataMem_NEW_ZEROED(nnz, sizeof(int)) + mat.indptr = PyDataMem_NEW_ZEROED((nrows+1), sizeof(int)) + else: + mat.indices = PyDataMem_NEW(nnz * sizeof(int)) + mat.indptr = PyDataMem_NEW((nrows+1) * sizeof(int)) + mat.nnz = nnz + mat.nrows = nrows + if ncols == 0: + mat.ncols = nrows + else: + mat.ncols = ncols + mat.is_set = 1 + mat.max_length = max_length + mat.numpy_lock = 0 + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void copy_CSR(CSR_Matrix * out, CSR_Matrix * mat): + """ + Copy a CSR_Matrix. + """ + cdef size_t kk + if not mat.is_set: + raise_error_CSR(-3) + elif out.is_set: + raise_error_CSR(-2) + init_CSR(out, mat.nnz, mat.nrows, mat.nrows, mat.max_length) + # We cannot use memcpy here since there are issues with + # doing so on Win with the GCC compiler + for kk in range(mat.nnz): + out.data[kk] = mat.data[kk] + out.indices[kk] = mat.indices[kk] + for kk in range(mat.nrows+1): + out.indptr[kk] = mat.indptr[kk] + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void init_COO(COO_Matrix * mat, int nnz, int nrows, int ncols = 0, + int max_length = 0, int init_zeros = 1): + """ + Initialize COO_Matrix struct. Matrix is assumed to be square with + shape nrows x nrows. Manually set mat.ncols otherwise + + Parameters + ---------- + mat : COO_Matrix * + Pointer to struct. + nnz : int + Number of nonzero elements. + nrows : int + Number of rows in matrix. + nrows : int (default = 0) + Number of cols in matrix. Default is ncols = nrows. + max_length : int (default = 0) + Maximum length of arrays. Used for resizing. + Default value of zero indicates no resizing. + """ + if max_length == 0: + max_length = nnz + if nnz > max_length: + raise_error_COO(-7, mat) + if init_zeros: + mat.data = PyDataMem_NEW_ZEROED(nnz, sizeof(double complex)) + else: + mat.data = PyDataMem_NEW(nnz * sizeof(double complex)) + if mat.data == NULL: + raise_error_COO(-1, mat) + if init_zeros: + mat.rows = PyDataMem_NEW_ZEROED(nnz, sizeof(int)) + mat.cols = PyDataMem_NEW_ZEROED(nnz, sizeof(int)) + else: + mat.rows = PyDataMem_NEW(nnz * sizeof(int)) + mat.cols = PyDataMem_NEW(nnz * sizeof(int)) + mat.nnz = nnz + mat.nrows = nrows + if ncols == 0: + mat.ncols = nrows + else: + mat.ncols = ncols + mat.is_set = 1 + mat.max_length = max_length + mat.numpy_lock = 0 + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void free_CSR(CSR_Matrix * mat): + """ + Manually free CSR_Matrix data structures if + data is not locked by NumPy. + """ + if not mat.numpy_lock and mat.is_set: + if mat.data != NULL: + PyDataMem_FREE(mat.data) + if mat.indices != NULL: + PyDataMem_FREE(mat.indices) + if mat.indptr != NULL: + PyDataMem_FREE(mat.indptr) + mat.is_set = 0 + else: + raise_error_CSR(-2) + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void free_COO(COO_Matrix * mat): + """ + Manually free COO_Matrix data structures if + data is not locked by NumPy. + """ + if not mat.numpy_lock and mat.is_set: + if mat.data != NULL: + PyDataMem_FREE(mat.data) + if mat.rows != NULL: + PyDataMem_FREE(mat.rows) + if mat.cols != NULL: + PyDataMem_FREE(mat.cols) + mat.is_set = 0 + else: + raise_error_COO(-2) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void shorten_CSR(CSR_Matrix * mat, int N): + """ + Shortends the length of CSR data and indices arrays. + """ + if (not mat.numpy_lock) and mat.is_set: + mat.data = PyDataMem_RENEW(mat.data, N * sizeof(double complex)) + mat.indices = PyDataMem_RENEW(mat.indices, N * sizeof(int)) + mat.nnz = N + else: + if mat.numpy_lock: + raise_error_CSR(-4, mat) + elif not mat.is_set: + raise_error_CSR(-3, mat) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void expand_CSR(CSR_Matrix * mat, int init_zeros=0): + """ + Expands the length of CSR data and indices arrays to accomodate + more nnz. THIS IS CURRENTLY NOT USED + """ + cdef size_t ii + cdef int new_size + if mat.nnz == mat.max_length: + raise_error_CSR(-5, mat) #Cannot expand data past max_length. + elif (not mat.numpy_lock) and mat.is_set: + new_size = int_min(2*mat.nnz, mat.max_length) + new_data = PyDataMem_RENEW(mat.data, new_size * sizeof(double complex)) + if new_data == NULL: + raise_error_CSR(-1, mat) + else: + mat.data = new_data + if init_zeros == 1: + for ii in range(mat.nnz, new_size): + mat.data[ii] = 0 + + new_ind = PyDataMem_RENEW(mat.indices, new_size * sizeof(int)) + mat.indices = new_ind + if init_zeros == 1: + for ii in range(mat.nnz, new_size): + mat.indices[ii] = 0 + mat.nnz = new_size + else: + if mat.numpy_lock: + raise_error_CSR(-4, mat) + elif not mat.is_set: + raise_error_CSR(-3, mat) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef object CSR_to_scipy(CSR_Matrix * mat): + """ + Converts a CSR_Matrix struct to a SciPy csr_matrix class object. + The NumPy arrays are generated from the pointers, and the lifetime + of the pointer memory is tied to that of the NumPy array + (i.e. automatic garbage cleanup.) + + Parameters + ---------- + mat : CSR_Matrix * + Pointer to CSR_Matrix. + """ + cdef np.npy_intp dat_len, ptr_len + cdef np.ndarray[complex, ndim=1] _data + cdef np.ndarray[int, ndim=1] _ind, _ptr + if (not mat.numpy_lock) and mat.is_set: + dat_len = mat.nnz + ptr_len = mat.nrows+1 + _data = np.PyArray_SimpleNewFromData(1, &dat_len, np.NPY_COMPLEX128, mat.data) + PyArray_ENABLEFLAGS(_data, np.NPY_OWNDATA) + + _ind = np.PyArray_SimpleNewFromData(1, &dat_len, np.NPY_INT32, mat.indices) + PyArray_ENABLEFLAGS(_ind, np.NPY_OWNDATA) + + _ptr = np.PyArray_SimpleNewFromData(1, &ptr_len, np.NPY_INT32, mat.indptr) + PyArray_ENABLEFLAGS(_ptr, np.NPY_OWNDATA) + mat.numpy_lock = 1 + return fast_csr_matrix((_data, _ind, _ptr), shape=(mat.nrows,mat.ncols)) + else: + if mat.numpy_lock: + raise_error_CSR(-4) + elif not mat.is_set: + raise_error_CSR(-3) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef object COO_to_scipy(COO_Matrix * mat): + """ + Converts a COO_Matrix struct to a SciPy coo_matrix class object. + The NumPy arrays are generated from the pointers, and the lifetime + of the pointer memory is tied to that of the NumPy array + (i.e. automatic garbage cleanup.) + + Parameters + ---------- + mat : COO_Matrix * + Pointer to COO_Matrix. + """ + cdef np.npy_intp dat_len + cdef np.ndarray[complex, ndim=1] _data + cdef np.ndarray[int, ndim=1] _row, _col + if (not mat.numpy_lock) and mat.is_set: + dat_len = mat.nnz + _data = np.PyArray_SimpleNewFromData(1, &dat_len, np.NPY_COMPLEX128, mat.data) + PyArray_ENABLEFLAGS(_data, np.NPY_OWNDATA) + + _row = np.PyArray_SimpleNewFromData(1, &dat_len, np.NPY_INT32, mat.rows) + PyArray_ENABLEFLAGS(_row, np.NPY_OWNDATA) + + _col = np.PyArray_SimpleNewFromData(1, &dat_len, np.NPY_INT32, mat.cols) + PyArray_ENABLEFLAGS(_col, np.NPY_OWNDATA) + mat.numpy_lock = 1 + return coo_matrix((_data, (_row, _col)), shape=(mat.nrows,mat.ncols)) + else: + if mat.numpy_lock: + raise_error_COO(-4) + elif not mat.is_set: + raise_error_COO(-3) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void COO_to_CSR(CSR_Matrix * out, COO_Matrix * mat): + """ + Conversion from COO to CSR. Not in place, + but result is sorted correctly. + """ + cdef int i, j, iad, j0 + cdef double complex val + cdef size_t kk + init_CSR(out, mat.nnz, mat.nrows, mat.ncols, max_length=0, init_zeros=1) + # Determine row lengths + for kk in range(mat.nnz): + out.indptr[mat.rows[kk]] = out.indptr[mat.rows[kk]] + 1 + # Starting position of rows + j = 0 + for kk in range(mat.nrows): + j0 = out.indptr[kk] + out.indptr[kk] = j + j += j0 + #Do the data + for kk in range(mat.nnz): + i = mat.rows[kk] + j = mat.cols[kk] + val = mat.data[kk] + iad = out.indptr[i] + out.data[iad] = val + out.indices[iad] = j + out.indptr[i] = iad+1 + # Shift back + for kk in range(mat.nrows,0,-1): + out.indptr[kk] = out.indptr[kk-1] + out.indptr[0] = 0 + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void CSR_to_COO(COO_Matrix * out, CSR_Matrix * mat): + """ + Converts a CSR_Matrix to a COO_Matrix. + """ + cdef int k1, k2 + cdef size_t jj, kk + init_COO(out, mat.nnz, mat.nrows, mat.ncols) + for kk in range(mat.nnz): + out.data[kk] = mat.data[kk] + out.cols[kk] = mat.indices[kk] + for kk in range(mat.nrows-1,0,-1): + k1 = mat.indptr[kk+1] + k2 = mat.indptr[kk] + for jj in range(k2, k1): + out.rows[jj] = kk + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void COO_to_CSR_inplace(CSR_Matrix * out, COO_Matrix * mat): + """ + In place conversion from COO to CSR. In place, but not sorted. + The length of the COO (data,rows,cols) must be equal to the NNZ + in the final matrix (i.e. no padded zeros on ends of arrays). + """ + cdef size_t kk + cdef int i, j, init, inext, jnext, ipos + cdef int * _tmp_rows + cdef complex val, val_next + cdef int * work = PyDataMem_NEW_ZEROED(mat.nrows+1, sizeof(int)) + # Determine output indptr array + for kk in range(mat.nnz): + i = mat.rows[kk] + work[i+1] += 1 + work[0] = 0 + for kk in range(mat.nrows): + work[kk+1] += work[kk] + + if mat.nnz < (mat.nrows+1): + _tmp_rows = PyDataMem_RENEW(mat.rows, (mat.nrows+1) * sizeof(int)) + mat.rows = _tmp_rows + init = 0 + while init < mat.nnz: + while (mat.rows[init] < 0): + init += 1 + val = mat.data[init] + i = mat.rows[init] + j = mat.cols[init] + mat.rows[init] = -1 + while 1: + ipos = work[i] + val_next = mat.data[ipos] + inext = mat.rows[ipos] + jnext = mat.cols[ipos] + + mat.data[ipos] = val + mat.cols[ipos] = j + mat.rows[ipos] = -1 + work[i] += 1 + if inext < 0: + break + val = val_next + i = inext + j = jnext + init += 1 + + for kk in range(mat.nrows): + mat.rows[kk+1] = work[kk] + mat.rows[0] = 0 + + if mat.nnz > (mat.nrows+1): + _tmp_rows = PyDataMem_RENEW(mat.rows, (mat.nrows+1) * sizeof(int)) + mat.rows = _tmp_rows + #Free working array + PyDataMem_FREE(work) + #Set CSR pointers to original COO data. + out.data = mat.data + out.indices = mat.cols + out.indptr = mat.rows + out.nrows = mat.nrows + out.ncols = mat.ncols + out.nnz = mat.nnz + out.max_length = mat.nnz + out.is_set = 1 + out.numpy_lock = 0 + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int ind_sort(data_ind_pair x, data_ind_pair y): + return x.ind < y.ind + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void sort_indices(CSR_Matrix * mat): + """ + Sorts the indices of a CSR_Matrix inplace. + """ + cdef size_t ii, jj + cdef vector[data_ind_pair] pairs + cdef cfptr cfptr_ = &ind_sort + cdef int row_start, row_end, length + + for ii in range(mat.nrows): + row_start = mat.indptr[ii] + row_end = mat.indptr[ii+1] + length = row_end - row_start + pairs.resize(length) + + for jj in range(length): + pairs[jj].data = mat.data[row_start+jj] + pairs[jj].ind = mat.indices[row_start+jj] + + sort(pairs.begin(),pairs.end(),cfptr_) + + for jj in range(length): + mat.data[row_start+jj] = pairs[jj].data + mat.indices[row_start+jj] = pairs[jj].ind + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef CSR_Matrix CSR_from_scipy(object A): + """ + Converts a SciPy CSR sparse matrix to a + CSR_Matrix struct. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + cdef int nnz = ptr[nrows] + + cdef CSR_Matrix mat + + mat.data = &data[0] + mat.indices = &ind[0] + mat.indptr = &ptr[0] + mat.nrows = nrows + mat.ncols = ncols + mat.nnz = nnz + mat.max_length = nnz + mat.is_set = 1 + mat.numpy_lock = 1 + + return mat + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void CSR_from_scipy_inplace(object A, CSR_Matrix* mat): + """ + Converts a SciPy CSR sparse matrix to a + CSR_Matrix struct. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + cdef int nnz = ptr[nrows] + + mat.data = &data[0] + mat.indices = &ind[0] + mat.indptr = &ptr[0] + mat.nrows = nrows + mat.ncols = ncols + mat.nnz = nnz + mat.max_length = nnz + mat.is_set = 1 + mat.numpy_lock = 1 + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef COO_Matrix COO_from_scipy(object A): + """ + Converts a SciPy COO sparse matrix to a + COO_Matrix struct. + """ + cdef complex[::1] data = A.data + cdef int[::1] rows = A.row + cdef int[::1] cols = A.col + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + cdef int nnz = data.shape[0] + + cdef COO_Matrix mat + mat.data = &data[0] + mat.rows = &rows[0] + mat.cols = &cols[0] + mat.nrows = nrows + mat.ncols = ncols + mat.nnz = nnz + mat.max_length = nnz + mat.is_set = 1 + mat.numpy_lock = 1 + + return mat + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void identity_CSR(CSR_Matrix * mat, unsigned int nrows): + cdef size_t kk + init_CSR(mat, nrows, nrows, nrows, 0, 0) + for kk in range(nrows): + mat.data[kk] = 1 + mat.indices[kk] = kk + mat.indptr[kk] = kk + mat.indptr[nrows] = nrows diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_structs.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_structs.pxd new file mode 100755 index 0000000000..89c7971639 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_structs.pxd @@ -0,0 +1,59 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +cdef struct _csr_mat: + double complex * data + int * indices + int * indptr + int nnz + int nrows + int ncols + int is_set + int max_length + int numpy_lock + +cdef struct _coo_mat: + double complex * data + int * rows + int * cols + int nnz + int nrows + int ncols + int is_set + int max_length + int numpy_lock + +ctypedef _csr_mat CSR_Matrix +ctypedef _coo_mat COO_Matrix diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx new file mode 100755 index 0000000000..beb04563e1 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx @@ -0,0 +1,375 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +from qutip.fastsparse import fast_csr_matrix +cimport numpy as cnp +from libc.math cimport abs, fabs, sqrt +from libcpp cimport bool +cimport cython +cnp.import_array() + +cdef extern from "numpy/arrayobject.h" nogil: + void PyArray_ENABLEFLAGS(cnp.ndarray arr, int flags) + void PyDataMem_FREE(void * ptr) + void PyDataMem_RENEW(void * ptr, size_t size) + void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) + void PyDataMem_NEW(size_t size) + + +cdef extern from "" namespace "std" nogil: + double abs(double complex x) + double real(double complex x) + double imag(double complex x) + +cdef extern from "" namespace "std" nogil: + double cabs "abs" (double complex x) + +cdef inline int int_max(int x, int y): + return x ^ ((x ^ y) & -(x < y)) + +include "parameters.pxi" + +@cython.boundscheck(False) +@cython.wraparound(False) +def _sparse_bandwidth( + int[::1] idx, + int[::1] ptr, + int nrows): + """ + Calculates the max (mb), lower(lb), and upper(ub) bandwidths of a + csr_matrix. + """ + cdef int ldist + cdef int lb = -nrows + cdef int ub = -nrows + cdef int mb = 0 + cdef size_t ii, jj + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii + 1]): + ldist = ii - idx[jj] + lb = int_max(lb, ldist) + ub = int_max(ub, -ldist) + mb = int_max(mb, ub + lb + 1) + + return mb, lb, ub + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _sparse_profile(int[::1] idx, + int[::1] ptr, + int nrows): + cdef int ii, jj, temp, ldist=0 + cdef LTYPE_t pro = 0 + for ii in range(nrows): + temp = 0 + for jj in range(ptr[ii], ptr[ii + 1]): + ldist = idx[jj] - ii + temp = int_max(temp, ldist) + pro += temp + return pro + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _sparse_permute( + cnp.ndarray[cython.numeric, ndim=1] data, + int[::1] idx, + int[::1] ptr, + int nrows, + int ncols, + cnp.ndarray[ITYPE_t, ndim=1] rperm, + cnp.ndarray[ITYPE_t, ndim=1] cperm, + int flag): + """ + Permutes the rows and columns of a sparse CSR or CSC matrix according to + the permutation arrays rperm and cperm, respectively. + Here, the permutation arrays specify the new order of the rows and columns. + i.e. [0,1,2,3,4] -> [3,0,4,1,2]. + """ + cdef int ii, jj, kk, k0, nnz + cdef cnp.ndarray[cython.numeric] new_data = np.zeros_like(data) + cdef cnp.ndarray[ITYPE_t] new_idx = np.zeros_like(idx) + cdef cnp.ndarray[ITYPE_t] new_ptr = np.zeros_like(ptr) + cdef cnp.ndarray[ITYPE_t] perm_r + cdef cnp.ndarray[ITYPE_t] perm_c + cdef cnp.ndarray[ITYPE_t] inds + + if flag == 0: # CSR matrix + if rperm.shape[0] != 0: + inds = np.argsort(rperm).astype(ITYPE) + perm_r = np.arange(rperm.shape[0], dtype=ITYPE)[inds] + + for jj in range(nrows): + ii = perm_r[jj] + new_ptr[ii + 1] = ptr[jj + 1] - ptr[jj] + + for jj in range(nrows): + new_ptr[jj + 1] = new_ptr[jj+1] + new_ptr[jj] + + for jj in range(nrows): + k0 = new_ptr[perm_r[jj]] + for kk in range(ptr[jj], ptr[jj + 1]): + new_idx[k0] = idx[kk] + new_data[k0] = data[kk] + k0 = k0 + 1 + + if cperm.shape[0] != 0: + inds = np.argsort(cperm).astype(ITYPE) + perm_c = np.arange(cperm.shape[0], dtype=ITYPE)[inds] + nnz = new_ptr[new_ptr.shape[0] - 1] + for jj in range(nnz): + new_idx[jj] = perm_c[new_idx[jj]] + + elif flag == 1: # CSC matrix + if cperm.shape[0] != 0: + inds = np.argsort(cperm).astype(ITYPE) + perm_c = np.arange(cperm.shape[0], dtype=ITYPE)[inds] + + for jj in range(ncols): + ii = perm_c[jj] + new_ptr[ii + 1] = ptr[jj + 1] - ptr[jj] + + for jj in range(ncols): + new_ptr[jj + 1] = new_ptr[jj + 1] + new_ptr[jj] + + for jj in range(ncols): + k0 = new_ptr[perm_c[jj]] + for kk in range(ptr[jj], ptr[jj + 1]): + new_idx[k0] = idx[kk] + new_data[k0] = data[kk] + k0 = k0 + 1 + + if rperm.shape[0] != 0: + inds = np.argsort(rperm).astype(ITYPE) + perm_r = np.arange(rperm.shape[0], dtype=ITYPE)[inds] + nnz = new_ptr[new_ptr.shape[0] - 1] + for jj in range(nnz): + new_idx[jj] = perm_r[new_idx[jj]] + + return new_data, new_idx, new_ptr + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _sparse_reverse_permute( + cnp.ndarray[cython.numeric, ndim=1] data, + int[::1] idx, + int[::1] ptr, + int nrows, + int ncols, + cnp.ndarray[ITYPE_t, ndim=1] rperm, + cnp.ndarray[ITYPE_t, ndim=1] cperm, + int flag): + """ + Reverse permutes the rows and columns of a sparse CSR or CSC matrix + according to the original permutation arrays rperm and cperm, respectively. + """ + cdef int ii, jj, kk, k0, nnz + cdef cnp.ndarray[cython.numeric, ndim=1] new_data = np.zeros_like(data) + cdef cnp.ndarray[ITYPE_t, ndim=1] new_idx = np.zeros_like(idx) + cdef cnp.ndarray[ITYPE_t, ndim=1] new_ptr = np.zeros_like(ptr) + + if flag == 0: # CSR matrix + if rperm.shape[0] != 0: + for jj in range(nrows): + ii = rperm[jj] + new_ptr[ii + 1] = ptr[jj + 1] - ptr[jj] + + for jj in range(nrows): + new_ptr[jj + 1] = new_ptr[jj + 1] + new_ptr[jj] + + for jj in range(nrows): + k0 = new_ptr[rperm[jj]] + for kk in range(ptr[jj], ptr[jj + 1]): + new_idx[k0] = idx[kk] + new_data[k0] = data[kk] + k0 = k0 + 1 + + if cperm.shape[0] > 0: + nnz = new_ptr[new_ptr.shape[0] - 1] + for jj in range(nnz): + new_idx[jj] = cperm[new_idx[jj]] + + if flag == 1: # CSC matrix + if cperm.shape[0] != 0: + for jj in range(ncols): + ii = cperm[jj] + new_ptr[ii + 1] = ptr[jj + 1] - ptr[jj] + + for jj in range(ncols): + new_ptr[jj + 1] = new_ptr[jj + 1] + new_ptr[jj] + + for jj in range(ncols): + k0 = new_ptr[cperm[jj]] + for kk in range(ptr[jj], ptr[jj + 1]): + new_idx[k0] = idx[kk] + new_data[k0] = data[kk] + k0 = k0 + 1 + + if cperm.shape[0] != 0: + nnz = new_ptr[new_ptr.shape[0] - 1] + for jj in range(nnz): + new_idx[jj] = rperm[new_idx[jj]] + + return new_data, new_idx, new_ptr + + +@cython.boundscheck(False) +@cython.wraparound(False) +def _isdiag(int[::1] idx, + int[::1] ptr, + int nrows): + + cdef int row, num_elems + for row in range(nrows): + num_elems = ptr[row+1] - ptr[row] + if num_elems > 1: + return 0 + elif num_elems == 1: + if idx[ptr[row]] != row: + return 0 + return 1 + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode='c'] _csr_get_diag(complex[::1] data, + int[::1] idx, int[::1] ptr, int k=0): + + cdef size_t row, jj + cdef int num_rows = ptr.shape[0]-1 + cdef int abs_k = abs(k) + cdef int start, stop + cdef cnp.ndarray[complex, ndim=1, mode='c'] out = np.zeros(num_rows-abs_k, dtype=complex) + + if k >= 0: + start = 0 + stop = num_rows-abs_k + else: #k < 0 + start = abs_k + stop = num_rows + + for row in range(start, stop): + for jj in range(ptr[row], ptr[row+1]): + if idx[jj]-k == row: + out[row-start] = data[jj] + break + return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +def unit_row_norm(complex[::1] data, int[::1] ptr, int nrows): + cdef size_t row, ii + cdef double total + for row in range(nrows): + total = 0 + for ii in range(ptr[row], ptr[row+1]): + total += real(data[ii]) * real(data[ii]) + imag(data[ii]) * imag(data[ii]) + total = sqrt(total) + for ii in range(ptr[row], ptr[row+1]): + data[ii] /= total + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef double zcsr_one_norm(complex[::1] data, int[::1] ind, int[::1] ptr, + int nrows, int ncols): + + cdef int k + cdef size_t ii, jj + cdef double * col_sum = PyDataMem_NEW_ZEROED(ncols, sizeof(double)) + cdef double max_col = 0 + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + col_sum[k] += cabs(data[jj]) + for ii in range(ncols): + if col_sum[ii] > max_col: + max_col = col_sum[ii] + PyDataMem_FREE(col_sum) + return max_col + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef double zcsr_inf_norm(complex[::1] data, int[::1] ind, int[::1] ptr, + int nrows, int ncols): + + cdef int k + cdef size_t ii, jj + cdef double * row_sum = PyDataMem_NEW_ZEROED(nrows, sizeof(double)) + cdef double max_row = 0 + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + row_sum[ii] += cabs(data[jj]) + for ii in range(nrows): + if row_sum[ii] > max_row: + max_row = row_sum[ii] + PyDataMem_FREE(row_sum) + return max_row + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef bool cy_tidyup(complex[::1] data, double atol, unsigned int nnz): + """ + Performs an in-place tidyup of CSR matrix data + """ + cdef size_t kk + cdef double re, im + cdef bool re_flag, im_flag, out_flag = 0 + for kk in range(nnz): + re_flag = 0 + im_flag = 0 + re = real(data[kk]) + im = imag(data[kk]) + if fabs(re) < atol: + re = 0 + re_flag = 1 + if fabs(im) < atol: + im = 0 + im_flag = 1 + + if re_flag or im_flag: + data[kk] = re + 1j*im + + if re_flag and im_flag: + out_flag = 1 + return out_flag diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd new file mode 100755 index 0000000000..df2785fb77 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd @@ -0,0 +1,39 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +from qutip.cy.sparse_structs cimport CSR_Matrix + +cdef void fdense2D_to_CSR(complex[::1, :] mat, CSR_Matrix * out, + unsigned int nrows, unsigned int ncols) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx new file mode 100755 index 0000000000..0a4abe24e4 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx @@ -0,0 +1,303 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +from qutip.fastsparse import fast_csr_matrix +cimport numpy as cnp +cimport cython +from libc.stdlib cimport div, malloc, free + +cdef extern from "stdlib.h": + ctypedef struct div_t: + int quot + int rem + +include "sparse_routines.pxi" + +@cython.boundscheck(False) +@cython.wraparound(False) +def arr_coo2fast(complex[::1] data, int[::1] rows, int[::1] cols, int nrows, int ncols): + """ + Converts a set of ndarrays (data, rows, cols) that specify a COO sparse matrix + to CSR format. + """ + cdef int nnz = data.shape[0] + cdef COO_Matrix mat + mat.data = &data[0] + mat.rows = &rows[0] + mat.cols = &cols[0] + mat.nrows = nrows + mat.ncols = ncols + mat.nnz = nnz + mat.is_set = 1 + mat.max_length = nnz + + cdef CSR_Matrix out + COO_to_CSR(&out, &mat) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def dense2D_to_fastcsr_cmode(complex[:, ::1] mat, int nrows, int ncols): + """ + Converts a dense c-mode complex ndarray to a sparse CSR matrix. + + Parameters + ---------- + mat : ndarray + Input complex ndarray + nrows : int + Number of rows in matrix. + ncols : int + Number of cols in matrix. + + Returns + ------- + out : fast_csr_matrix + Output matrix in CSR format. + """ + cdef int nnz = 0 + cdef size_t ii, jj + cdef np.ndarray[complex, ndim=1, mode='c'] data = np.zeros(nrows*ncols, dtype=complex) + cdef np.ndarray[int, ndim=1, mode='c'] ind = np.zeros(nrows*ncols, dtype=np.int32) + cdef np.ndarray[int, ndim=1, mode='c'] ptr = np.zeros(nrows+1, dtype=np.int32) + + for ii in range(nrows): + for jj in range(ncols): + if mat[ii,jj] != 0: + ind[nnz] = jj + data[nnz] = mat[ii,jj] + nnz += 1 + ptr[ii+1] = nnz + + if nnz < (nrows*ncols): + return fast_csr_matrix((data[:nnz], ind[:nnz], ptr), shape=(nrows,ncols)) + else: + return fast_csr_matrix((data, ind, ptr), shape=(nrows,ncols)) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def dense1D_to_fastcsr_ket(complex[::1] vec): + """ + Converts a dense c-mode complex ndarray to a sparse CSR matrix. + + Parameters + ---------- + mat : ndarray + Input complex ndarray + + Returns + ------- + out : fast_csr_matrix + Output matrix in CSR format. + """ + cdef int nnz = 0 + cdef size_t ii, nrows = vec.shape[0] + cdef np.ndarray[complex, ndim=1, mode='c'] data = np.zeros(nrows, dtype=complex) + cdef np.ndarray[int, ndim=1, mode='c'] ind = np.zeros(nrows, dtype=np.int32) + cdef np.ndarray[int, ndim=1, mode='c'] ptr = np.zeros(nrows+1, dtype=np.int32) + + for ii in range(nrows): + if vec[ii] != 0: + data[nnz] = vec[ii] + nnz += 1 + ptr[ii+1] = nnz + + if nnz < (nrows): + return fast_csr_matrix((data[:nnz], ind[:nnz], ptr), shape=(nrows,1)) + else: + return fast_csr_matrix((data, ind, ptr), shape=(nrows,1)) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void fdense2D_to_CSR(complex[::1, :] mat, CSR_Matrix * out, + unsigned int nrows, unsigned int ncols): + """ + Converts a dense complex ndarray to a CSR matrix struct. + + Parameters + ---------- + mat : ndarray + Input complex ndarray + nrows : int + Number of rows in matrix. + ncols : int + Number of cols in matrix. + + Returns + ------- + out : CSR_Matrix + Output matrix as CSR struct. + """ + cdef int nnz = 0 + cdef size_t ii, jj + init_CSR(out, nrows*ncols, nrows, ncols, nrows*ncols) + + for ii in range(nrows): + for jj in range(ncols): + if mat[ii,jj] != 0: + out.indices[nnz] = jj + out.data[nnz] = mat[ii,jj] + nnz += 1 + out.indptr[ii+1] = nnz + + if nnz < (nrows*ncols): + shorten_CSR(out, nnz) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def dense2D_to_fastcsr_fmode(complex[::1, :] mat, int nrows, int ncols): + """ + Converts a dense fortran-mode complex ndarray to a sparse CSR matrix. + + Parameters + ---------- + mat : ndarray + Input complex ndarray + nrows : int + Number of rows in matrix. + ncols : int + Number of cols in matrix. + + Returns + ------- + out : fast_csr_matrix + Output matrix in CSR format. + """ + cdef int nnz = 0 + cdef size_t ii, jj + cdef np.ndarray[complex, ndim=1, mode='c'] data = np.zeros(nrows*ncols, dtype=complex) + cdef np.ndarray[int, ndim=1, mode='c'] ind = np.zeros(nrows*ncols, dtype=np.int32) + cdef np.ndarray[int, ndim=1, mode='c'] ptr = np.zeros(nrows+1, dtype=np.int32) + + for ii in range(nrows): + for jj in range(ncols): + if mat[ii,jj] != 0: + ind[nnz] = jj + data[nnz] = mat[ii,jj] + nnz += 1 + ptr[ii+1] = nnz + + if nnz < (nrows*ncols): + return fast_csr_matrix((data[:nnz], ind[:nnz], ptr), shape=(nrows,ncols)) + else: + return fast_csr_matrix((data, ind, ptr), shape=(nrows,ncols)) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_reshape(object A not None, int new_rows, int new_cols): + """ + Reshapes a complex CSR matrix. + + Parameters + ---------- + A : fast_csr_matrix + Input CSR matrix. + new_rows : int + Number of rows in reshaped matrix. + new_cols : int + Number of cols in reshaped matrix. + + Returns + ------- + out : fast_csr_matrix + Reshaped CSR matrix. + + Notes + ----- + This routine does not need to make a temp. copy of the matrix. + """ + cdef CSR_Matrix inmat = CSR_from_scipy(A) + cdef COO_Matrix mat + CSR_to_COO(&mat, &inmat) + cdef CSR_Matrix out + cdef div_t new_inds + cdef size_t kk + + if (mat.nrows * mat.ncols) != (new_rows * new_cols): + raise Exception('Total size of array must be unchanged.') + + for kk in range(mat.nnz): + new_inds = div(mat.ncols*mat.rows[kk]+mat.cols[kk], new_cols) + mat.rows[kk] = new_inds.quot + mat.cols[kk] = new_inds.rem + + mat.nrows = new_rows + mat.ncols = new_cols + + COO_to_CSR_inplace(&out, &mat) + sort_indices(&out) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.cdivision(True) +def cy_index_permute(int [::1] idx_arr, + int [::1] dims, + int [::1] order): + + cdef int ndims = dims.shape[0] + cdef int ii, n, dim, idx, orderr + + #the fastest way to allocate memory for a temporary array + cdef int * multi_idx = malloc(sizeof(int) * ndims) + + try: + for ii from 0 <= ii < idx_arr.shape[0]: + idx = idx_arr[ii] + + #First, decompose long index into multi-index + for n from ndims > n >= 0: + dim = dims[n] + multi_idx[n] = idx % dim + idx = idx // dim + + #Finally, assemble new long index from reordered multi-index + dim = 1 + idx = 0 + for n from ndims > n >= 0: + orderr = order[n] + idx += multi_idx[orderr] * dim + dim *= dims[orderr] + + idx_arr[ii] = idx + finally: + free(multi_idx) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pxd new file mode 100755 index 0000000000..1fe0259a9e --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pxd @@ -0,0 +1,115 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +cimport numpy as cnp +cimport cython +from libcpp cimport bool + +include "parameters.pxi" + +cpdef cnp.ndarray[CTYPE_t, ndim=1, mode="c"] spmv_csr(complex[::1] data, + int[::1] ind, int[::1] ptr, complex[::1] vec) + + +cdef void spmvpy(complex * data, + int * ind, + int * ptr, + complex * vec, + complex a, + complex * out, + unsigned int nrows) + + +cpdef cy_expect_rho_vec_csr(complex[::1] data, + int[::1] idx, + int[::1] ptr, + complex[::1] rho_vec, + int herm) + + +cpdef cy_expect_psi(object A, + complex[::1] vec, + bool isherm) + + +cpdef cy_expect_psi_csr(complex[::1] data, + int[::1] ind, + int[::1] ptr, + complex[::1] vec, + bool isherm) + + +cdef void _spmm_c_py(complex * data, + int * ind, + int * ptr, + complex * mat, + complex a, + complex * out, + unsigned int sp_rows, + unsigned int nrows, + unsigned int ncols) + +cpdef void spmmpy_c(complex[::1] data, + int[::1] ind, + int[::1] ptr, + complex[:,::1] M, + complex a, + complex[:,::1] out) + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmmc(object sparse, + complex[:,::1] mat) + +cdef void _spmm_f_py(complex * data, + int * ind, + int * ptr, + complex * mat, + complex a, + complex * out, + unsigned int sp_rows, + unsigned int nrows, + unsigned int ncols) + +cpdef void spmmpy_f(complex[::1] data, + int[::1] ind, + int[::1] ptr, + complex[::1,:] mat, + complex a, + complex[::1,:] out) + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmmf(object sparse, + complex[::1,:] mat) + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmm(object sparse, + cnp.ndarray[complex, ndim=2] mat) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx new file mode 100755 index 0000000000..1408140e73 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx @@ -0,0 +1,572 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +cimport numpy as cnp +cimport cython +cimport libc.math +from libcpp cimport bool + +cdef extern from "src/zspmv.hpp" nogil: + void zspmvpy(double complex *data, int *ind, int *ptr, double complex *vec, + double complex a, double complex *out, int nrows) + +include "complex_math.pxi" + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmv( + object super_op, + complex[::1] vec): + """ + Sparse matrix, dense vector multiplication. + Here the vector is assumed to have one-dimension. + Matrix must be in CSR format and have complex entries. + + Parameters + ---------- + super_op : csr matrix + vec : array + Dense vector for multiplication. Must be one-dimensional. + + Returns + ------- + out : array + Returns dense array. + + """ + return spmv_csr(super_op.data, super_op.indices, super_op.indptr, vec) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmv_csr(complex[::1] data, + int[::1] ind, int[::1] ptr, complex[::1] vec): + """ + Sparse matrix, dense vector multiplication. + Here the vector is assumed to have one-dimension. + Matrix must be in CSR format and have complex entries. + + Parameters + ---------- + data : array + Data for sparse matrix. + idx : array + Indices for sparse matrix data. + ptr : array + Pointers for sparse matrix data. + vec : array + Dense vector for multiplication. Must be one-dimensional. + + Returns + ------- + out : array + Returns dense array. + + """ + cdef unsigned int num_rows = ptr.shape[0] - 1 + cdef cnp.ndarray[complex, ndim=1, mode="c"] out = np.zeros((num_rows), dtype=np.complex) + zspmvpy(&data[0], &ind[0], &ptr[0], &vec[0], 1.0, &out[0], num_rows) + return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +def spmvpy_csr(complex[::1] data, + int[::1] ind, int[::1] ptr, complex[::1] vec, + complex alpha, complex[::1] out): + """ + Sparse matrix, dense vector multiplication. + Here the vector is assumed to have one-dimension. + Matrix must be in CSR format and have complex entries. + + Parameters + ---------- + data : array + Data for sparse matrix. + idx : array + Indices for sparse matrix data. + ptr : array + Pointers for sparse matrix data. + vec : array + Dense vector for multiplication. Must be one-dimensional. + alpha : complex + Numerical coefficient for sparse matrix. + out: array + Output array + + """ + cdef unsigned int num_rows = vec.shape[0] + zspmvpy(&data[0], &ind[0], &ptr[0], &vec[0], alpha, &out[0], num_rows) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef inline void spmvpy(complex* data, int* ind, int* ptr, + complex* vec, + complex a, + complex* out, + unsigned int nrows): + + zspmvpy(data, ind, ptr, vec, a, out, nrows) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _spmm_c_py(complex* data, int* ind, int* ptr, + complex* mat, complex a, complex* out, + unsigned int sp_rows, unsigned int nrows, unsigned int ncols): + """ + sparse*dense "C" ordered. + """ + cdef int row, col, ii, jj, row_start, row_end + for row from 0 <= row < sp_rows : + row_start = ptr[row] + row_end = ptr[row+1] + for jj from row_start <= jj < row_end: + for col in range(ncols): + out[row * ncols + col] += a*data[jj]*mat[ind[jj] * ncols + col] + + +cpdef void spmmpy_c(complex[::1] data, int[::1] ind, int[::1] ptr, + complex[:,::1] M, complex a, complex[:,::1] out): + """ + Sparse matrix, c ordered dense matrix multiplication. + The sparse matrix must be in CSR format and have complex entries. + + Parameters + ---------- + data : array + Data for sparse matrix. + idx : array + Indices for sparse matrix data. + ptr : array + Pointers for sparse matrix data. + mat : array 2d + Dense matrix for multiplication. Must be in c mode. + alpha : complex + Numerical coefficient for sparse matrix. + out: array + Output array. Must be in c mode. + + """ + cdef unsigned int sp_rows = ptr.shape[0]-1 + cdef unsigned int nrows = M.shape[0] + cdef unsigned int ncols = M.shape[1] + _spmm_c_py(&data[0], &ind[0], &ptr[0], &M[0,0], 1., + &out[0,0], sp_rows, nrows, ncols) + + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmmc(object sparse, + complex[:,::1] mat): + """ + Sparse matrix, c ordered dense matrix multiplication. + The sparse matrix must be in CSR format and have complex entries. + + Parameters + ---------- + sparse : csr matrix + mat : array 2d + Dense matrix for multiplication. Must be in c mode. + + Returns + ------- + out : array + Keep input ordering + """ + cdef unsigned int sp_rows = sparse.indptr.shape[0]-1 + cdef unsigned int ncols = mat.shape[1] + cdef cnp.ndarray[complex, ndim=2, mode="c"] out = \ + np.zeros((sp_rows, ncols), dtype=complex) + spmmpy_c(sparse.data, sparse.indices, sparse.indptr, + mat, 1., out) + return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _spmm_f_py(complex* data, int* ind, int* ptr, + complex* mat, complex a, complex* out, + unsigned int sp_rows, unsigned int nrows, unsigned int ncols): + """ + sparse*dense "F" ordered. + """ + cdef int col + for col in range(ncols): + spmvpy(data, ind, ptr, mat+nrows*col, a, out+sp_rows*col, sp_rows) + + +cpdef void spmmpy_f(complex[::1] data, int[::1] ind, int[::1] ptr, + complex[::1,:] mat, complex a, complex[::1,:] out): + """ + Sparse matrix, fortran ordered dense matrix multiplication. + The sparse matrix must be in CSR format and have complex entries. + + Parameters + ---------- + data : array + Data for sparse matrix. + idx : array + Indices for sparse matrix data. + ptr : array + Pointers for sparse matrix data. + mat : array 2d + Dense matrix for multiplication. Must be in fortran mode. + alpha : complex + Numerical coefficient for sparse matrix. + out: array + Output array. Must be in fortran mode. + + """ + cdef unsigned int sp_rows = ptr.shape[0]-1 + cdef unsigned int nrows = mat.shape[0] + cdef unsigned int ncols = mat.shape[1] + _spmm_f_py(&data[0], &ind[0], &ptr[0], &mat[0,0], 1., + &out[0,0], sp_rows, nrows, ncols) + + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmmf(object sparse, + complex[::1,:] mat): + """ + Sparse matrix, fortran ordered dense matrix multiplication. + The sparse matrix must be in CSR format and have complex entries. + + Parameters + ---------- + sparse : csr matrix + mat : array 2d + Dense matrix for multiplication. Must be in fortran mode. + + Returns + ------- + out : array + Keep input ordering + """ + cdef unsigned int sp_rows = sparse.indptr.shape[0]-1 + cdef unsigned int ncols = mat.shape[1] + cdef cnp.ndarray[complex, ndim=2, mode="fortran"] out = \ + np.zeros((sp_rows, ncols), dtype=complex, order="F") + spmmpy_f(sparse.data, sparse.indices, sparse.indptr, + mat, 1., out) + return out + + +cpdef cnp.ndarray[complex, ndim=1, mode="c"] spmm(object sparse, + cnp.ndarray[complex, ndim=2] mat): + """ + Sparse matrix, dense matrix multiplication. + The sparse matrix must be in CSR format and have complex entries. + + Parameters + ---------- + sparse : csr matrix + mat : array 2d + Dense matrix for multiplication. Can be in c or fortran mode. + + Returns + ------- + out : array + Keep input ordering + """ + if mat.flags["F_CONTIGUOUS"]: + return spmmf(sparse, mat) + else: + return spmmc(sparse, mat) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] cy_ode_rhs( + double t, + complex[::1] rho, + complex[::1] data, + int[::1] ind, + int[::1] ptr): + + cdef unsigned int nrows = rho.shape[0] + cdef cnp.ndarray[complex, ndim=1, mode="c"] out = \ + np.zeros(nrows, dtype=complex) + zspmvpy(&data[0], &ind[0], &ptr[0], &rho[0], 1.0, &out[0], nrows) + + return out + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] cy_ode_psi_func_td( + double t, + cnp.ndarray[complex, ndim=1, mode="c"] psi, + object H_func, + object args): + + H = H_func(t, args).data + return -1j * spmv_csr(H.data, H.indices, H.indptr, psi) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] cy_ode_psi_func_td_with_state( + double t, + cnp.ndarray[complex, ndim=1, mode="c"] psi, + object H_func, + object args): + + H = H_func(t, psi, args) + return -1j * spmv_csr(H.data, H.indices, H.indptr, psi) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cnp.ndarray[complex, ndim=1, mode="c"] cy_ode_rho_func_td( + double t, + cnp.ndarray[complex, ndim=1, mode="c"] rho, + object L0, + object L_func, + object args): + cdef object L + L = L0 + L_func(t, args).data + return spmv_csr(L.data, L.indices, L.indptr, rho) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cy_expect_psi(object A, complex[::1] vec, bool isherm): + + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + + cdef size_t row, jj + cdef int nrows = vec.shape[0] + cdef complex expt = 0, temp, cval + + for row in range(nrows): + cval = conj(vec[row]) + temp = 0 + for jj in range(ptr[row], ptr[row+1]): + temp += data[jj]*vec[ind[jj]] + expt += cval*temp + + if isherm : + return real(expt) + else: + return expt + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cy_expect_psi_csr(complex[::1] data, + int[::1] ind, + int[::1] ptr, + complex[::1] vec, + bool isherm): + + cdef size_t row, jj + cdef int nrows = vec.shape[0] + cdef complex expt = 0, temp, cval + + for row in range(nrows): + cval = conj(vec[row]) + temp = 0 + for jj in range(ptr[row], ptr[row+1]): + temp += data[jj]*vec[ind[jj]] + expt += cval*temp + + if isherm : + return real(expt) + else: + return expt + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cy_expect_rho_vec(object super_op, + complex[::1] rho_vec, + int herm): + + return cy_expect_rho_vec_csr(super_op.data, + super_op.indices, + super_op.indptr, + rho_vec, + herm) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cy_expect_rho_vec_csr(complex[::1] data, + int[::1] idx, + int[::1] ptr, + complex[::1] rho_vec, + int herm): + + cdef size_t row + cdef int jj,row_start,row_end + cdef int num_rows = rho_vec.shape[0] + cdef int n = libc.math.sqrt(num_rows) + cdef complex dot = 0.0 + + for row from 0 <= row < num_rows by n+1: + row_start = ptr[row] + row_end = ptr[row+1] + for jj from row_start <= jj < row_end: + dot += data[jj]*rho_vec[idx[jj]] + + if herm == 0: + return dot + else: + return real(dot) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef cy_spmm_tr(object op1, object op2, int herm): + + cdef size_t row + cdef complex tr = 0.0 + + cdef int col1, row1_idx_start, row1_idx_end + cdef complex[::1] data1 = op1.data + cdef int[::1] idx1 = op1.indices + cdef int[::1] ptr1 = op1.indptr + + cdef int col2, row2_idx_start, row2_idx_end + cdef complex[::1] data2 = op2.data + cdef int[::1] idx2 = op2.indices + cdef int[::1] ptr2 = op2.indptr + + cdef int num_rows = ptr1.shape[0]-1 + + for row in range(num_rows): + + row1_idx_start = ptr1[row] + row1_idx_end = ptr1[row + 1] + for row1_idx from row1_idx_start <= row1_idx < row1_idx_end: + col1 = idx1[row1_idx] + + row2_idx_start = ptr2[col1] + row2_idx_end = ptr2[col1 + 1] + for row2_idx from row2_idx_start <= row2_idx < row2_idx_end: + col2 = idx2[row2_idx] + + if col2 == row: + tr += data1[row1_idx] * data2[row2_idx] + break + + if herm == 0: + return tr + else: + return real(tr) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def expect_csr_ket(object A, object B, int isherm): + + cdef complex[::1] Adata = A.data + cdef int[::1] Aind = A.indices + cdef int[::1] Aptr = A.indptr + cdef complex[::1] Bdata = B.data + cdef int[::1] Bptr = B.indptr + cdef int nrows = A.shape[0] + + cdef int j + cdef size_t ii, jj + cdef double complex cval=0, row_sum, expt = 0 + + for ii in range(nrows): + if (Bptr[ii+1] - Bptr[ii]) != 0: + cval = conj(Bdata[Bptr[ii]]) + row_sum = 0 + for jj in range(Aptr[ii], Aptr[ii+1]): + j = Aind[jj] + if (Bptr[j+1] - Bptr[j]) != 0: + row_sum += Adata[jj]*Bdata[Bptr[j]] + expt += cval*row_sum + if isherm: + return real(expt) + else: + return expt + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cpdef double complex zcsr_mat_elem(object A, object left, object right, bool bra_ket=1): + """ + Computes the matrix element for an operator A and left and right vectors. + right must be a ket, but left can be a ket or bra vector. If left + is bra then bra_ket = 1, else set bra_ket = 0. + """ + cdef complex[::1] Adata = A.data + cdef int[::1] Aind = A.indices + cdef int[::1] Aptr = A.indptr + cdef int nrows = A.shape[0] + + cdef complex[::1] Ldata = left.data + cdef int[::1] Lind = left.indices + cdef int[::1] Lptr = left.indptr + cdef int Lnnz = Lind.shape[0] + + cdef complex[::1] Rdata = right.data + cdef int[::1] Rind = right.indices + cdef int[::1] Rptr = right.indptr + + cdef int j, go, head=0 + cdef size_t ii, jj, kk + cdef double complex cval=0, row_sum, mat_elem=0 + + for ii in range(nrows): + row_sum = 0 + go = 0 + if bra_ket: + for kk in range(head, Lnnz): + if Lind[kk] == ii: + cval = Ldata[kk] + head = kk + go = 1 + else: + if (Lptr[ii] - Lptr[ii+1]) != 0: + cval = conj(Ldata[Lptr[ii]]) + go = 1 + + if go: + for jj in range(Aptr[ii], Aptr[ii+1]): + j = Aind[jj] + if (Rptr[j] - Rptr[j+1]) != 0: + row_sum += Adata[jj]*Rdata[Rptr[j]] + mat_elem += cval*row_sum + + return mat_elem diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd new file mode 100755 index 0000000000..cccc021a97 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd @@ -0,0 +1,59 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +from qutip.cy.sparse_structs cimport CSR_Matrix + +cdef void _zcsr_add(CSR_Matrix * A, CSR_Matrix * B, + CSR_Matrix * C, double complex alpha) + +cdef int _zcsr_add_core(double complex * Adata, int * Aind, int * Aptr, + double complex * Bdata, int * Bind, int * Bptr, + double complex alpha, + CSR_Matrix * C, + int nrows, int ncols) nogil + +cdef void _zcsr_mult(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C) + + +cdef void _zcsr_kron(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C) + +cdef void _zcsr_kron_core(double complex * dataA, int * indsA, int * indptrA, + double complex * dataB, int * indsB, int * indptrB, + CSR_Matrix * out, + int rowsA, int rowsB, int colsB) nogil + +cdef void _zcsr_transpose(CSR_Matrix * A, CSR_Matrix * B) + +cdef void _zcsr_adjoint(CSR_Matrix * A, CSR_Matrix * B) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx new file mode 100755 index 0000000000..ae8ffad3e2 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx @@ -0,0 +1,764 @@ +#!python +#cython: language_level=3 +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +import qutip.settings as qset +cimport numpy as cnp +cimport cython +from libcpp cimport bool + +cdef extern from "" namespace "std" nogil: + double complex conj(double complex x) + double real(double complex) + double imag(double complex) + double abs(double complex) + +include "sparse_routines.pxi" + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_add(complex[::1] dataA, int[::1] indsA, int[::1] indptrA, + complex[::1] dataB, int[::1] indsB, int[::1] indptrB, + int nrows, int ncols, + int Annz, int Bnnz, + double complex alpha = 1): + + """ + Adds two sparse CSR matries. Like SciPy, we assume the worse case + for the fill A.nnz + B.nnz. + """ + cdef int worse_fill = Annz + Bnnz + cdef int nnz + #Both matrices are zero mats + if Annz == 0 and Bnnz == 0: + return fast_csr_matrix(([], [], []), shape=(nrows,ncols)) + #A is the zero matrix + elif Annz == 0: + return fast_csr_matrix((alpha*np.asarray(dataB), indsB, indptrB), + shape=(nrows,ncols)) + #B is the zero matrix + elif Bnnz == 0: + return fast_csr_matrix((dataA, indsA, indptrA), + shape=(nrows,ncols)) + # Out CSR_Matrix + cdef CSR_Matrix out + init_CSR(&out, worse_fill, nrows, ncols, worse_fill) + + nnz = _zcsr_add_core(&dataA[0], &indsA[0], &indptrA[0], + &dataB[0], &indsB[0], &indptrB[0], + alpha, + &out, + nrows, ncols) + #Shorten data and indices if needed + if out.nnz > nnz: + shorten_CSR(&out, nnz) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_add(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C, double complex alpha): + """ + Adds two sparse CSR matries. Like SciPy, we assume the worse case + for the fill A.nnz + B.nnz. + """ + cdef int worse_fill = A.nnz + B.nnz + cdef int nrows = A.nrows + cdef int ncols = A.ncols + cdef int nnz + init_CSR(C, worse_fill, nrows, ncols, worse_fill) + + nnz = _zcsr_add_core(A.data, A.indices, A.indptr, + B.data, B.indices, B.indptr, + alpha, C, nrows, ncols) + #Shorten data and indices if needed + if C.nnz > nnz: + shorten_CSR(C, nnz) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int _zcsr_add_core(double complex * Adata, int * Aind, int * Aptr, + double complex * Bdata, int * Bind, int * Bptr, + double complex alpha, + CSR_Matrix * C, + int nrows, int ncols) nogil: + + cdef int j1, j2, kc = 0 + cdef int ka, kb, ka_max, kb_max + cdef size_t ii + cdef double complex tmp + C.indptr[0] = 0 + if alpha != 1: + for ii in range(nrows): + ka = Aptr[ii] + kb = Bptr[ii] + ka_max = Aptr[ii+1]-1 + kb_max = Bptr[ii+1]-1 + while (ka <= ka_max) or (kb <= kb_max): + if ka <= ka_max: + j1 = Aind[ka] + else: + j1 = ncols+1 + + if kb <= kb_max: + j2 = Bind[kb] + else: + j2 = ncols+1 + + if j1 == j2: + tmp = Adata[ka] + alpha*Bdata[kb] + if tmp != 0: + C.data[kc] = tmp + C.indices[kc] = j1 + kc += 1 + ka += 1 + kb += 1 + elif j1 < j2: + C.data[kc] = Adata[ka] + C.indices[kc] = j1 + ka += 1 + kc += 1 + elif j1 > j2: + C.data[kc] = alpha*Bdata[kb] + C.indices[kc] = j2 + kb += 1 + kc += 1 + + C.indptr[ii+1] = kc + else: + for ii in range(nrows): + ka = Aptr[ii] + kb = Bptr[ii] + ka_max = Aptr[ii+1]-1 + kb_max = Bptr[ii+1]-1 + while (ka <= ka_max) or (kb <= kb_max): + if ka <= ka_max: + j1 = Aind[ka] + else: + j1 = ncols+1 + + if kb <= kb_max: + j2 = Bind[kb] + else: + j2 = ncols+1 + + if j1 == j2: + tmp = Adata[ka] + Bdata[kb] + if tmp != 0: + C.data[kc] = tmp + C.indices[kc] = j1 + kc += 1 + ka += 1 + kb += 1 + elif j1 < j2: + C.data[kc] = Adata[ka] + C.indices[kc] = j1 + ka += 1 + kc += 1 + elif j1 > j2: + C.data[kc] = Bdata[kb] + C.indices[kc] = j2 + kb += 1 + kc += 1 + + C.indptr[ii+1] = kc + return kc + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_mult(object A, object B, int sorted = 1): + + cdef complex [::1] dataA = A.data + cdef int[::1] indsA = A.indices + cdef int[::1] indptrA = A.indptr + cdef int Annz = A.nnz + + cdef complex [::1] dataB = B.data + cdef int[::1] indsB = B.indices + cdef int[::1] indptrB = B.indptr + cdef int Bnnz = B.nnz + + cdef int nrows = A.shape[0] + cdef int ncols = B.shape[1] + + #Both matrices are zero mats + if Annz == 0 or Bnnz == 0: + return fast_csr_matrix(shape=(nrows,ncols)) + + cdef int nnz + cdef CSR_Matrix out + + nnz = _zcsr_mult_pass1(&dataA[0], &indsA[0], &indptrA[0], + &dataB[0], &indsB[0], &indptrB[0], + nrows, ncols) + + if nnz == 0: + return fast_csr_matrix(shape=(nrows,ncols)) + + init_CSR(&out, nnz, nrows, ncols) + _zcsr_mult_pass2(&dataA[0], &indsA[0], &indptrA[0], + &dataB[0], &indsB[0], &indptrB[0], + &out, + nrows, ncols) + + #Shorten data and indices if needed + if out.nnz > out.indptr[out.nrows]: + shorten_CSR(&out, out.indptr[out.nrows]) + + if sorted: + sort_indices(&out) + return CSR_to_scipy(&out) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_mult(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C): + + nnz = _zcsr_mult_pass1(A.data, A.indices, A.indptr, + B.data, B.indices, B.indptr, + A.nrows, B.ncols) + + init_CSR(C, nnz, A.nrows, B.ncols) + _zcsr_mult_pass2(A.data, A.indices, A.indptr, + B.data, B.indices, B.indptr, + C, + A.nrows, B.ncols) + + #Shorten data and indices if needed + if C.nnz > C.indptr[C.nrows]: + shorten_CSR(C, C.indptr[C.nrows]) + sort_indices(C) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef int _zcsr_mult_pass1(double complex * Adata, int * Aind, int * Aptr, + double complex * Bdata, int * Bind, int * Bptr, + int nrows, int ncols) nogil: + + cdef int j, k, nnz = 0 + cdef size_t ii,jj,kk + #Setup mask array + cdef int * mask = PyDataMem_NEW(ncols*sizeof(int)) + for ii in range(ncols): + mask[ii] = -1 + #Pass 1 + for ii in range(nrows): + for jj in range(Aptr[ii], Aptr[ii+1]): + j = Aind[jj] + for kk in range(Bptr[j], Bptr[j+1]): + k = Bind[kk] + if mask[k] != ii: + mask[k] = ii + nnz += 1 + PyDataMem_FREE(mask) + return nnz + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_mult_pass2(double complex * Adata, int * Aind, int * Aptr, + double complex * Bdata, int * Bind, int * Bptr, + CSR_Matrix * C, + int nrows, int ncols) nogil: + + cdef int head, length, temp, j, k, nnz = 0 + cdef size_t ii,jj,kk + cdef double complex val + cdef double complex * sums = PyDataMem_NEW_ZEROED(ncols, sizeof(double complex)) + cdef int * nxt = PyDataMem_NEW(ncols*sizeof(int)) + for ii in range(ncols): + nxt[ii] = -1 + + C.indptr[0] = 0 + for ii in range(nrows): + head = -2 + length = 0 + for jj in range(Aptr[ii], Aptr[ii+1]): + j = Aind[jj] + val = Adata[jj] + for kk in range(Bptr[j], Bptr[j+1]): + k = Bind[kk] + sums[k] += val*Bdata[kk] + if nxt[k] == -1: + nxt[k] = head + head = k + length += 1 + + for jj in range(length): + if sums[head] != 0: + C.indices[nnz] = head + C.data[nnz] = sums[head] + nnz += 1 + temp = head + head = nxt[head] + nxt[temp] = -1 + sums[temp] = 0 + + C.indptr[ii+1] = nnz + + #Free temp arrays + PyDataMem_FREE(sums) + PyDataMem_FREE(nxt) + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_kron(object A, object B): + """ + Computes the kronecker product between two complex + sparse matrices in CSR format. + """ + cdef complex[::1] dataA = A.data + cdef int[::1] indsA = A.indices + cdef int[::1] indptrA = A.indptr + cdef int rowsA = A.shape[0] + cdef int colsA = A.shape[1] + + cdef complex[::1] dataB = B.data + cdef int[::1] indsB = B.indices + cdef int[::1] indptrB = B.indptr + cdef int rowsB = B.shape[0] + cdef int colsB = B.shape[1] + + cdef int out_nnz = _safe_multiply(dataA.shape[0], dataB.shape[0]) + cdef int rows_out = rowsA * rowsB + cdef int cols_out = colsA * colsB + + cdef CSR_Matrix out + init_CSR(&out, out_nnz, rows_out, cols_out) + + _zcsr_kron_core(&dataA[0], &indsA[0], &indptrA[0], + &dataB[0], &indsB[0], &indptrB[0], + &out, + rowsA, rowsB, colsB) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_kron(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C): + """ + Computes the kronecker product between two complex + sparse matrices in CSR format. + """ + + cdef int out_nnz = _safe_multiply(A.nnz, B.nnz) + cdef int rows_out = A.nrows * B.nrows + cdef int cols_out = A.ncols * B.ncols + + init_CSR(C, out_nnz, rows_out, cols_out) + + _zcsr_kron_core(A.data, A.indices, A.indptr, + B.data, B.indices, B.indptr, + C, + A.nrows, B.nrows, B.ncols) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_kron_core(double complex * dataA, int * indsA, int * indptrA, + double complex * dataB, int * indsB, int * indptrB, + CSR_Matrix * out, + int rowsA, int rowsB, int colsB) nogil: + cdef size_t ii, jj, ptrA, ptr + cdef int row = 0 + cdef int ptr_start, ptr_end + cdef int row_startA, row_endA, row_startB, row_endB, distA, distB, ptrB + + for ii in range(rowsA): + row_startA = indptrA[ii] + row_endA = indptrA[ii+1] + distA = row_endA - row_startA + + for jj in range(rowsB): + row_startB = indptrB[jj] + row_endB = indptrB[jj+1] + distB = row_endB - row_startB + + ptr_start = out.indptr[row] + ptr_end = ptr_start + distB + + out.indptr[row+1] = out.indptr[row] + distA * distB + row += 1 + + for ptrA in range(row_startA, row_endA): + ptrB = row_startB + for ptr in range(ptr_start, ptr_end): + out.indices[ptr] = indsA[ptrA] * colsB + indsB[ptrB] + out.data[ptr] = dataA[ptrA] * dataB[ptrB] + ptrB += 1 + + ptr_start += distB + ptr_end += distB + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_transpose(object A): + """ + Transpose of a sparse matrix in CSR format. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + + cdef CSR_Matrix out + init_CSR(&out, data.shape[0], ncols, nrows) + + _zcsr_trans_core(&data[0], &ind[0], &ptr[0], + &out, nrows, ncols) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_transpose(CSR_Matrix * A, CSR_Matrix * B): + """ + Transpose of a sparse matrix in CSR format. + """ + init_CSR(B, A.nnz, A.ncols, A.nrows) + + _zcsr_trans_core(A.data, A.indices, A.indptr, B, A.nrows, A.ncols) + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_trans_core(double complex * data, int * ind, int * ptr, + CSR_Matrix * out, + int nrows, int ncols) nogil: + + cdef int k, nxt + cdef size_t ii, jj + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + 1 + out.indptr[k] += 1 + + for ii in range(ncols): + out.indptr[ii+1] += out.indptr[ii] + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + nxt = out.indptr[k] + out.data[nxt] = data[jj] + out.indices[nxt] = ii + out.indptr[k] = nxt + 1 + + for ii in range(ncols,0,-1): + out.indptr[ii] = out.indptr[ii-1] + + out.indptr[0] = 0 + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_adjoint(object A): + """ + Adjoint of a sparse matrix in CSR format. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + + cdef CSR_Matrix out + init_CSR(&out, data.shape[0], ncols, nrows) + + _zcsr_adjoint_core(&data[0], &ind[0], &ptr[0], + &out, nrows, ncols) + return CSR_to_scipy(&out) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_adjoint(CSR_Matrix * A, CSR_Matrix * B): + """ + Adjoint of a sparse matrix in CSR format. + """ + init_CSR(B, A.nnz, A.ncols, A.nrows) + + _zcsr_adjoint_core(A.data, A.indices, A.indptr, + B, A.nrows, A.ncols) + + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef void _zcsr_adjoint_core(double complex * data, int * ind, int * ptr, + CSR_Matrix * out, + int nrows, int ncols) nogil: + + cdef int k, nxt + cdef size_t ii, jj + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + 1 + out.indptr[k] += 1 + + for ii in range(ncols): + out.indptr[ii+1] += out.indptr[ii] + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + nxt = out.indptr[k] + out.data[nxt] = conj(data[jj]) + out.indices[nxt] = ii + out.indptr[k] = nxt + 1 + + for ii in range(ncols,0,-1): + out.indptr[ii] = out.indptr[ii-1] + + out.indptr[0] = 0 + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_isherm(object A not None, double tol = qset.atol): + """ + Determines if a given input sparse CSR matrix is Hermitian + to within a specified floating-point tolerance. + + Parameters + ---------- + A : csr_matrix + Input sparse matrix. + tol : float (default is atol from settings) + Desired tolerance value. + + Returns + ------- + isherm : int + One if matrix is Hermitian, zero otherwise. + + Notes + ----- + This implimentation is esentially an adjoint calulation + where the data and indices are not stored, but checked + elementwise to see if they match those of the input matrix. + Thus we do not need to build the actual adjoint. Here we + only need a temp array of output indptr. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = A.shape[0] + cdef int ncols = A.shape[1] + + cdef int k, nxt, isherm = 1 + cdef size_t ii, jj + cdef complex tmp, tmp2 + + if nrows != ncols: + return 0 + + cdef int * out_ptr = PyDataMem_NEW_ZEROED(ncols+1, sizeof(int)) + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + 1 + out_ptr[k] += 1 + + for ii in range(nrows): + out_ptr[ii+1] += out_ptr[ii] + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + k = ind[jj] + nxt = out_ptr[k] + out_ptr[k] += 1 + #structure test + if ind[nxt] != ii: + isherm = 0 + break + tmp = conj(data[jj]) + tmp2 = data[nxt] + #data test + if abs(tmp-tmp2) > tol: + isherm = 0 + break + else: + continue + break + + PyDataMem_FREE(out_ptr) + return isherm + +@cython.overflowcheck(True) +cdef _safe_multiply(int A, int B): + """ + Computes A*B and checks for overflow. + """ + cdef int C = A*B + return C + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_trace(object A, bool isherm): + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows = ptr.shape[0]-1 + cdef size_t ii, jj + cdef complex tr = 0 + + for ii in range(nrows): + for jj in range(ptr[ii], ptr[ii+1]): + if ind[jj] == ii: + tr += data[jj] + break + if imag(tr) == 0 or isherm: + return real(tr) + else: + return tr + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_proj(object A, bool is_ket=1): + """ + Computes the projection operator + from a given ket or bra vector + in CSR format. The flag 'is_ket' + is True if passed a ket. + + This is ~3x faster than doing the + conjugate transpose and sparse multiplication + directly. Also, does not need a temp matrix. + """ + cdef complex[::1] data = A.data + cdef int[::1] ind = A.indices + cdef int[::1] ptr = A.indptr + cdef int nrows + cdef int nnz + + cdef int offset = 0, new_idx, count, change_idx + cdef size_t jj, kk + + if is_ket: + nrows = A.shape[0] + nnz = ptr[nrows] + else: + nrows = A.shape[1] + nnz = ptr[1] + + cdef CSR_Matrix out + init_CSR(&out, nnz**2, nrows) + + if is_ket: + #Compute new ptrs and inds + for jj in range(nrows): + out.indptr[jj] = ptr[jj]*nnz + if ptr[jj+1] != ptr[jj]: + new_idx = jj + for kk in range(nnz): + out.indices[offset+kk*nnz] = new_idx + offset += 1 + #set nnz in new ptr + out.indptr[nrows] = nnz**2 + + #Compute the data + for jj in range(nnz): + for kk in range(nnz): + out.data[jj*nnz+kk] = data[jj]*conj(data[kk]) + + else: + count = nnz**2 + new_idx = nrows + for kk in range(nnz-1,-1,-1): + for jj in range(nnz-1,-1,-1): + out.indices[offset+jj] = ind[jj] + out.data[kk*nnz+jj] = conj(data[kk])*data[jj] + offset += nnz + change_idx = ind[kk] + while new_idx > change_idx: + out.indptr[new_idx] = count + new_idx -= 1 + count -= nnz + + + return CSR_to_scipy(&out) + + + +@cython.boundscheck(False) +@cython.wraparound(False) +def zcsr_inner(object A, object B, bool bra_ket): + """ + Computes the inner-product between ket-ket, + or bra-ket vectors in sparse CSR format. + """ + cdef complex[::1] a_data = A.data + cdef int[::1] a_ind = A.indices + cdef int[::1] a_ptr = A.indptr + + cdef complex[::1] b_data = B.data + cdef int[::1] b_ind = B.indices + cdef int[::1] b_ptr = B.indptr + cdef int nrows = B.shape[0] + + cdef double complex inner = 0 + cdef size_t jj, kk + cdef int a_idx, b_idx + + if bra_ket: + for kk in range(a_ind.shape[0]): + a_idx = a_ind[kk] + for jj in range(nrows): + if (b_ptr[jj+1]-b_ptr[jj]) != 0: + if jj == a_idx: + inner += a_data[kk]*b_data[b_ptr[jj]] + break + else: + for kk in range(nrows): + a_idx = a_ptr[kk] + b_idx = b_ptr[kk] + if (a_ptr[kk+1]-a_idx) != 0: + if (b_ptr[kk+1]-b_idx) != 0: + inner += conj(a_data[a_idx])*b_data[b_idx] + + return inner diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp b/qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp new file mode 100755 index 0000000000..bf84d7ed72 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp @@ -0,0 +1,172 @@ +// This file is part of QuTiP: Quantum Toolbox in Python. +// +// Copyright (c) 2011 and later, QuSTaR. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +// of its contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//############################################################################# +#include + +#if defined(__GNUC__) && defined(__SSE3__) // Using GCC or CLANG and SSE3 +#include +void zspmvpy(const std::complex * __restrict__ data, const int * __restrict__ ind, + const int * __restrict__ ptr, + const std::complex * __restrict__ vec, const std::complex a, + std::complex * __restrict__ out, const unsigned int nrows) +{ + size_t row, jj; + unsigned int row_start, row_end; + __m128d num1, num2, num3, num4; + for (row=0; row < nrows; row++) + { + num4 = _mm_setzero_pd(); + row_start = ptr[row]; + row_end = ptr[row+1]; + for (jj=row_start; jj (data[jj])[0]); + num2 = _mm_set_pd(std::imag(vec[ind[jj]]),std::real(vec[ind[jj]])); + num3 = _mm_mul_pd(num2, num1); + num1 = _mm_loaddup_pd(&reinterpret_cast(data[jj])[1]); + num2 = _mm_shuffle_pd(num2, num2, 1); + num2 = _mm_mul_pd(num2, num1); + num3 = _mm_addsub_pd(num3, num2); + num4 = _mm_add_pd(num3, num4); + } + num1 = _mm_loaddup_pd(&reinterpret_cast(a)[0]); + num3 = _mm_mul_pd(num4, num1); + num1 = _mm_loaddup_pd(&reinterpret_cast(a)[1]); + num4 = _mm_shuffle_pd(num4, num4, 1); + num4 = _mm_mul_pd(num4, num1); + num3 = _mm_addsub_pd(num3, num4); + num2 = _mm_loadu_pd((double *)&out[row]); + num3 = _mm_add_pd(num2, num3); + _mm_storeu_pd((double *)&out[row], num3); + } +} +#elif defined(__GNUC__) // Using GCC or CLANG but no SSE3 +void zspmvpy(const std::complex * __restrict__ data, const int * __restrict__ ind, + const int * __restrict__ ptr, + const std::complex * __restrict__ vec, const std::complex a, + std::complex * __restrict__ out, const unsigned int nrows) +{ + size_t row, jj; + unsigned int row_start, row_end; + std::complex dot; + for (row=0; row < nrows; row++) + { + dot = 0; + row_start = ptr[row]; + row_end = ptr[row+1]; + for (jj=row_start; jj +void zspmvpy(const std::complex * __restrict data, const int * __restrict ind, + const int * __restrict ptr, + const std::complex * __restrict vec, const std::complex a, + std::complex * __restrict out, const unsigned int nrows) +{ + size_t row, jj; + unsigned int row_start, row_end; + __m128d num1, num2, num3, num4; + for (row=0; row < nrows; row++) + { + num4 = _mm_setzero_pd(); + row_start = ptr[row]; + row_end = ptr[row+1]; + for (jj=row_start; jj (data[jj])[0]); + num2 = _mm_set_pd(std::imag(vec[ind[jj]]),std::real(vec[ind[jj]])); + num3 = _mm_mul_pd(num2, num1); + num1 = _mm_loaddup_pd(&reinterpret_cast(data[jj])[1]); + num2 = _mm_shuffle_pd(num2, num2, 1); + num2 = _mm_mul_pd(num2, num1); + num3 = _mm_addsub_pd(num3, num2); + num4 = _mm_add_pd(num3, num4); + } + num1 = _mm_loaddup_pd(&reinterpret_cast(a)[0]); + num3 = _mm_mul_pd(num4, num1); + num1 = _mm_loaddup_pd(&reinterpret_cast(a)[1]); + num4 = _mm_shuffle_pd(num4, num4, 1); + num4 = _mm_mul_pd(num4, num1); + num3 = _mm_addsub_pd(num3, num4); + num2 = _mm_loadu_pd((double *)&out[row]); + num3 = _mm_add_pd(num2, num3); + _mm_storeu_pd((double *)&out[row], num3); + } +} +#elif defined(_MSC_VER) // Visual Studio no AVX +void zspmvpy(const std::complex * __restrict data, const int * __restrict ind, + const int * __restrict ptr, + const std::complex * __restrict vec, const std::complex a, + std::complex * __restrict out, const unsigned int nrows) +{ + size_t row, jj; + unsigned int row_start, row_end; + std::complex dot; + for (row=0; row < nrows; row++) + { + dot = 0; + row_start = ptr[row]; + row_end = ptr[row+1]; + for (jj=row_start; jj * data, const int * ind, + const int * ptr, + const std::complex * vec, const std::complex a, + std::complex * out, const unsigned int nrows) +{ + size_t row, jj; + unsigned int row_start, row_end; + std::complex dot; + for (row=0; row < nrows; row++) + { + dot = 0; + row_start = ptr[row]; + row_end = ptr[row+1]; + for (jj=row_start; jj + +#ifdef __GNUC__ +void zspmvpy(const std::complex * __restrict__ data, const int * __restrict__ ind, + const int *__restrict__ ptr, + const std::complex * __restrict__ vec, const std::complex a, + std::complex * __restrict__ out, + const unsigned int nrows); +#elif defined(_MSC_VER) +void zspmvpy(const std::complex * __restrict data, const int * __restrict ind, + const int *__restrict ptr, + const std::complex * __restrict vec, const std::complex a, + std::complex * __restrict out, + const unsigned int nrows); +#else +void zspmvpy(const std::complex * data, const int * ind, + const int * ptr, + const std::complex * vec, const std::complex a, + std::complex * out, + const unsigned int nrows); +#endif \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py new file mode 100755 index 0000000000..4cc23b7002 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py @@ -0,0 +1,53 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import os + +def _cython_build_cleanup(tdname, build_dir=None): + if build_dir is None: + build_dir = os.path.join(os.path.expanduser('~'), '.pyxbld') + + # Remove tdname.pyx + pyx_file = tdname + ".pyx" + try: + os.remove(pyx_file) + except: + pass + + # Remove temp build files + for dirpath, subdirs, files in os.walk(build_dir): + for f in files: + if f.startswith(tdname): + try: + os.remove(os.path.join(dirpath,f)) + except: + pass diff --git a/qiskit/providers/aer/openpulse/qutip_lite/expect.py b/qiskit/providers/aer/openpulse/qutip_lite/expect.py new file mode 100755 index 0000000000..60f4091cfc --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/expect.py @@ -0,0 +1,174 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +__all__ = ['expect', 'variance'] + +import numpy as np +import scipy.sparse as sp + +from qutip.qobj import Qobj, isoper +from qutip.eseries import eseries +from qutip.cy.spmatfuncs import (cy_expect_rho_vec, cy_expect_psi, cy_spmm_tr, + expect_csr_ket) + + +expect_rho_vec = cy_expect_rho_vec +expect_psi = cy_expect_psi + + +def expect(oper, state): + '''Calculates the expectation value for operator(s) and state(s). + + Parameters + ---------- + oper : qobj/array-like + A single or a `list` or operators for expectation value. + + state : qobj/array-like + A single or a `list` of quantum states or density matrices. + + Returns + ------- + expt : float/complex/array-like + Expectation value. ``real`` if `oper` is Hermitian, ``complex`` + otherwise. A (nested) array of expectaction values of state or operator + are arrays. + + Examples + -------- + >>> expect(num(4), basis(4, 3)) + 3 + + ''' + if isinstance(state, Qobj) and isinstance(oper, Qobj): + return _single_qobj_expect(oper, state) + + elif isinstance(oper, Qobj) and isinstance(state, eseries): + return _single_eseries_expect(oper, state) + + elif isinstance(oper, (list, np.ndarray)): + if isinstance(state, Qobj): + if (all([op.isherm for op in oper]) and + (state.isket or state.isherm)): + return np.array([_single_qobj_expect(o, state) for o in oper]) + else: + return np.array([_single_qobj_expect(o, state) for o in oper], + dtype=complex) + else: + return [expect(o, state) for o in oper] + + elif isinstance(state, (list, np.ndarray)): + if oper.isherm and all([(op.isherm or op.type == 'ket') + for op in state]): + return np.array([_single_qobj_expect(oper, x) for x in state]) + else: + return np.array([_single_qobj_expect(oper, x) for x in state], + dtype=complex) + else: + raise TypeError('Arguments must be quantum objects or eseries') + + +def _single_qobj_expect(oper, state): + """ + Private function used by expect to calculate expectation values of Qobjs. + """ + if isoper(oper): + if oper.dims[1] != state.dims[0]: + raise Exception('Operator and state do not have same tensor ' + + 'structure: %s and %s' % + (oper.dims[1], state.dims[0])) + + if state.type == 'oper': + # calculates expectation value via TR(op*rho) + return cy_spmm_tr(oper.data, state.data, + oper.isherm and state.isherm) + + elif state.type == 'ket': + # calculates expectation value via + return expect_csr_ket(oper.data, state.data, + oper.isherm) + else: + raise TypeError('Invalid operand types') + + +def _single_eseries_expect(oper, state): + """ + Private function used by expect to calculate expectation values for + eseries. + """ + + out = eseries() + + if isoper(state.ampl[0]): + out.rates = state.rates + out.ampl = np.array([expect(oper, a) for a in state.ampl]) + + else: + out.rates = np.array([]) + out.ampl = np.array([]) + + for m in range(len(state.rates)): + op_m = state.ampl[m].data.conj().T * oper.data + + for n in range(len(state.rates)): + a = op_m * state.ampl[n].data + + if isinstance(a, sp.spmatrix): + a = a.todense() + + out.rates = np.append(out.rates, state.rates[n] - + state.rates[m]) + out.ampl = np.append(out.ampl, a) + + return out + + +def variance(oper, state): + """ + Variance of an operator for the given state vector or density matrix. + + Parameters + ---------- + oper : qobj + Operator for expectation value. + + state : qobj/list + A single or `list` of quantum states or density matrices.. + + Returns + ------- + var : float + Variance of operator 'oper' for given state. + + """ + return expect(oper ** 2, state) - expect(oper, state) ** 2 diff --git a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py new file mode 100755 index 0000000000..23833797ff --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py @@ -0,0 +1,406 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, The QuTiP Project. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +import numpy as np +import operator +from scipy.sparse import (_sparsetools, isspmatrix, isspmatrix_csr, + csr_matrix, coo_matrix, csc_matrix, dia_matrix) +from scipy.sparse.sputils import (upcast, upcast_char, to_native, isdense, isshape, + getdtype, isscalarlike, get_index_dtype) +from scipy.sparse.base import spmatrix, isspmatrix, SparseEfficiencyWarning +from warnings import warn + +class fast_csr_matrix(csr_matrix): + """ + A subclass of scipy.sparse.csr_matrix that skips the data format + checks that are run everytime a new csr_matrix is created. + """ + def __init__(self, args=None, shape=None, dtype=None, copy=False): + if args is None: #Build zero matrix + if shape is None: + raise Exception('Shape must be given when building zero matrix.') + self.data = np.array([], dtype=complex) + self.indices = np.array([], dtype=np.int32) + self.indptr = np.zeros(shape[0]+1, dtype=np.int32) + self._shape = tuple(int(s) for s in shape) + + else: + if args[0].shape[0] and args[0].dtype != complex: + raise TypeError('fast_csr_matrix allows only complex data.') + if args[1].shape[0] and args[1].dtype != np.int32: + raise TypeError('fast_csr_matrix allows only int32 indices.') + if args[2].shape[0] and args[1].dtype != np.int32: + raise TypeError('fast_csr_matrix allows only int32 indptr.') + self.data = np.array(args[0], dtype=complex, copy=copy) + self.indices = np.array(args[1], dtype=np.int32, copy=copy) + self.indptr = np.array(args[2], dtype=np.int32, copy=copy) + if shape is None: + self._shape = tuple([len(self.indptr)-1]*2) + else: + self._shape = tuple(int(s) for s in shape) + self.dtype = complex + self.maxprint = 50 + self.format = 'csr' + + def _binopt(self, other, op): + """ + Do the binary operation fn to two sparse matrices using + fast_csr_matrix only when other is also a fast_csr_matrix. + """ + # e.g. csr_plus_csr, csr_minus_csr, etc. + if not isinstance(other, fast_csr_matrix): + other = csr_matrix(other) + # e.g. csr_plus_csr, csr_minus_csr, etc. + fn = getattr(_sparsetools, self.format + op + self.format) + + maxnnz = self.nnz + other.nnz + idx_dtype = get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=maxnnz) + indptr = np.empty(self.indptr.shape, dtype=idx_dtype) + indices = np.empty(maxnnz, dtype=idx_dtype) + + bool_ops = ['_ne_', '_lt_', '_gt_', '_le_', '_ge_'] + if op in bool_ops: + data = np.empty(maxnnz, dtype=np.bool_) + else: + data = np.empty(maxnnz, dtype=upcast(self.dtype, other.dtype)) + + fn(self.shape[0], self.shape[1], + np.asarray(self.indptr, dtype=idx_dtype), + np.asarray(self.indices, dtype=idx_dtype), + self.data, + np.asarray(other.indptr, dtype=idx_dtype), + np.asarray(other.indices, dtype=idx_dtype), + other.data, + indptr, indices, data) + + actual_nnz = indptr[-1] + indices = indices[:actual_nnz] + data = data[:actual_nnz] + if actual_nnz < maxnnz // 2: + # too much waste, trim arrays + indices = indices.copy() + data = data.copy() + if isinstance(other, fast_csr_matrix) and (not op in bool_ops): + A = fast_csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) + else: + A = csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) + return A + + def multiply(self, other): + """Point-wise multiplication by another matrix, vector, or + scalar. + """ + # Scalar multiplication. + if isscalarlike(other): + return self._mul_scalar(other) + # Sparse matrix or vector. + if isspmatrix(other): + if self.shape == other.shape: + if not isinstance(other, fast_csr_matrix): + other = csr_matrix(other) + return self._binopt(other, '_elmul_') + # Single element. + elif other.shape == (1,1): + return self._mul_scalar(other.toarray()[0, 0]) + elif self.shape == (1,1): + return other._mul_scalar(self.toarray()[0, 0]) + # A row times a column. + elif self.shape[1] == other.shape[0] and self.shape[1] == 1: + return self._mul_sparse_matrix(other.tocsc()) + elif self.shape[0] == other.shape[1] and self.shape[0] == 1: + return other._mul_sparse_matrix(self.tocsc()) + # Row vector times matrix. other is a row. + elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: + other = dia_matrix((other.toarray().ravel(), [0]), + shape=(other.shape[1], other.shape[1])) + return self._mul_sparse_matrix(other) + # self is a row. + elif self.shape[0] == 1 and self.shape[1] == other.shape[1]: + copy = dia_matrix((self.toarray().ravel(), [0]), + shape=(self.shape[1], self.shape[1])) + return other._mul_sparse_matrix(copy) + # Column vector times matrix. other is a column. + elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: + other = dia_matrix((other.toarray().ravel(), [0]), + shape=(other.shape[0], other.shape[0])) + return other._mul_sparse_matrix(self) + # self is a column. + elif self.shape[1] == 1 and self.shape[0] == other.shape[0]: + copy = dia_matrix((self.toarray().ravel(), [0]), + shape=(self.shape[0], self.shape[0])) + return copy._mul_sparse_matrix(other) + else: + raise ValueError("inconsistent shapes") + # Dense matrix. + if isdense(other): + if self.shape == other.shape: + ret = self.tocoo() + ret.data = np.multiply(ret.data, other[ret.row, ret.col] + ).view(np.ndarray).ravel() + return ret + # Single element. + elif other.size == 1: + return self._mul_scalar(other.flat[0]) + # Anything else. + return np.multiply(self.todense(), other) + + def _mul_sparse_matrix(self, other): + """ + Do the sparse matrix mult returning fast_csr_matrix only + when other is also fast_csr_matrix. + """ + M, K1 = self.shape + K2, N = other.shape + + major_axis = self._swap((M,N))[0] + if isinstance(other, fast_csr_matrix): + A = zcsr_mult(self, other, sorted=1) + return A + + other = csr_matrix(other) # convert to this format + idx_dtype = get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=M*N) + indptr = np.empty(major_axis + 1, dtype=idx_dtype) + + fn = getattr(_sparsetools, self.format + '_matmat_pass1') + fn(M, N, + np.asarray(self.indptr, dtype=idx_dtype), + np.asarray(self.indices, dtype=idx_dtype), + np.asarray(other.indptr, dtype=idx_dtype), + np.asarray(other.indices, dtype=idx_dtype), + indptr) + + nnz = indptr[-1] + idx_dtype = get_index_dtype((self.indptr, self.indices, + other.indptr, other.indices), + maxval=nnz) + indptr = np.asarray(indptr, dtype=idx_dtype) + indices = np.empty(nnz, dtype=idx_dtype) + data = np.empty(nnz, dtype=upcast(self.dtype, other.dtype)) + + fn = getattr(_sparsetools, self.format + '_matmat_pass2') + fn(M, N, np.asarray(self.indptr, dtype=idx_dtype), + np.asarray(self.indices, dtype=idx_dtype), + self.data, + np.asarray(other.indptr, dtype=idx_dtype), + np.asarray(other.indices, dtype=idx_dtype), + other.data, + indptr, indices, data) + A = csr_matrix((data,indices,indptr),shape=(M,N)) + return A + + def _scalar_binopt(self, other, op): + """Scalar version of self._binopt, for cases in which no new nonzeros + are added. Produces a new spmatrix in canonical form. + """ + self.sum_duplicates() + res = self._with_data(op(self.data, other), copy=True) + res.eliminate_zeros() + return res + + def __eq__(self, other): + # Scalar other. + if isscalarlike(other): + if np.isnan(other): + return csr_matrix(self.shape, dtype=np.bool_) + + if other == 0: + warn("Comparing a sparse matrix with 0 using == is inefficient" + ", try using != instead.", SparseEfficiencyWarning) + all_true = _all_true(self.shape) + inv = self._scalar_binopt(other, operator.ne) + return all_true - inv + else: + return self._scalar_binopt(other, operator.eq) + # Dense other. + elif isdense(other): + return self.todense() == other + # Sparse other. + elif isspmatrix(other): + warn("Comparing sparse matrices using == is inefficient, try using" + " != instead.", SparseEfficiencyWarning) + #TODO sparse broadcasting + if self.shape != other.shape: + return False + elif self.format != other.format: + other = other.asformat(self.format) + res = self._binopt(other,'_ne_') + all_true = _all_true(self.shape) + return all_true - res + else: + return False + + def __ne__(self, other): + # Scalar other. + if isscalarlike(other): + if np.isnan(other): + warn("Comparing a sparse matrix with nan using != is inefficient", + SparseEfficiencyWarning) + all_true = _all_true(self.shape) + return all_true + elif other != 0: + warn("Comparing a sparse matrix with a nonzero scalar using !=" + " is inefficient, try using == instead.", SparseEfficiencyWarning) + all_true = _all_true(self.shape) + inv = self._scalar_binopt(other, operator.eq) + return all_true - inv + else: + return self._scalar_binopt(other, operator.ne) + # Dense other. + elif isdense(other): + return self.todense() != other + # Sparse other. + elif isspmatrix(other): + #TODO sparse broadcasting + if self.shape != other.shape: + return True + elif self.format != other.format: + other = other.asformat(self.format) + return self._binopt(other,'_ne_') + else: + return True + + def _inequality(self, other, op, op_name, bad_scalar_msg): + # Scalar other. + if isscalarlike(other): + if 0 == other and op_name in ('_le_', '_ge_'): + raise NotImplementedError(" >= and <= don't work with 0.") + elif op(0, other): + warn(bad_scalar_msg, SparseEfficiencyWarning) + other_arr = np.empty(self.shape, dtype=np.result_type(other)) + other_arr.fill(other) + other_arr = csr_matrix(other_arr) + return self._binopt(other_arr, op_name) + else: + return self._scalar_binopt(other, op) + # Dense other. + elif isdense(other): + return op(self.todense(), other) + # Sparse other. + elif isspmatrix(other): + #TODO sparse broadcasting + if self.shape != other.shape: + raise ValueError("inconsistent shapes") + elif self.format != other.format: + other = other.asformat(self.format) + if op_name not in ('_ge_', '_le_'): + return self._binopt(other, op_name) + + warn("Comparing sparse matrices using >= and <= is inefficient, " + "using <, >, or !=, instead.", SparseEfficiencyWarning) + all_true = _all_true(self.shape) + res = self._binopt(other, '_gt_' if op_name == '_le_' else '_lt_') + return all_true - res + else: + raise ValueError("Operands could not be compared.") + + def _with_data(self,data,copy=True): + """Returns a matrix with the same sparsity structure as self, + but with different data. By default the structure arrays + (i.e. .indptr and .indices) are copied. + """ + # We need this just in case something like abs(data) gets called + # does nothing if data.dtype is complex. + data = np.asarray(data, dtype=complex) + if copy: + return fast_csr_matrix((data,self.indices.copy(),self.indptr.copy()), + shape=self.shape,dtype=data.dtype) + else: + return fast_csr_matrix((data,self.indices,self.indptr), + shape=self.shape,dtype=data.dtype) + + def transpose(self): + """ + Returns the transpose of the matrix, keeping + it in fast_csr format. + """ + return zcsr_transpose(self) + + def trans(self): + """ + Same as transpose + """ + return zcsr_transpose(self) + + def getH(self): + """ + Returns the conjugate-transpose of the matrix, keeping + it in fast_csr format. + """ + return zcsr_adjoint(self) + + def adjoint(self): + """ + Same as getH + """ + return zcsr_adjoint(self) + + +def csr2fast(A, copy=False): + if (not isinstance(A, fast_csr_matrix)) or copy: + # Do not need to do any type checking here + # since fast_csr_matrix does that. + return fast_csr_matrix((A.data,A.indices,A.indptr), + shape=A.shape,copy=copy) + else: + return A + + +def fast_identity(N): + """Generates a sparse identity matrix in + fast_csr format. + """ + data = np.ones(N, dtype=complex) + ind = np.arange(N, dtype=np.int32) + ptr = np.arange(N+1, dtype=np.int32) + ptr[-1] = N + return fast_csr_matrix((data,ind,ptr),shape=(N,N)) + + + +#Convenience functions +#-------------------- +def _all_true(shape): + A = csr_matrix((np.ones(np.prod(shape), dtype=np.bool_), + np.tile(np.arange(shape[1],dtype=np.int32),shape[0]), + np.arange(0,np.prod(shape)+1,shape[1],dtype=np.int32)), + shape=shape) + return A + + + +#Need to do some trailing imports here +#------------------------------------- +from qutip.cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_mult) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/graph.py b/qiskit/providers/aer/openpulse/qutip_lite/graph.py new file mode 100755 index 0000000000..bac840c452 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/graph.py @@ -0,0 +1,295 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +This module contains a collection of graph theory routines used mainly +to reorder matrices for iterative steady state solvers. +""" + +__all__ = ['graph_degree', 'column_permutation', 'breadth_first_search', + 'reverse_cuthill_mckee', 'maximum_bipartite_matching', + 'weighted_bipartite_matching'] + +import numpy as np +import scipy.sparse as sp +from qutip.cy.graph_utils import ( + _breadth_first_search, _node_degrees, + _reverse_cuthill_mckee, _maximum_bipartite_matching, + _weighted_bipartite_matching) + + +def graph_degree(A): + """ + Returns the degree for the nodes (rows) of a symmetric + graph in sparse CSR or CSC format, or a qobj. + + Parameters + ---------- + A : qobj, csr_matrix, csc_matrix + Input quantum object or csr_matrix. + + Returns + ------- + degree : array + Array of integers giving the degree for each node (row). + + """ + if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): + raise TypeError('Input must be CSC or CSR sparse matrix.') + return np.asarray(_node_degrees(A.indices, A.indptr, A.shape[0])) + + +def breadth_first_search(A, start): + """ + Breadth-First-Search (BFS) of a graph in CSR or CSC matrix format starting + from a given node (row). Takes Qobjs and CSR or CSC matrices as inputs. + + This function requires a matrix with symmetric structure. + Use A+trans(A) if original matrix is not symmetric or not sure. + + Parameters + ---------- + A : csc_matrix, csr_matrix + Input graph in CSC or CSR matrix format + start : int + Staring node for BFS traversal. + + Returns + ------- + order : array + Order in which nodes are traversed from starting node. + levels : array + Level of the nodes in the order that they are traversed. + + """ + if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): + raise TypeError('Input must be CSC or CSR sparse matrix.') + + num_rows = A.shape[0] + start = int(start) + order, levels = _breadth_first_search(A.indices, A.indptr, num_rows, start) + # since maybe not all nodes are in search, check for unused entires in + # arrays + return order[order != -1], levels[levels != -1] + + +def column_permutation(A): + """ + Finds the non-symmetric column permutation of A such that the columns + are given in ascending order according to the number of nonzero entries. + This is sometimes useful for decreasing the fill-in of sparse LU + factorization. + + Parameters + ---------- + A : csc_matrix + Input sparse CSC sparse matrix. + + Returns + ------- + perm : array + Array of permuted row and column indices. + + """ + if not sp.isspmatrix_csc(A): + A = sp.csc_matrix(A) + count = np.diff(A.indptr) + perm = np.argsort(count) + return perm + + +def reverse_cuthill_mckee(A, sym=False): + """ + Returns the permutation array that orders a sparse CSR or CSC matrix + in Reverse-Cuthill McKee ordering. Since the input matrix must be + symmetric, this routine works on the matrix A+Trans(A) if the sym flag is + set to False (Default). + + It is assumed by default (*sym=False*) that the input matrix is not + symmetric. This is because it is faster to do A+Trans(A) than it is to + check for symmetry for a generic matrix. If you are guaranteed that the + matrix is symmetric in structure (values of matrix element do not matter) + then set *sym=True* + + Parameters + ---------- + A : csc_matrix, csr_matrix + Input sparse CSC or CSR sparse matrix format. + sym : bool {False, True} + Flag to set whether input matrix is symmetric. + + Returns + ------- + perm : array + Array of permuted row and column indices. + + Notes + ----- + This routine is used primarily for internal reordering of Lindblad + superoperators for use in iterative solver routines. + + References + ---------- + E. Cuthill and J. McKee, "Reducing the Bandwidth of Sparse Symmetric + Matrices", ACM '69 Proceedings of the 1969 24th national conference, + (1969). + + """ + if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): + raise TypeError('Input must be CSC or CSR sparse matrix.') + + nrows = A.shape[0] + + if not sym: + A = A + A.transpose() + + return _reverse_cuthill_mckee(A.indices, A.indptr, nrows) + + +def maximum_bipartite_matching(A, perm_type='row'): + """ + Returns an array of row or column permutations that removes nonzero + elements from the diagonal of a nonsingular square CSC sparse matrix. Such + a permutation is always possible provided that the matrix is nonsingular. + This function looks at the structure of the matrix only. + + The input matrix will be converted to CSC matrix format if + necessary. + + Parameters + ---------- + A : sparse matrix + Input matrix + + perm_type : str {'row', 'column'} + Type of permutation to generate. + + Returns + ------- + perm : array + Array of row or column permutations. + + Notes + ----- + This function relies on a maximum cardinality bipartite matching algorithm + based on a breadth-first search (BFS) of the underlying graph[1]_. + + References + ---------- + I. S. Duff, K. Kaya, and B. Ucar, "Design, Implementation, and + Analysis of Maximum Transversal Algorithms", ACM Trans. Math. Softw. + 38, no. 2, (2011). + + """ + nrows = A.shape[0] + if A.shape[0] != A.shape[1]: + raise ValueError( + 'Maximum bipartite matching requires a square matrix.') + + if sp.isspmatrix_csr(A) or sp.isspmatrix_coo(A): + A = A.tocsc() + elif not sp.isspmatrix_csc(A): + raise TypeError("matrix must be in CSC, CSR, or COO format.") + + if perm_type == 'column': + A = A.transpose().tocsc() + + perm = _maximum_bipartite_matching(A.indices, A.indptr, nrows) + + if np.any(perm == -1): + raise Exception('Possibly singular input matrix.') + + return perm + + +def weighted_bipartite_matching(A, perm_type='row'): + """ + Returns an array of row permutations that attempts to maximize + the product of the ABS values of the diagonal elements in + a nonsingular square CSC sparse matrix. Such a permutation is + always possible provided that the matrix is nonsingular. + + This function looks at both the structure and ABS values of the + underlying matrix. + + Parameters + ---------- + A : csc_matrix + Input matrix + + perm_type : str {'row', 'column'} + Type of permutation to generate. + + Returns + ------- + perm : array + Array of row or column permutations. + + Notes + ----- + This function uses a weighted maximum cardinality bipartite matching + algorithm based on breadth-first search (BFS). The columns are weighted + according to the element of max ABS value in the associated rows and + are traversed in descending order by weight. When performing the BFS + traversal, the row associated to a given column is the one with maximum + weight. Unlike other techniques[1]_, this algorithm does not guarantee the + product of the diagonal is maximized. However, this limitation is offset + by the substantially faster runtime of this method. + + References + ---------- + I. S. Duff and J. Koster, "The design and use of algorithms for + permuting large entries to the diagonal of sparse matrices", SIAM J. + Matrix Anal. and Applics. 20, no. 4, 889 (1997). + + """ + + nrows = A.shape[0] + if A.shape[0] != A.shape[1]: + raise ValueError('weighted_bfs_matching requires a square matrix.') + + if sp.isspmatrix_csr(A) or sp.isspmatrix_coo(A): + A = A.tocsc() + elif not sp.isspmatrix_csc(A): + raise TypeError("matrix must be in CSC, CSR, or COO format.") + + if perm_type == 'column': + A = A.transpose().tocsc() + + perm = _weighted_bipartite_matching( + np.asarray(np.abs(A.data), dtype=float), + A.indices, A.indptr, nrows) + + if np.any(perm == -1): + raise Exception('Possibly singular input matrix.') + + return perm diff --git a/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py b/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py new file mode 100755 index 0000000000..02d10ac009 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py @@ -0,0 +1,135 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +__all__ = ['hardware_info'] + +import os +import sys +import multiprocessing +import numpy as np + +def _mac_hardware_info(): + info = dict() + results = dict() + for l in [l.split(':') for l in os.popen('sysctl hw').readlines()[1:]]: + info[l[0].strip(' "').replace(' ', '_').lower().strip('hw.')] = \ + l[1].strip('.\n ') + results.update({'cpus': int(info['physicalcpu'])}) + results.update({'cpu_freq': int(float(os.popen('sysctl -n machdep.cpu.brand_string') + .readlines()[0].split('@')[1][:-4])*1000)}) + results.update({'memsize': int(int(info['memsize']) / (1024 ** 2))}) + # add OS information + results.update({'os': 'Mac OSX'}) + return results + + +def _linux_hardware_info(): + results = {} + # get cpu number + sockets = 0 + cores_per_socket = 0 + frequency = 0.0 + for l in [l.split(':') for l in open("/proc/cpuinfo").readlines()]: + if (l[0].strip() == "physical id"): + sockets = np.maximum(sockets,int(l[1].strip())+1) + if (l[0].strip() == "cpu cores"): + cores_per_socket = int(l[1].strip()) + if (l[0].strip() == "cpu MHz"): + frequency = float(l[1].strip()) / 1000. + results.update({'cpus': sockets * cores_per_socket}) + # get cpu frequency directly (bypasses freq scaling) + try: + file = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" + line = open(file).readlines()[0] + frequency = float(line.strip('\n')) / 1000000. + except: + pass + results.update({'cpu_freq': frequency}) + + # get total amount of memory + mem_info = dict() + for l in [l.split(':') for l in open("/proc/meminfo").readlines()]: + mem_info[l[0]] = l[1].strip('.\n ').strip('kB') + results.update({'memsize': int(mem_info['MemTotal']) / 1024}) + # add OS information + results.update({'os': 'Linux'}) + return results + +def _freebsd_hardware_info(): + results = {} + results.update({'cpus': int(os.popen('sysctl -n hw.ncpu').readlines()[0])}) + results.update({'cpu_freq': int(os.popen('sysctl -n dev.cpu.0.freq').readlines()[0])}) + results.update({'memsize': int(os.popen('sysctl -n hw.realmem').readlines()[0]) / 1024}) + results.update({'os': 'FreeBSD'}) + return results + +def _win_hardware_info(): + try: + from comtypes.client import CoGetObject + winmgmts_root = CoGetObject("winmgmts:root\cimv2") + cpus = winmgmts_root.ExecQuery("Select * from Win32_Processor") + ncpus = 0 + for cpu in cpus: + ncpus += int(cpu.Properties_['NumberOfCores'].Value) + except: + ncpus = int(multiprocessing.cpu_count()) + return {'os': 'Windows', 'cpus': ncpus} + + +def hardware_info(): + """ + Returns basic hardware information about the computer. + + Gives actual number of CPU's in the machine, even when hyperthreading is + turned on. + + Returns + ------- + info : dict + Dictionary containing cpu and memory information. + + """ + if sys.platform == 'darwin': + out = _mac_hardware_info() + elif sys.platform == 'win32': + out = _win_hardware_info() + elif sys.platform in ['linux', 'linux2']: + out = _linux_hardware_info() + elif sys.platform.startswith('freebsd'): + out = _freebsd_hardware_info() + else: + out = {} + return out + +if __name__ == '__main__': + print(hardware_info()) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/operators.py b/qiskit/providers/aer/openpulse/qutip_lite/operators.py new file mode 100755 index 0000000000..dfeeac4886 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/operators.py @@ -0,0 +1,976 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +This module contains functions for generating Qobj representation of a variety +of commonly occuring quantum operators. +""" + +__all__ = ['jmat', 'spin_Jx', 'spin_Jy', 'spin_Jz', 'spin_Jm', 'spin_Jp', + 'spin_J_set', 'sigmap', 'sigmam', 'sigmax', 'sigmay', 'sigmaz', + 'destroy', 'create', 'qeye', 'identity', 'position', 'momentum', + 'num', 'squeeze', 'squeezing', 'displace', 'commutator', + 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'enr_destroy', + 'enr_identity', 'charge', 'tunneling'] + +import numpy as np +import scipy +import scipy.sparse as sp +from qutip.qobj import Qobj +from qutip.fastsparse import fast_csr_matrix, fast_identity + +# +# Spin operators +# +def jmat(j, *args): + """Higher-order spin operators: + + Parameters + ---------- + j : float + Spin of operator + + args : str + Which operator to return 'x','y','z','+','-'. + If no args given, then output is ['x','y','z'] + + Returns + ------- + jmat : qobj / ndarray + ``qobj`` for requested spin operator(s). + + + Examples + -------- + >>> jmat(1) + [ Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 0. 0.70710678 0. ] + [ 0.70710678 0. 0.70710678] + [ 0. 0.70710678 0. ]] + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 0.+0.j 0.-0.70710678j 0.+0.j ] + [ 0.+0.70710678j 0.+0.j 0.-0.70710678j] + [ 0.+0.j 0.+0.70710678j 0.+0.j ]] + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 1. 0. 0.] + [ 0. 0. 0.] + [ 0. 0. -1.]]] + + + Notes + ----- + If no 'args' input, then returns array of ['x','y','z'] operators. + + """ + if (scipy.fix(2 * j) != 2 * j) or (j < 0): + raise TypeError('j must be a non-negative integer or half-integer') + + if not args: + return jmat(j, 'x'), jmat(j, 'y'), jmat(j, 'z') + + if args[0] == '+': + A = _jplus(j) + elif args[0] == '-': + A = _jplus(j).getH() + elif args[0] == 'x': + A = 0.5 * (_jplus(j) + _jplus(j).getH()) + elif args[0] == 'y': + A = -0.5 * 1j * (_jplus(j) - _jplus(j).getH()) + elif args[0] == 'z': + A = _jz(j) + else: + raise TypeError('Invalid type') + + return Qobj(A) + + +def _jplus(j): + """ + Internal functions for generating the data representing the J-plus + operator. + """ + m = np.arange(j, -j - 1, -1, dtype=complex) + data = (np.sqrt(j * (j + 1.0) - (m + 1.0) * m))[1:] + N = m.shape[0] + ind = np.arange(1, N, dtype=np.int32) + ptr = np.array(list(range(N-1))+[N-1]*2, dtype=np.int32) + ptr[-1] = N-1 + return fast_csr_matrix((data,ind,ptr), shape=(N,N)) + + +def _jz(j): + """ + Internal functions for generating the data representing the J-z operator. + """ + N = int(2*j+1) + data = np.array([j-k for k in range(N) if (j-k)!=0], dtype=complex) + # Even shaped matrix + if (N % 2 == 0): + ind = np.arange(N, dtype=np.int32) + ptr = np.arange(N+1,dtype=np.int32) + ptr[-1] = N + # Odd shaped matrix + else: + j = int(j) + ind = np.array(list(range(j))+list(range(j+1,N)), dtype=np.int32) + ptr = np.array(list(range(j+1))+list(range(j,N)), dtype=np.int32) + ptr[-1] = N-1 + return fast_csr_matrix((data,ind,ptr), shape=(N,N)) + + +# +# Spin j operators: +# +def spin_Jx(j): + """Spin-j x operator + + Parameters + ---------- + j : float + Spin of operator + + Returns + ------- + op : Qobj + ``qobj`` representation of the operator. + + """ + return jmat(j, 'x') + + +def spin_Jy(j): + """Spin-j y operator + + Parameters + ---------- + j : float + Spin of operator + + Returns + ------- + op : Qobj + ``qobj`` representation of the operator. + + """ + return jmat(j, 'y') + + +def spin_Jz(j): + """Spin-j z operator + + Parameters + ---------- + j : float + Spin of operator + + Returns + ------- + op : Qobj + ``qobj`` representation of the operator. + + """ + return jmat(j, 'z') + + +def spin_Jm(j): + """Spin-j annihilation operator + + Parameters + ---------- + j : float + Spin of operator + + Returns + ------- + op : Qobj + ``qobj`` representation of the operator. + + """ + return jmat(j, '-') + + +def spin_Jp(j): + """Spin-j creation operator + + Parameters + ---------- + j : float + Spin of operator + + Returns + ------- + op : Qobj + ``qobj`` representation of the operator. + + """ + return jmat(j, '+') + + +def spin_J_set(j): + """Set of spin-j operators (x, y, z) + + Parameters + ---------- + j : float + Spin of operators + + Returns + ------- + list : list of Qobj + list of ``qobj`` representating of the spin operator. + + """ + return jmat(j) + + +# +# Pauli spin 1/2 operators: +# +def sigmap(): + """Creation operator for Pauli spins. + + Examples + -------- + >>> sigmap() + Quantum object: dims = [[2], [2]], \ +shape = [2, 2], type = oper, isHerm = False + Qobj data = + [[ 0. 1.] + [ 0. 0.]] + + """ + return jmat(1 / 2., '+') + + +def sigmam(): + """Annihilation operator for Pauli spins. + + Examples + -------- + >>> sigmam() + Quantum object: dims = [[2], [2]], \ +shape = [2, 2], type = oper, isHerm = False + Qobj data = + [[ 0. 0.] + [ 1. 0.]] + + """ + return jmat(1 / 2., '-') + + +def sigmax(): + """Pauli spin 1/2 sigma-x operator + + Examples + -------- + >>> sigmax() + Quantum object: dims = [[2], [2]], \ +shape = [2, 2], type = oper, isHerm = False + Qobj data = + [[ 0. 1.] + [ 1. 0.]] + + """ + return 2.0 * jmat(1.0 / 2, 'x') + + +def sigmay(): + """Pauli spin 1/2 sigma-y operator. + + Examples + -------- + >>> sigmay() + Quantum object: dims = [[2], [2]], \ +shape = [2, 2], type = oper, isHerm = True + Qobj data = + [[ 0.+0.j 0.-1.j] + [ 0.+1.j 0.+0.j]] + + """ + return 2.0 * jmat(1.0 / 2, 'y') + + +def sigmaz(): + """Pauli spin 1/2 sigma-z operator. + + Examples + -------- + >>> sigmaz() + Quantum object: dims = [[2], [2]], \ +shape = [2, 2], type = oper, isHerm = True + Qobj data = + [[ 1. 0.] + [ 0. -1.]] + + """ + return 2.0 * jmat(1.0 / 2, 'z') + + +# +# DESTROY returns annihilation operator for N dimensional Hilbert space +# out = destroy(N), N is integer value & N>0 +# +def destroy(N, offset=0): + '''Destruction (lowering) operator. + + Parameters + ---------- + N : int + Dimension of Hilbert space. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper : qobj + Qobj for lowering operator. + + Examples + -------- + >>> destroy(4) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isHerm = False + Qobj data = + [[ 0.00000000+0.j 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j] + [ 0.00000000+0.j 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j] + [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j] + [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j]] + + ''' + if not isinstance(N, (int, np.integer)): # raise error if N not integer + raise ValueError("Hilbert space dimension must be integer value") + data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) + ind = np.arange(1,N, dtype=np.int32) + ptr = np.arange(N+1, dtype=np.int32) + ptr[-1] = N-1 + return Qobj(fast_csr_matrix((data,ind,ptr),shape=(N,N)), isherm=False) + + +# +# create returns creation operator for N dimensional Hilbert space +# out = create(N), N is integer value & N>0 +# +def create(N, offset=0): + '''Creation (raising) operator. + + Parameters + ---------- + N : int + Dimension of Hilbert space. + + Returns + ------- + oper : qobj + Qobj for raising operator. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Examples + -------- + >>> create(4) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isHerm = False + Qobj data = + [[ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j] + [ 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j] + [ 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j 0.00000000+0.j] + [ 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j 0.00000000+0.j]] + + ''' + if not isinstance(N, (int, np.integer)): # raise error if N not integer + raise ValueError("Hilbert space dimension must be integer value") + qo = destroy(N, offset=offset) # create operator using destroy function + return qo.dag() + + +# +# QEYE returns identity operator for an N dimensional space +# a = qeye(N), N is integer & N>0 +# +def qeye(N): + """ + Identity operator + + Parameters + ---------- + N : int or list of ints + Dimension of Hilbert space. If provided as a list of ints, + then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. + + Returns + ------- + oper : qobj + Identity operator Qobj. + + Examples + -------- + >>> qeye(3) + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 1. 0. 0.] + [ 0. 1. 0.] + [ 0. 0. 1.]] + + """ + if isinstance(N, list): + return tensor(*[identity(n) for n in N]) + N = int(N) + if N < 0: + raise ValueError("N must be integer N>=0") + return Qobj(fast_identity(N), isherm=True, isunitary=True) + + +def identity(N): + """Identity operator. Alternative name to :func:`qeye`. + + Parameters + ---------- + N : int or list of ints + Dimension of Hilbert space. If provided as a list of ints, + then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. + + Returns + ------- + oper : qobj + Identity operator Qobj. + """ + return qeye(N) + + +def position(N, offset=0): + """ + Position operator x=1/sqrt(2)*(a+a.dag()) + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper : qobj + Position operator as Qobj. + """ + a = destroy(N, offset=offset) + return 1.0 / np.sqrt(2.0) * (a + a.dag()) + + +def momentum(N, offset=0): + """ + Momentum operator p=-1j/sqrt(2)*(a-a.dag()) + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper : qobj + Momentum operator as Qobj. + """ + a = destroy(N, offset=offset) + return -1j / np.sqrt(2.0) * (a - a.dag()) + + +def num(N, offset=0): + """Quantum object for number operator. + + Parameters + ---------- + N : int + The dimension of the Hilbert space. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper: qobj + Qobj for number operator. + + Examples + -------- + >>> num(4) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isHerm = True + Qobj data = + [[0 0 0 0] + [0 1 0 0] + [0 0 2 0] + [0 0 0 3]] + + """ + if offset == 0: + data = np.arange(1,N, dtype=complex) + ind = np.arange(1,N, dtype=np.int32) + ptr = np.array([0]+list(range(0,N)), dtype=np.int32) + ptr[-1] = N-1 + else: + data = np.arange(offset, offset + N, dtype=complex) + ind = np.arange(N, dtype=np.int32) + ptr = np.arange(N+1,dtype=np.int32) + ptr[-1] = N + + return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,N)), isherm=True) + + +def squeeze(N, z, offset=0): + """Single-mode Squeezing operator. + + + Parameters + ---------- + N : int + Dimension of hilbert space. + + z : float/complex + Squeezing parameter. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper : :class:`qutip.qobj.Qobj` + Squeezing operator. + + + Examples + -------- + >>> squeeze(4, 0.25) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isHerm = False + Qobj data = + [[ 0.98441565+0.j 0.00000000+0.j 0.17585742+0.j 0.00000000+0.j] + [ 0.00000000+0.j 0.95349007+0.j 0.00000000+0.j 0.30142443+0.j] + [-0.17585742+0.j 0.00000000+0.j 0.98441565+0.j 0.00000000+0.j] + [ 0.00000000+0.j -0.30142443+0.j 0.00000000+0.j 0.95349007+0.j]] + + """ + a = destroy(N, offset=offset) + op = (1 / 2.0) * np.conj(z) * (a ** 2) - (1 / 2.0) * z * (a.dag()) ** 2 + return op.expm() + + +def squeezing(a1, a2, z): + """Generalized squeezing operator. + + .. math:: + + S(z) = \\exp\\left(\\frac{1}{2}\\left(z^*a_1a_2 + - za_1^\\dagger a_2^\\dagger\\right)\\right) + + Parameters + ---------- + a1 : :class:`qutip.qobj.Qobj` + Operator 1. + + a2 : :class:`qutip.qobj.Qobj` + Operator 2. + + z : float/complex + Squeezing parameter. + + Returns + ------- + oper : :class:`qutip.qobj.Qobj` + Squeezing operator. + + """ + b = 0.5 * (np.conj(z) * (a1 * a2) - z * (a1.dag() * a2.dag())) + return b.expm() + + +def displace(N, alpha, offset=0): + """Single-mode displacement operator. + + Parameters + ---------- + N : int + Dimension of Hilbert space. + + alpha : float/complex + Displacement amplitude. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the operator. + + Returns + ------- + oper : qobj + Displacement operator. + + Examples + --------- + >>> displace(4,0.25) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isHerm = False + Qobj data = + [[ 0.96923323+0.j -0.24230859+0.j 0.04282883+0.j -0.00626025+0.j] + [ 0.24230859+0.j 0.90866411+0.j -0.33183303+0.j 0.07418172+0.j] + [ 0.04282883+0.j 0.33183303+0.j 0.84809499+0.j -0.41083747+0.j] + [ 0.00626025+0.j 0.07418172+0.j 0.41083747+0.j 0.90866411+0.j]] + + """ + a = destroy(N, offset=offset) + D = (alpha * a.dag() - np.conj(alpha) * a).expm() + return D + + +def commutator(A, B, kind="normal"): + """ + Return the commutator of kind `kind` (normal, anti) of the + two operators A and B. + """ + if kind == 'normal': + return A * B - B * A + + elif kind == 'anti': + return A * B + B * A + + else: + raise TypeError("Unknown commutator kind '%s'" % kind) + + +def qutrit_ops(): + """ + Operators for a three level system (qutrit). + + Returns + ------- + opers: array + `array` of qutrit operators. + + """ + from qutip.states import qutrit_basis + + one, two, three = qutrit_basis() + sig11 = one * one.dag() + sig22 = two * two.dag() + sig33 = three * three.dag() + sig12 = one * two.dag() + sig23 = two * three.dag() + sig31 = three * one.dag() + return np.array([sig11, sig22, sig33, sig12, sig23, sig31], + dtype=object) + + +def qdiags(diagonals, offsets, dims=None, shape=None): + """ + Constructs an operator from an array of diagonals. + + Parameters + ---------- + diagonals : sequence of array_like + Array of elements to place along the selected diagonals. + + offsets : sequence of ints + Sequence for diagonals to be set: + - k=0 main diagonal + - k>0 kth upper diagonal + - k<0 kth lower diagonal + dims : list, optional + Dimensions for operator + + shape : list, tuple, optional + Shape of operator. If omitted, a square operator large enough + to contain the diagonals is generated. + + See Also + -------- + scipy.sparse.diags : for usage information. + + Notes + ----- + This function requires SciPy 0.11+. + + Examples + -------- + >>> qdiags(sqrt(range(1, 4)), 1) + Quantum object: dims = [[4], [4]], \ +shape = [4, 4], type = oper, isherm = False + Qobj data = + [[ 0. 1. 0. 0. ] + [ 0. 0. 1.41421356 0. ] + [ 0. 0. 0. 1.73205081] + [ 0. 0. 0. 0. ]] + + """ + data = sp.diags(diagonals, offsets, shape, format='csr', dtype=complex) + if not dims: + dims = [[], []] + if not shape: + shape = [] + return Qobj(data, dims, list(shape)) + + +def phase(N, phi0=0): + """ + Single-mode Pegg-Barnett phase operator. + + Parameters + ---------- + N : int + Number of basis states in Hilbert space. + phi0 : float + Reference phase. + + Returns + ------- + oper : qobj + Phase operator with respect to reference phase. + + Notes + ----- + The Pegg-Barnett phase operator is Hermitian on a truncated Hilbert space. + + """ + phim = phi0 + (2.0 * np.pi * np.arange(N)) / N # discrete phase angles + n = np.arange(N).reshape((N, 1)) + states = np.array([np.sqrt(kk) / np.sqrt(N) * np.exp(1.0j * n * kk) + for kk in phim]) + ops = np.array([np.outer(st, st.conj()) for st in states]) + return Qobj(np.sum(ops, axis=0)) + + +def qzero(N): + """ + Zero operator + + Parameters + ---------- + N : int or list of ints + Dimension of Hilbert space. If provided as a list of ints, + then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. + + Returns + ------- + qzero : qobj + Zero operator Qobj. + + """ + + if isinstance(N, list): + return tensor(*[qzero(n) for n in N]) + N = int(N) + if (not isinstance(N, (int, np.integer))) or N < 0: + raise ValueError("N must be integer N>=0") + return Qobj(sp.csr_matrix((N, N), dtype=complex), isherm=True) + + +def enr_destroy(dims, excitations): + """ + Generate annilation operators for modes in a excitation-number-restricted + state space. For example, consider a system consisting of 4 modes, each + with 5 states. The total hilbert space size is 5**4 = 625. If we are + only interested in states that contain up to 2 excitations, we only need + to include states such as + + (0, 0, 0, 0) + (0, 0, 0, 1) + (0, 0, 0, 2) + (0, 0, 1, 0) + (0, 0, 1, 1) + (0, 0, 2, 0) + ... + + This function creates annihilation operators for the 4 modes that act + within this state space: + + a1, a2, a3, a4 = enr_destroy([5, 5, 5, 5], excitations=2) + + From this point onwards, the annihiltion operators a1, ..., a4 can be + used to setup a Hamiltonian, collapse operators and expectation-value + operators, etc., following the usual pattern. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + Returns + ------- + a_ops : list of qobj + A list of annihilation operators for each mode in the composite + quantum system described by dims. + """ + from qutip.states import enr_state_dictionaries + + nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) + + a_ops = [sp.lil_matrix((nstates, nstates), dtype=np.complex) + for _ in range(len(dims))] + + for n1, state1 in idx2state.items(): + for n2, state2 in idx2state.items(): + for idx, a in enumerate(a_ops): + s1 = [s for idx2, s in enumerate(state1) if idx != idx2] + s2 = [s for idx2, s in enumerate(state2) if idx != idx2] + if (state1[idx] == state2[idx] - 1) and (s1 == s2): + a_ops[idx][n1, n2] = np.sqrt(state2[idx]) + + return [Qobj(a, dims=[dims, dims]) for a in a_ops] + + +def enr_identity(dims, excitations): + """ + Generate the identity operator for the excitation-number restricted + state space defined by the `dims` and `exciations` arguments. See the + docstring for enr_fock for a more detailed description of these arguments. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + state : list of integers + The state in the number basis representation. + + Returns + ------- + op : Qobj + A Qobj instance that represent the identity operator in the + exication-number-restricted state space defined by `dims` and + `exciations`. + """ + from qutip.states import enr_state_dictionaries + + nstates, _, _ = enr_state_dictionaries(dims, excitations) + data = sp.eye(nstates, nstates, dtype=np.complex) + return Qobj(data, dims=[dims, dims]) + + + +def charge(Nmax, Nmin=None, frac = 1): + """ + Generate the diagonal charge operator over charge states + from Nmin to Nmax. + + Parameters + ---------- + Nmax : int + Maximum charge state to consider. + + Nmin : int (default = -Nmax) + Lowest charge state to consider. + + frac : float (default = 1) + Specify fractional charge if needed. + + Returns + ------- + C : Qobj + Charge operator over [Nmin,Nmax]. + + Notes + ----- + .. versionadded:: 3.2 + + """ + if Nmin is None: + Nmin = -Nmax + diag = np.arange(Nmin, Nmax+1, dtype=float) + if frac != 1: + diag *= frac + C = sp.diags(diag, 0, format='csr', dtype=complex) + return Qobj(C, isherm=True) + + + +def tunneling(N, m=1): + """ + Tunneling operator with elements of the form + :math:`\sum |N> 0 or td_type[2] > 0: + H2 = [] + for k in range(len(H)): + if isinstance(H[k], list): + H2.append([tensor(qeye(N), H[k][0]), H[k][1]]) + else: + H2.append(tensor(qeye(N), H[k])) + else: + H2 = tensor(qeye(N), H) + options.normalize_output = False + output = sesolve(H2, psi0, tlist, [], + args=args, options=options, + _safe_mode=False) + for k, t in enumerate(tlist): + u[k] = sp_reshape(output.states[k].data, (N, N)) + unit_row_norm(u[k].data, u[k].indptr, u[k].shape[0]) + u[k] = u[k].T.tocsr() + + else: + raise Exception('Invalid unitary mode.') + + + elif len(c_op_list) == 0 and H0.issuper: + # calculate the propagator for the vector representation of the + # density matrix (a superoperator propagator) + unitary_mode = 'single' + N = H0.shape[0] + sqrt_N = int(np.sqrt(N)) + dims = H0.dims + + u = np.zeros([N, N, len(tlist)], dtype=complex) + + if parallel: + output = parallel_map(_parallel_mesolve,range(N * N), + task_args=( + sqrt_N, H, tlist, c_op_list, args, + options), + progress_bar=progress_bar, num_cpus=num_cpus) + for n in range(N * N): + for k, t in enumerate(tlist): + u[:, n, k] = mat2vec(output[n].states[k].full()).T + else: + rho0 = qeye(N,N) + rho0.dims = [[sqrt_N, sqrt_N], [sqrt_N, sqrt_N]] + output = mesolve(H, psi0, tlist, [], args, options, + _safe_mode=False) + if len(tlist) == 2: + return output.states[-1] + else: + return output.states + + else: + # calculate the propagator for the vector representation of the + # density matrix (a superoperator propagator) + unitary_mode = 'single' + N = H0.shape[0] + dims = [H0.dims, H0.dims] + + u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) + + if parallel: + output = parallel_map(_parallel_mesolve, range(N * N), + task_args=( + N, H, tlist, c_op_list, args, options), + progress_bar=progress_bar, num_cpus=num_cpus) + for n in range(N * N): + for k, t in enumerate(tlist): + u[:, n, k] = mat2vec(output[n].states[k].full()).T + else: + progress_bar.start(N * N) + for n in range(N * N): + progress_bar.update(n) + col_idx, row_idx = np.unravel_index(n, (N, N)) + rho0 = Qobj(sp.csr_matrix(([1], ([row_idx], [col_idx])), + shape=(N,N), dtype=complex)) + output = mesolve(H, rho0, tlist, c_op_list, [], args, options, + _safe_mode=False) + for k, t in enumerate(tlist): + u[:, n, k] = mat2vec(output.states[k].full()).T + progress_bar.finished() + + if len(tlist) == 2: + if unitary_mode == 'batch': + return Qobj(u[-1], dims=dims) + else: + return Qobj(u[:, :, 1], dims=dims) + else: + if unitary_mode == 'batch': + return np.array([Qobj(u[k], dims=dims) + for k in range(len(tlist))], dtype=object) + else: + return np.array([Qobj(u[:, :, k], dims=dims) + for k in range(len(tlist))], dtype=object) + + +def _get_min_and_index(lst): + """ + Private function for obtaining min and max indicies. + """ + minval, minidx = lst[0], 0 + for i, v in enumerate(lst[1:]): + if v < minval: + minval, minidx = v, i + 1 + return minval, minidx + + +def propagator_steadystate(U): + """Find the steady state for successive applications of the propagator + :math:`U`. + + Parameters + ---------- + U : qobj + Operator representing the propagator. + + Returns + ------- + a : qobj + Instance representing the steady-state density matrix. + + """ + + evals, evecs = la.eig(U.full()) + + shifted_vals = np.abs(evals - 1.0) + ev_idx = np.argmin(shifted_vals) + ev_min = shifted_vals[ev_idx] + evecs = evecs.T + rho = Qobj(vec2mat(evecs[ev_idx]), dims=U.dims[0]) + rho = rho * (1.0 / rho.tr()) + rho = 0.5 * (rho + rho.dag()) # make sure rho is herm + rho.isherm = True + return rho + + +def _parallel_sesolve(n, N, H, tlist, args, options): + psi0 = basis(N, n) + output = sesolve(H, psi0, tlist, [], args, options, _safe_mode=False) + return output + +def _parallel_mesolve(n, N, H, tlist, c_op_list, args, options): + col_idx, row_idx = np.unravel_index(n, (N, N)) + rho0 = Qobj(sp.csr_matrix(([1], ([row_idx], [col_idx])), + shape=(N,N), dtype=complex)) + output = mesolve(H, rho0, tlist, c_op_list, [], args, options, + _safe_mode=False) + return output diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py new file mode 100755 index 0000000000..f774b9e12e --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -0,0 +1,2409 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +"""The Quantum Object (Qobj) class, for representing quantum states and +operators, and related functions. +""" + +__all__ = ['Qobj', 'qobj_list_evaluate', 'ptrace', 'dag', 'isequal', + 'issuper', 'isoper', 'isoperket', 'isoperbra', 'isket', 'isbra', + 'isherm', 'shape', 'dims'] + +import warnings +import types + +try: + import builtins +except: + import __builtin__ as builtins + +# import math functions from numpy.math: required for td string evaluation +from numpy import (arccos, arccosh, arcsin, arcsinh, arctan, arctan2, arctanh, + ceil, copysign, cos, cosh, degrees, e, exp, expm1, fabs, + floor, fmod, frexp, hypot, isinf, isnan, ldexp, log, log10, + log1p, modf, pi, radians, sin, sinh, sqrt, tan, tanh, trunc) + +import numpy as np +import scipy.sparse as sp +import scipy.linalg as la +import qutip.settings as settings +from qutip import __version__ +from qutip.fastsparse import fast_csr_matrix, fast_identity +from qutip.cy.ptrace import _ptrace +from qutip.permute import _permute +from qutip.sparse import (sp_eigs, sp_expm, sp_fro_norm, sp_max_norm, + sp_one_norm, sp_L2_norm) +from qutip.dimensions import type_from_dims, enumerate_flat, collapse_dims_super +from qutip.cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_isherm, + zcsr_trace, zcsr_proj, zcsr_inner) +from qutip.cy.spmatfuncs import zcsr_mat_elem +from qutip.cy.sparse_utils import cy_tidyup +import sys +if sys.version_info.major >= 3: + from itertools import zip_longest +elif sys.version_info.major < 3: + from itertools import izip_longest + zip_longest = izip_longest + +#OPENMP stuff +from qutip.cy.openmp.utilities import use_openmp +if settings.has_openmp: + from qutip.cy.openmp.omp_sparse_utils import omp_tidyup + + +class Qobj(object): + """A class for representing quantum objects, such as quantum operators + and states. + + The Qobj class is the QuTiP representation of quantum operators and state + vectors. This class also implements math operations +,-,* between Qobj + instances (and / by a C-number), as well as a collection of common + operator/state operations. The Qobj constructor optionally takes a + dimension ``list`` and/or shape ``list`` as arguments. + + Parameters + ---------- + inpt : array_like + Data for vector/matrix representation of the quantum object. + dims : list + Dimensions of object used for tensor products. + shape : list + Shape of underlying data structure (matrix shape). + copy : bool + Flag specifying whether Qobj should get a copy of the + input data, or use the original. + fast : bool + Flag for fast qobj creation when running ode solvers. + This parameter is used internally only. + + + Attributes + ---------- + data : array_like + Sparse matrix characterizing the quantum object. + dims : list + List of dimensions keeping track of the tensor structure. + shape : list + Shape of the underlying `data` array. + type : str + Type of quantum object: 'bra', 'ket', 'oper', 'operator-ket', + 'operator-bra', or 'super'. + superrep : str + Representation used if `type` is 'super'. One of 'super' + (Liouville form) or 'choi' (Choi matrix with tr = dimension). + isherm : bool + Indicates if quantum object represents Hermitian operator. + isunitary : bool + Indictaes if quantum object represents unitary operator. + iscp : bool + Indicates if the quantum object represents a map, and if that map is + completely positive (CP). + ishp : bool + Indicates if the quantum object represents a map, and if that map is + hermicity preserving (HP). + istp : bool + Indicates if the quantum object represents a map, and if that map is + trace preserving (TP). + iscptp : bool + Indicates if the quantum object represents a map that is completely + positive and trace preserving (CPTP). + isket : bool + Indicates if the quantum object represents a ket. + isbra : bool + Indicates if the quantum object represents a bra. + isoper : bool + Indicates if the quantum object represents an operator. + issuper : bool + Indicates if the quantum object represents a superoperator. + isoperket : bool + Indicates if the quantum object represents an operator in column vector + form. + isoperbra : bool + Indicates if the quantum object represents an operator in row vector + form. + + Methods + ------- + copy() + Create copy of Qobj + conj() + Conjugate of quantum object. + cosm() + Cosine of quantum object. + dag() + Adjoint (dagger) of quantum object. + dnorm() + Diamond norm of quantum operator. + dual_chan() + Dual channel of quantum object representing a CP map. + eigenenergies(sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000) + Returns eigenenergies (eigenvalues) of a quantum object. + eigenstates(sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000) + Returns eigenenergies and eigenstates of quantum object. + expm() + Matrix exponential of quantum object. + full(order='C') + Returns dense array of quantum object `data` attribute. + groundstate(sparse=False, tol=0, maxiter=100000) + Returns eigenvalue and eigenket for the groundstate of a quantum + object. + matrix_element(bra, ket) + Returns the matrix element of operator between `bra` and `ket` vectors. + norm(norm='tr', sparse=False, tol=0, maxiter=100000) + Returns norm of a ket or an operator. + permute(order) + Returns composite qobj with indices reordered. + proj() + Computes the projector for a ket or bra vector. + ptrace(sel) + Returns quantum object for selected dimensions after performing + partial trace. + sinm() + Sine of quantum object. + sqrtm() + Matrix square root of quantum object. + tidyup(atol=1e-12) + Removes small elements from quantum object. + tr() + Trace of quantum object. + trans() + Transpose of quantum object. + transform(inpt, inverse=False) + Performs a basis transformation defined by `inpt` matrix. + trunc_neg(method='clip') + Removes negative eigenvalues and returns a new Qobj that is + a valid density operator. + unit(norm='tr', sparse=False, tol=0, maxiter=100000) + Returns normalized quantum object. + + """ + __array_priority__ = 100 # sets Qobj priority above numpy arrays + + def __init__(self, inpt=None, dims=[[], []], shape=[], + type=None, isherm=None, copy=True, + fast=False, superrep=None, isunitary=None): + """ + Qobj constructor. + """ + self._isherm = isherm + self._type = type + self.superrep = superrep + self._isunitary = isunitary + + if fast == 'mc': + # fast Qobj construction for use in mcsolve with ket output + self._data = inpt + self.dims = dims + self._isherm = False + return + + if fast == 'mc-dm': + # fast Qobj construction for use in mcsolve with dm output + self._data = inpt + self.dims = dims + self._isherm = True + return + + if isinstance(inpt, Qobj): + # if input is already Qobj then return identical copy + + self._data = fast_csr_matrix((inpt.data.data, inpt.data.indices, + inpt.data.indptr), + shape=inpt.shape, copy=copy) + + if not np.any(dims): + # Dimensions of quantum object used for keeping track of tensor + # components + self.dims = inpt.dims + else: + self.dims = dims + + self.superrep = inpt.superrep + self._isunitary = inpt._isunitary + + elif inpt is None: + # initialize an empty Qobj with correct dimensions and shape + + if any(dims): + N, M = np.prod(dims[0]), np.prod(dims[1]) + self.dims = dims + + elif shape: + N, M = shape + self.dims = [[N], [M]] + + else: + N, M = 1, 1 + self.dims = [[N], [M]] + + self._data = fast_csr_matrix(shape=(N, M)) + + elif isinstance(inpt, list) or isinstance(inpt, tuple): + # case where input is a list + data = np.array(inpt) + if len(data.shape) == 1: + # if list has only one dimension (i.e [5,4]) + data = data.transpose() + + _tmp = sp.csr_matrix(data, dtype=complex) + self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), + shape=_tmp.shape) + if not np.any(dims): + self.dims = [[int(data.shape[0])], [int(data.shape[1])]] + else: + self.dims = dims + + elif isinstance(inpt, np.ndarray) or sp.issparse(inpt): + # case where input is array or sparse + if inpt.ndim == 1: + inpt = inpt[:, np.newaxis] + + do_copy = copy + if not isinstance(inpt, fast_csr_matrix): + _tmp = sp.csr_matrix(inpt, dtype=complex, copy=do_copy) + _tmp.sort_indices() #Make sure indices are sorted. + do_copy = 0 + else: + _tmp = inpt + self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), + shape=_tmp.shape, copy=do_copy) + + if not np.any(dims): + self.dims = [[int(inpt.shape[0])], [int(inpt.shape[1])]] + else: + self.dims = dims + + elif isinstance(inpt, (int, float, complex, + np.integer, np.floating, np.complexfloating)): + # if input is int, float, or complex then convert to array + _tmp = sp.csr_matrix([[inpt]], dtype=complex) + self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), + shape=_tmp.shape) + if not np.any(dims): + self.dims = [[1], [1]] + else: + self.dims = dims + + else: + warnings.warn("Initializing Qobj from unsupported type: %s" % + builtins.type(inpt)) + inpt = np.array([[0]]) + _tmp = sp.csr_matrix(inpt, dtype=complex, copy=copy) + self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), + shape = _tmp.shape) + self.dims = [[int(inpt.shape[0])], [int(inpt.shape[1])]] + + if type == 'super': + # Type is not super, i.e. dims not explicitly passed, but oper shape + if dims== [[], []] and self.shape[0] == self.shape[1]: + sub_shape = np.sqrt(self.shape[0]) + # check if root of shape is int + if (sub_shape % 1) != 0: + raise Exception('Invalid shape for a super operator.') + else: + sub_shape = int(sub_shape) + self.dims = [[[sub_shape], [sub_shape]]]*2 + + + if superrep: + self.superrep = superrep + else: + if self.type == 'super' and self.superrep is None: + self.superrep = 'super' + + # clear type cache + self._type = None + + def copy(self): + """Create identical copy""" + return Qobj(inpt=self) + + def get_data(self): + return self._data + #Here we perfrom a check of the csr matrix type during setting of Q.data + def set_data(self, data): + if not isinstance(data, fast_csr_matrix): + raise TypeError('Qobj data must be in fast_csr format.') + else: + self._data = data + data = property(get_data, set_data) + + def __add__(self, other): + """ + ADDITION with Qobj on LEFT [ ex. Qobj+4 ] + """ + self._isunitary = None + + if isinstance(other, eseries): + return other.__radd__(self) + + if not isinstance(other, Qobj): + if isinstance(other, (int, float, complex, np.integer, np.floating, + np.complexfloating, np.ndarray, list, tuple)) \ + or sp.issparse(other): + other = Qobj(other) + else: + return NotImplemented + + if np.prod(other.shape) == 1 and np.prod(self.shape) != 1: + # case for scalar quantum object + dat = other.data[0, 0] + if dat == 0: + return self + + out = Qobj() + + if self.type in ['oper', 'super']: + out.data = self.data + dat * fast_identity( + self.shape[0]) + else: + out.data = self.data + out.data.data = out.data.data + dat + + out.dims = self.dims + + if settings.auto_tidyup: out.tidyup() + + if isinstance(dat, (int, float)): + out._isherm = self._isherm + else: + # We use _isherm here to prevent recalculating on self and + # other, relying on that bool(None) == False. + out._isherm = (True if self._isherm and other._isherm + else out.isherm) + + out.superrep = self.superrep + + return out + + elif np.prod(self.shape) == 1 and np.prod(other.shape) != 1: + # case for scalar quantum object + dat = self.data[0, 0] + if dat == 0: + return other + + out = Qobj() + if other.type in ['oper', 'super']: + out.data = dat * fast_identity(other.shape[0]) + other.data + else: + out.data = other.data + out.data.data = out.data.data + dat + out.dims = other.dims + + if settings.auto_tidyup: out.tidyup() + + if isinstance(dat, complex): + out._isherm = out.isherm + else: + out._isherm = self._isherm + + out.superrep = self.superrep + + return out + + elif self.dims != other.dims: + raise TypeError('Incompatible quantum object dimensions') + + elif self.shape != other.shape: + raise TypeError('Matrix shapes do not match') + + else: # case for matching quantum objects + out = Qobj() + out.data = self.data + other.data + out.dims = self.dims + if settings.auto_tidyup: out.tidyup() + + if self.type in ['ket', 'bra', 'operator-ket', 'operator-bra']: + out._isherm = False + elif self._isherm is None or other._isherm is None: + out._isherm = out.isherm + elif not self._isherm and not other._isherm: + out._isherm = out.isherm + else: + out._isherm = self._isherm and other._isherm + + if self.superrep and other.superrep: + if self.superrep != other.superrep: + msg = ("Adding superoperators with different " + + "representations") + warnings.warn(msg) + + out.superrep = self.superrep + + return out + + def __radd__(self, other): + """ + ADDITION with Qobj on RIGHT [ ex. 4+Qobj ] + """ + return self + other + + def __sub__(self, other): + """ + SUBTRACTION with Qobj on LEFT [ ex. Qobj-4 ] + """ + return self + (-other) + + def __rsub__(self, other): + """ + SUBTRACTION with Qobj on RIGHT [ ex. 4-Qobj ] + """ + return (-self) + other + + def __mul__(self, other): + """ + MULTIPLICATION with Qobj on LEFT [ ex. Qobj*4 ] + """ + self._isunitary = None + + if isinstance(other, Qobj): + if self.dims[1] == other.dims[0]: + out = Qobj() + out.data = self.data * other.data + dims = [self.dims[0], other.dims[1]] + out.dims = dims + if settings.auto_tidyup: out.tidyup() + if (settings.auto_tidyup_dims + and not isinstance(dims[0][0], list) + and not isinstance(dims[1][0], list)): + # If neither left or right is a superoperator, + # we should implicitly partial trace over + # matching dimensions of 1. + # Using izip_longest allows for the left and right dims + # to have uneven length (non-square Qobjs). + # We use None as padding so that it doesn't match anything, + # and will never cause a partial trace on the other side. + mask = [l == r == 1 for l, r in zip_longest(dims[0], dims[1], + fillvalue=None)] + # To ensure that there are still any dimensions left, we + # use max() to add a dimensions list of [1] if all matching dims + # are traced out of that side. + out.dims = [max([1], + [dim for dim, m in zip(dims[0], mask) + if not m]), + max([1], + [dim for dim, m in zip(dims[1], mask) + if not m])] + + else: + out.dims = dims + + out._isherm = None + + if self.superrep and other.superrep: + if self.superrep != other.superrep: + msg = ("Multiplying superoperators with different " + + "representations") + warnings.warn(msg) + + out.superrep = self.superrep + + return out + + elif np.prod(self.shape) == 1: + out = Qobj(other) + out.data *= self.data[0, 0] + out.superrep = other.superrep + return out.tidyup() if settings.auto_tidyup else out + + elif np.prod(other.shape) == 1: + out = Qobj(self) + out.data *= other.data[0, 0] + out.superrep = self.superrep + return out.tidyup() if settings.auto_tidyup else out + + else: + raise TypeError("Incompatible Qobj shapes") + + elif isinstance(other, np.ndarray): + if other.dtype=='object': + return np.array([self * item for item in other], + dtype=object) + else: + return self.data * other + + + elif isinstance(other, list): + # if other is a list, do element-wise multiplication + return np.array([self * item for item in other], + dtype=object) + + elif isinstance(other, eseries): + return other.__rmul__(self) + + elif isinstance(other, (int, float, complex, + np.integer, np.floating, np.complexfloating)): + out = Qobj() + out.data = self.data * other + out.dims = self.dims + out.superrep = self.superrep + if settings.auto_tidyup: out.tidyup() + if isinstance(other, complex): + out._isherm = out.isherm + else: + out._isherm = self._isherm + + return out + + else: + return NotImplemented + + def __rmul__(self, other): + """ + MULTIPLICATION with Qobj on RIGHT [ ex. 4*Qobj ] + """ + if isinstance(other, np.ndarray): + if other.dtype=='object': + return np.array([item * self for item in other], + dtype=object) + else: + return other * self.data + + elif isinstance(other, list): + # if other is a list, do element-wise multiplication + return np.array([item * self for item in other], + dtype=object) + + elif isinstance(other, eseries): + return other.__mul__(self) + + elif isinstance(other, (int, float, complex, + np.integer, np.floating, np.complexfloating)): + out = Qobj() + out.data = other * self.data + out.dims = self.dims + out.superrep = self.superrep + if settings.auto_tidyup: out.tidyup() + if isinstance(other, complex): + out._isherm = out.isherm + else: + out._isherm = self._isherm + + return out + + else: + raise TypeError("Incompatible object for multiplication") + + def __truediv__(self, other): + return self.__div__(other) + + def __div__(self, other): + """ + DIVISION (by numbers only) + """ + if isinstance(other, Qobj): # if both are quantum objects + raise TypeError("Incompatible Qobj shapes " + + "[division with Qobj not implemented]") + + if isinstance(other, (int, float, complex, + np.integer, np.floating, np.complexfloating)): + out = Qobj() + out.data = self.data / other + out.dims = self.dims + if settings.auto_tidyup: out.tidyup() + if isinstance(other, complex): + out._isherm = out.isherm + else: + out._isherm = self._isherm + + out.superrep = self.superrep + + return out + + else: + raise TypeError("Incompatible object for division") + + def __neg__(self): + """ + NEGATION operation. + """ + out = Qobj() + out.data = -self.data + out.dims = self.dims + out.superrep = self.superrep + if settings.auto_tidyup: out.tidyup() + out._isherm = self._isherm + out._isunitary = self._isunitary + return out + + def __getitem__(self, ind): + """ + GET qobj elements. + """ + out = self.data[ind] + if sp.issparse(out): + return np.asarray(out.todense()) + else: + return out + + def __eq__(self, other): + """ + EQUALITY operator. + """ + if (isinstance(other, Qobj) and + self.dims == other.dims and + not np.any(np.abs((self.data - other.data).data) > + settings.atol)): + return True + else: + return False + + def __ne__(self, other): + """ + INEQUALITY operator. + """ + return not (self == other) + + def __pow__(self, n, m=None): # calculates powers of Qobj + """ + POWER operation. + """ + if self.type not in ['oper', 'super']: + raise Exception("Raising a qobj to some power works only for " + + "operators and super-operators (square matrices).") + + if m is not None: + raise NotImplementedError("modulo is not implemented for Qobj") + + try: + data = self.data ** n + out = Qobj(data, dims=self.dims) + out.superrep = self.superrep + return out.tidyup() if settings.auto_tidyup else out + + except: + raise ValueError('Invalid choice of exponent.') + + def __abs__(self): + return abs(self.data) + + def __str__(self): + s = "" + t = self.type + shape = self.shape + if self.type in ['oper', 'super']: + s += ("Quantum object: " + + "dims = " + str(self.dims) + + ", shape = " + str(shape) + + ", type = " + t + + ", isherm = " + str(self.isherm) + + ( + ", superrep = {0.superrep}".format(self) + if t == "super" and self.superrep != "super" + else "" + ) + "\n") + else: + s += ("Quantum object: " + + "dims = " + str(self.dims) + + ", shape = " + str(shape) + + ", type = " + t + "\n") + s += "Qobj data =\n" + + if shape[0] > 10000 or shape[1] > 10000: + # if the system is huge, don't attempt to convert to a + # dense matrix and then to string, because it is pointless + # and is likely going to produce memory errors. Instead print the + # sparse data string representation + s += str(self.data) + + elif all(np.imag(self.data.data) == 0): + s += str(np.real(self.full())) + + else: + s += str(self.full()) + + return s + + def __repr__(self): + # give complete information on Qobj without print statement in + # command-line we cant realistically serialize a Qobj into a string, + # so we simply return the informal __str__ representation instead.) + return self.__str__() + + def __call__(self, other): + """ + Acts this Qobj on another Qobj either by left-multiplication, + or by vectorization and devectorization, as + appropriate. + """ + if not isinstance(other, Qobj): + raise TypeError("Only defined for quantum objects.") + + if self.type == "super": + if other.type == "ket": + other = qutip.states.ket2dm(other) + + if other.type == "oper": + return qutip.superoperator.vector_to_operator( + self * qutip.superoperator.operator_to_vector(other) + ) + else: + raise TypeError("Can only act super on oper or ket.") + + elif self.type == "oper": + if other.type == "ket": + return self * other + else: + raise TypeError("Can only act oper on ket.") + + def __getstate__(self): + # defines what happens when Qobj object gets pickled + self.__dict__.update({'qutip_version': __version__[:5]}) + return self.__dict__ + + def __setstate__(self, state): + # defines what happens when loading a pickled Qobj + if 'qutip_version' in state.keys(): + del state['qutip_version'] + (self.__dict__).update(state) + + def _repr_latex_(self): + """ + Generate a LaTeX representation of the Qobj instance. Can be used for + formatted output in ipython notebook. + """ + t = self.type + shape = self.shape + s = r'' + if self.type in ['oper', 'super']: + s += ("Quantum object: " + + "dims = " + str(self.dims) + + ", shape = " + str(shape) + + ", type = " + t + + ", isherm = " + str(self.isherm) + + ( + ", superrep = {0.superrep}".format(self) + if t == "super" and self.superrep != "super" + else "" + )) + else: + s += ("Quantum object: " + + "dims = " + str(self.dims) + + ", shape = " + str(shape) + + ", type = " + t) + + M, N = self.data.shape + + s += r'\begin{equation*}\left(\begin{array}{*{11}c}' + + def _format_float(value): + if value == 0.0: + return "0.0" + elif abs(value) > 1000.0 or abs(value) < 0.001: + return ("%.3e" % value).replace("e", r"\times10^{") + "}" + elif abs(value - int(value)) < 0.001: + return "%.1f" % value + else: + return "%.3f" % value + + def _format_element(m, n, d): + s = " & " if n > 0 else "" + if type(d) == str: + return s + d + else: + if abs(np.imag(d)) < settings.atol: + return s + _format_float(np.real(d)) + elif abs(np.real(d)) < settings.atol: + return s + _format_float(np.imag(d)) + "j" + else: + s_re = _format_float(np.real(d)) + s_im = _format_float(np.imag(d)) + if np.imag(d) > 0.0: + return (s + "(" + s_re + "+" + s_im + "j)") + else: + return (s + "(" + s_re + s_im + "j)") + + if M > 10 and N > 10: + # truncated matrix output + for m in range(5): + for n in range(5): + s += _format_element(m, n, self.data[m, n]) + s += r' & \cdots' + for n in range(N - 5, N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + for n in range(5): + s += _format_element(m, n, r'\vdots') + s += r' & \ddots' + for n in range(N - 5, N): + s += _format_element(m, n, r'\vdots') + s += r'\\' + + for m in range(M - 5, M): + for n in range(5): + s += _format_element(m, n, self.data[m, n]) + s += r' & \cdots' + for n in range(N - 5, N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + elif M > 10 and N <= 10: + # truncated vertically elongated matrix output + for m in range(5): + for n in range(N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + for n in range(N): + s += _format_element(m, n, r'\vdots') + s += r'\\' + + for m in range(M - 5, M): + for n in range(N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + elif M <= 10 and N > 10: + # truncated horizontally elongated matrix output + for m in range(M): + for n in range(5): + s += _format_element(m, n, self.data[m, n]) + s += r' & \cdots' + for n in range(N - 5, N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + else: + # full output + for m in range(M): + for n in range(N): + s += _format_element(m, n, self.data[m, n]) + s += r'\\' + + s += r'\end{array}\right)\end{equation*}' + return s + + def dag(self): + """Adjoint operator of quantum object. + """ + out = Qobj() + out.data = zcsr_adjoint(self.data) + out.dims = [self.dims[1], self.dims[0]] + out._isherm = self._isherm + out.superrep = self.superrep + return out + + def dual_chan(self): + """Dual channel of quantum object representing a completely positive + map. + """ + # Uses the technique of Johnston and Kribs (arXiv:1102.0948), which + # is only valid for completely positive maps. + if not self.iscp: + raise ValueError("Dual channels are only implemented for CP maps.") + J = sr.to_choi(self) + tensor_idxs = enumerate_flat(J.dims) + J_dual = tensor.tensor_swap(J, *( + list(zip(tensor_idxs[0][1], tensor_idxs[0][0])) + + list(zip(tensor_idxs[1][1], tensor_idxs[1][0])) + )).trans() + J_dual.superrep = 'choi' + return J_dual + + + def conj(self): + """Conjugate operator of quantum object. + """ + out = Qobj() + out.data = self.data.conj() + out.dims = [self.dims[0], self.dims[1]] + return out + + def norm(self, norm=None, sparse=False, tol=0, maxiter=100000): + """Norm of a quantum object. + + Default norm is L2-norm for kets and trace-norm for operators. + Other ket and operator norms may be specified using the `norm` and + argument. + + Parameters + ---------- + norm : str + Which norm to use for ket/bra vectors: L2 'l2', max norm 'max', + or for operators: trace 'tr', Frobius 'fro', one 'one', or max + 'max'. + + sparse : bool + Use sparse eigenvalue solver for trace norm. Other norms are not + affected by this parameter. + + tol : float + Tolerance for sparse solver (if used) for trace norm. The sparse + solver may not converge if the tolerance is set too low. + + maxiter : int + Maximum number of iterations performed by sparse solver (if used) + for trace norm. + + Returns + ------- + norm : float + The requested norm of the operator or state quantum object. + + + Notes + ----- + The sparse eigensolver is much slower than the dense version. + Use sparse only if memory requirements demand it. + + """ + if self.type in ['oper', 'super']: + if norm is None or norm == 'tr': + _op = self*self.dag() + vals = sp_eigs(_op.data, _op.isherm, vecs=False, + sparse=sparse, tol=tol, maxiter=maxiter) + return np.sum(np.sqrt(np.abs(vals))) + elif norm == 'fro': + return sp_fro_norm(self.data) + elif norm == 'one': + return sp_one_norm(self.data) + elif norm == 'max': + return sp_max_norm(self.data) + else: + raise ValueError( + "For matrices, norm must be 'tr', 'fro', 'one', or 'max'.") + else: + if norm is None or norm == 'l2': + return sp_L2_norm(self.data) + elif norm == 'max': + return sp_max_norm(self.data) + else: + raise ValueError("For vectors, norm must be 'l2', or 'max'.") + + def proj(self): + """Form the projector from a given ket or bra vector. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Input bra or ket vector + + Returns + ------- + P : :class:`qutip.Qobj` + Projection operator. + """ + if self.isket: + _out = zcsr_proj(self.data,1) + _dims = [self.dims[0],self.dims[0]] + elif self.isbra: + _out = zcsr_proj(self.data,0) + _dims = [self.dims[1],self.dims[1]] + else: + raise TypeError('Projector can only be formed from a bra or ket.') + + return Qobj(_out,dims=_dims) + + + def tr(self): + """Trace of a quantum object. + + Returns + ------- + trace : float + Returns ``real`` if operator is Hermitian, returns ``complex`` + otherwise. + + """ + return zcsr_trace(self.data, self.isherm) + + def full(self, order='C', squeeze=False): + """Dense array from quantum object. + + Parameters + ---------- + order : str {'C', 'F'} + Return array in C (default) or Fortran ordering. + squeeze : bool {False, True} + Squeeze output array. + + Returns + ------- + data : array + Array of complex data from quantum objects `data` attribute. + """ + if squeeze: + return self.data.toarray(order=order).squeeze() + else: + return self.data.toarray(order=order) + + def __array__(self, *arg, **kwarg): + """Numpy array from Qobj + For compatibility with np.array + """ + return self.full() + + def diag(self): + """Diagonal elements of quantum object. + + Returns + ------- + diags : array + Returns array of ``real`` values if operators is Hermitian, + otherwise ``complex`` values are returned. + + """ + out = self.data.diagonal() + if np.any(np.imag(out) > settings.atol) or not self.isherm: + return out + else: + return np.real(out) + + def expm(self, method='dense'): + """Matrix exponential of quantum operator. + + Input operator must be square. + + Parameters + ---------- + method : str {'dense', 'sparse'} + Use set method to use to calculate the matrix exponentiation. The + available choices includes 'dense' and 'sparse'. Since the + exponential of a matrix is nearly always dense, method='dense' + is set as default.s + + Returns + ------- + oper : :class:`qutip.Qobj` + Exponentiated quantum operator. + + Raises + ------ + TypeError + Quantum operator is not square. + + """ + if self.dims[0][0] != self.dims[1][0]: + raise TypeError('Invalid operand for matrix exponential') + + if method == 'dense': + F = sp_expm(self.data, sparse=False) + + elif method == 'sparse': + F = sp_expm(self.data, sparse=True) + + else: + raise ValueError("method must be 'dense' or 'sparse'.") + + out = Qobj(F, dims=self.dims) + return out.tidyup() if settings.auto_tidyup else out + + def check_herm(self): + """Check if the quantum object is hermitian. + + Returns + ------- + isherm : bool + Returns the new value of isherm property. + """ + self._isherm = None + return self.isherm + + def sqrtm(self, sparse=False, tol=0, maxiter=100000): + """Sqrt of a quantum operator. + + Operator must be square. + + Parameters + ---------- + sparse : bool + Use sparse eigenvalue/vector solver. + tol : float + Tolerance used by sparse solver (0 = machine precision). + maxiter : int + Maximum number of iterations used by sparse solver. + + Returns + ------- + oper : :class:`qutip.Qobj` + Matrix square root of operator. + + Raises + ------ + TypeError + Quantum object is not square. + + Notes + ----- + The sparse eigensolver is much slower than the dense version. + Use sparse only if memory requirements demand it. + + """ + if self.dims[0][0] == self.dims[1][0]: + evals, evecs = sp_eigs(self.data, self.isherm, sparse=sparse, + tol=tol, maxiter=maxiter) + numevals = len(evals) + dV = sp.spdiags(np.sqrt(evals, dtype=complex), 0, numevals, + numevals, format='csr') + if self.isherm: + spDv = dV.dot(evecs.T.conj().T) + else: + spDv = dV.dot(np.linalg.inv(evecs.T)) + + out = Qobj(evecs.T.dot(spDv), dims=self.dims) + return out.tidyup() if settings.auto_tidyup else out + + else: + raise TypeError('Invalid operand for matrix square root') + + + def cosm(self): + """Cosine of a quantum operator. + + Operator must be square. + + Returns + ------- + oper : :class:`qutip.Qobj` + Matrix cosine of operator. + + Raises + ------ + TypeError + Quantum object is not square. + + Notes + ----- + Uses the Q.expm() method. + + """ + if self.dims[0][0] == self.dims[1][0]: + return 0.5 * ((1j * self).expm() + (-1j * self).expm()) + else: + raise TypeError('Invalid operand for matrix square root') + + + def sinm(self): + """Sine of a quantum operator. + + Operator must be square. + + Returns + ------- + oper : :class:`qutip.Qobj` + Matrix sine of operator. + + Raises + ------ + TypeError + Quantum object is not square. + + Notes + ----- + Uses the Q.expm() method. + + """ + if self.dims[0][0] == self.dims[1][0]: + return -0.5j * ((1j * self).expm() - (-1j * self).expm()) + else: + raise TypeError('Invalid operand for matrix square root') + + + + def unit(self, inplace=False, + norm=None, sparse=False, + tol=0, maxiter=100000): + """Operator or state normalized to unity. + + Uses norm from Qobj.norm(). + + Parameters + ---------- + inplace : bool + Do an in-place normalization + norm : str + Requested norm for states / operators. + sparse : bool + Use sparse eigensolver for trace norm. Does not affect other norms. + tol : float + Tolerance used by sparse eigensolver. + maxiter : int + Number of maximum iterations performed by sparse eigensolver. + + Returns + ------- + oper : :class:`qutip.Qobj` + Normalized quantum object if not in-place, + else None. + + """ + if inplace: + nrm = self.norm(norm=norm, sparse=sparse, + tol=tol, maxiter=maxiter) + + self.data /= nrm + elif not inplace: + out = self / self.norm(norm=norm, sparse=sparse, + tol=tol, maxiter=maxiter) + if settings.auto_tidyup: + return out.tidyup() + else: + return out + else: + raise Exception('inplace kwarg must be bool.') + + def ptrace(self, sel): + """Partial trace of the quantum object. + + Parameters + ---------- + sel : int/list + An ``int`` or ``list`` of components to keep after partial trace. + + Returns + ------- + oper : :class:`qutip.Qobj` + Quantum object representing partial trace with selected components + remaining. + + Notes + ----- + This function is identical to the :func:`qutip.qobj.ptrace` function + that has been deprecated. + + """ + q = Qobj() + q.data, q.dims, _ = _ptrace(self, sel) + return q.tidyup() if settings.auto_tidyup else q + + def permute(self, order): + """Permutes a composite quantum object. + + Parameters + ---------- + order : list/array + List specifying new tensor order. + + Returns + ------- + P : :class:`qutip.Qobj` + Permuted quantum object. + + """ + q = Qobj() + q.data, q.dims = _permute(self, order) + return q.tidyup() if settings.auto_tidyup else q + + def tidyup(self, atol=settings.auto_tidyup_atol): + """Removes small elements from the quantum object. + + Parameters + ---------- + atol : float + Absolute tolerance used by tidyup. Default is set + via qutip global settings parameters. + + Returns + ------- + oper : :class:`qutip.Qobj` + Quantum object with small elements removed. + + """ + if self.data.nnz: + #This does the tidyup and returns True if + #The sparse data needs to be shortened + if use_openmp() and self.data.nnz > 500: + if omp_tidyup(self.data.data,atol,self.data.nnz, + settings.num_cpus): + self.data.eliminate_zeros() + else: + if cy_tidyup(self.data.data,atol,self.data.nnz): + self.data.eliminate_zeros() + return self + else: + return self + + def transform(self, inpt, inverse=False, sparse=True): + """Basis transform defined by input array. + + Input array can be a ``matrix`` defining the transformation, + or a ``list`` of kets that defines the new basis. + + + Parameters + ---------- + inpt : array_like + A ``matrix`` or ``list`` of kets defining the transformation. + inverse : bool + Whether to return inverse transformation. + sparse : bool + Use sparse matrices when possible. Can be slower. + + Returns + ------- + oper : :class:`qutip.Qobj` + Operator in new basis. + + Notes + ----- + This function is still in development. + + + """ + if isinstance(inpt, list) or (isinstance(inpt, np.ndarray) and + len(inpt.shape) == 1): + if len(inpt) != max(self.shape): + raise TypeError( + 'Invalid size of ket list for basis transformation') + if sparse: + S = sp.hstack([psi.data for psi in inpt], + format='csr', dtype=complex).conj().T + else: + S = np.hstack([psi.full() for psi in inpt], + dtype=complex).conj().T + elif isinstance(inpt, Qobj) and inpt.isoper: + S = inpt.data + elif isinstance(inpt, np.ndarray): + S = inpt.conj() + sparse = False + else: + raise TypeError('Invalid operand for basis transformation') + + + # transform data + if inverse: + if self.isket: + data = (S.conj().T) * self.data + elif self.isbra: + data = self.data.dot(S) + else: + if sparse: + data = (S.conj().T) * self.data * S + else: + data = (S.conj().T).dot(self.data.dot(S)) + else: + if self.isket: + data = S * self.data + elif self.isbra: + data = self.data.dot(S.conj().T) + else: + if sparse: + data = S * self.data * (S.conj().T) + else: + data = S.dot(self.data.dot(S.conj().T)) + + out = Qobj(data, dims=self.dims) + out._isherm = self._isherm + out.superrep = self.superrep + + if settings.auto_tidyup: + return out.tidyup() + else: + return out + + + + def trunc_neg(self, method="clip"): + """Truncates negative eigenvalues and renormalizes. + + Returns a new Qobj by removing the negative eigenvalues + of this instance, then renormalizing to obtain a valid density + operator. + + + Parameters + ---------- + method : str + Algorithm to use to remove negative eigenvalues. "clip" + simply discards negative eigenvalues, then renormalizes. + "sgs" uses the SGS algorithm (doi:10/bb76) to find the + positive operator that is nearest in the Shatten 2-norm. + + Returns + ------- + oper : :class:`qutip.Qobj` + A valid density operator. + + """ + if not self.isherm: + raise ValueError("Must be a Hermitian operator to remove negative " + "eigenvalues.") + + if method not in ('clip', 'sgs'): + raise ValueError("Method {} not recognized.".format(method)) + + eigvals, eigstates = self.eigenstates() + if all([eigval >= 0 for eigval in eigvals]): + # All positive, so just renormalize. + return self.unit() + idx_nonzero = eigvals != 0 + eigvals = eigvals[idx_nonzero] + eigstates = eigstates[idx_nonzero] + + if method == 'clip': + eigvals[eigvals < 0] = 0 + elif method == 'sgs': + eigvals = eigvals[::-1] + eigstates = eigstates[::-1] + + acc = 0.0 + dim = self.shape[0] + n_eigs = len(eigvals) + + for idx in reversed(range(n_eigs)): + if eigvals[idx] + acc / (idx + 1) >= 0: + break + else: + acc += eigvals[idx] + eigvals[idx] = 0.0 + + eigvals[:idx+1] += acc / (idx + 1) + + return sum([ + val * qutip.states.ket2dm(state) + for val, state in zip(eigvals, eigstates) + ], Qobj(np.zeros(self.shape), dims=self.dims) + ).unit() + + + def matrix_element(self, bra, ket): + """Calculates a matrix element. + + Gives the matrix element for the quantum object sandwiched between a + `bra` and `ket` vector. + + Parameters + ----------- + bra : :class:`qutip.Qobj` + Quantum object of type 'bra' or 'ket' + + ket : :class:`qutip.Qobj` + Quantum object of type 'ket'. + + Returns + ------- + elem : complex + Complex valued matrix element. + + Note + ---- + It is slightly more computationally efficient to use a ket + vector for the 'bra' input. + + """ + if not self.isoper: + raise TypeError("Can only get matrix elements for an operator.") + + else: + if bra.isbra and ket.isket: + return zcsr_mat_elem(self.data,bra.data,ket.data,1) + + elif bra.isket and ket.isket: + return zcsr_mat_elem(self.data,bra.data,ket.data,0) + else: + raise TypeError("Can only calculate matrix elements for bra and ket vectors.") + + def overlap(self, other): + """Overlap between two state vectors or two operators. + + Gives the overlap (inner product) between the current bra or ket Qobj + and and another bra or ket Qobj. It gives the Hilbert-Schmidt overlap + when one of the Qobj is an operator/density matrix. + + Parameters + ----------- + other : :class:`qutip.Qobj` + Quantum object for a state vector of type 'ket', 'bra' or density + matrix. + + Returns + ------- + overlap : complex + Complex valued overlap. + + Raises + ------ + TypeError + Can only calculate overlap between a bra, ket and density matrix + quantum objects. + + Notes + ----- + Since QuTiP mainly deals with ket vectors, the most efficient inner + product call is the ket-ket version that computes the product + with both vectors expressed as kets. + """ + + if isinstance(other, Qobj): + + if self.isbra: + if other.isket: + return zcsr_inner(self.data, other.data, 1) + elif other.isbra: + #Since we deal mainly with ket vectors, the bra-bra combo + #is not common, and not optimized. + return zcsr_inner(self.data, other.dag().data, 1) + elif other.isoper: + return (qutip.states.ket2dm(self).dag() * other).tr() + else: + raise TypeError("Can only calculate overlap for state vector Qobjs") + + elif self.isket: + if other.isbra: + return zcsr_inner(other.data, self.data, 1) + elif other.isket: + return zcsr_inner(self.data, other.data, 0) + elif other.isoper: + return (qutip.states.ket2dm(self).dag() * other).tr() + else: + raise TypeError("Can only calculate overlap for state vector Qobjs") + + elif self.isoper: + if other.isket or other.isbra: + return (self.dag() * qutip.states.ket2dm(other)).tr() + elif other.isoper: + return (self.dag() * other).tr() + else: + raise TypeError("Can only calculate overlap for state vector Qobjs") + + + raise TypeError("Can only calculate overlap for state vector Qobjs") + + + def eigenstates(self, sparse=False, sort='low', + eigvals=0, tol=0, maxiter=100000): + """Eigenstates and eigenenergies. + + Eigenstates and eigenenergies are defined for operators and + superoperators only. + + Parameters + ---------- + sparse : bool + Use sparse Eigensolver + + sort : str + Sort eigenvalues (and vectors) 'low' to high, or 'high' to low. + + eigvals : int + Number of requested eigenvalues. Default is all eigenvalues. + + tol : float + Tolerance used by sparse Eigensolver (0 = machine precision). + The sparse solver may not converge if the tolerance is set too low. + + maxiter : int + Maximum number of iterations performed by sparse solver (if used). + + Returns + ------- + eigvals : array + Array of eigenvalues for operator. + + eigvecs : array + Array of quantum operators representing the oprator eigenkets. + Order of eigenkets is determined by order of eigenvalues. + + Notes + ----- + The sparse eigensolver is much slower than the dense version. + Use sparse only if memory requirements demand it. + + """ + evals, evecs = sp_eigs(self.data, self.isherm, sparse=sparse, + sort=sort, eigvals=eigvals, tol=tol, + maxiter=maxiter) + new_dims = [self.dims[0], [1] * len(self.dims[0])] + ekets = np.array([Qobj(vec, dims=new_dims) for vec in evecs], + dtype=object) + norms = np.array([ket.norm() for ket in ekets]) + return evals, ekets / norms + + + def eigenenergies(self, sparse=False, sort='low', + eigvals=0, tol=0, maxiter=100000): + """Eigenenergies of a quantum object. + + Eigenenergies (eigenvalues) are defined for operators or superoperators + only. + + Parameters + ---------- + sparse : bool + Use sparse Eigensolver + sort : str + Sort eigenvalues 'low' to high, or 'high' to low. + eigvals : int + Number of requested eigenvalues. Default is all eigenvalues. + tol : float + Tolerance used by sparse Eigensolver (0=machine precision). + The sparse solver may not converge if the tolerance is set too low. + maxiter : int + Maximum number of iterations performed by sparse solver (if used). + + Returns + ------- + eigvals : array + Array of eigenvalues for operator. + + Notes + ----- + The sparse eigensolver is much slower than the dense version. + Use sparse only if memory requirements demand it. + + """ + return sp_eigs(self.data, self.isherm, vecs=False, sparse=sparse, + sort=sort, eigvals=eigvals, tol=tol, maxiter=maxiter) + + def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): + """Ground state Eigenvalue and Eigenvector. + + Defined for quantum operators or superoperators only. + + Parameters + ---------- + sparse : bool + Use sparse Eigensolver + tol : float + Tolerance used by sparse Eigensolver (0 = machine precision). + The sparse solver may not converge if the tolerance is set too low. + maxiter : int + Maximum number of iterations performed by sparse solver (if used). + safe : bool (default=True) + Check for degenerate ground state + + Returns + ------- + eigval : float + Eigenvalue for the ground state of quantum operator. + eigvec : :class:`qutip.Qobj` + Eigenket for the ground state of quantum operator. + + Notes + ----- + The sparse eigensolver is much slower than the dense version. + Use sparse only if memory requirements demand it. + + """ + if safe: + evals = 2 + else: + evals = 1 + grndval, grndvec = sp_eigs(self.data, self.isherm, sparse=sparse, + eigvals=evals, tol=tol, maxiter=maxiter) + if safe: + if tol == 0: tol = 1e-15 + if (grndval[1]-grndval[0]) <= 10*tol: + print("WARNING: Ground state may be degenerate. " + "Use Q.eigenstates()") + new_dims = [self.dims[0], [1] * len(self.dims[0])] + grndvec = Qobj(grndvec[0], dims=new_dims) + grndvec = grndvec / grndvec.norm() + return grndval[0], grndvec + + def trans(self): + """Transposed operator. + + Returns + ------- + oper : :class:`qutip.Qobj` + Transpose of input operator. + + """ + out = Qobj() + out.data = zcsr_transpose(self.data) + out.dims = [self.dims[1], self.dims[0]] + return out + + def extract_states(self, states_inds, normalize=False): + """Qobj with states in state_inds only. + + Parameters + ---------- + states_inds : list of integer + The states that should be kept. + + normalize : True / False + Weather or not the new Qobj instance should be normalized (default + is False). For Qobjs that represents density matrices or state + vectors normalized should probably be set to True, but for Qobjs + that represents operators in for example an Hamiltonian, normalize + should be False. + + Returns + ------- + q : :class:`qutip.Qobj` + A new instance of :class:`qutip.Qobj` that contains only the states + corresponding to the indices in `state_inds`. + + Notes + ----- + Experimental. + + """ + if self.isoper: + q = Qobj(self.data[states_inds, :][:, states_inds]) + elif self.isket: + q = Qobj(self.data[states_inds, :]) + elif self.isbra: + q = Qobj(self.data[:, states_inds]) + else: + raise TypeError("Can only eliminate states from operators or " + + "state vectors") + + return q.unit() if normalize else q + + def eliminate_states(self, states_inds, normalize=False): + """Creates a new quantum object with states in state_inds eliminated. + + Parameters + ---------- + states_inds : list of integer + The states that should be removed. + + normalize : True / False + Weather or not the new Qobj instance should be normalized (default + is False). For Qobjs that represents density matrices or state + vectors normalized should probably be set to True, but for Qobjs + that represents operators in for example an Hamiltonian, normalize + should be False. + + Returns + ------- + q : :class:`qutip.Qobj` + A new instance of :class:`qutip.Qobj` that contains only the states + corresponding to indices that are **not** in `state_inds`. + + Notes + ----- + Experimental. + + """ + keep_indices = np.array([s not in states_inds + for s in range(self.shape[0])]).nonzero()[0] + + return self.extract_states(keep_indices, normalize=normalize) + + def dnorm(self, B=None): + """Calculates the diamond norm, or the diamond distance to another + operator. + + Parameters + ---------- + B : :class:`qutip.Qobj` or None + If B is not None, the diamond distance d(A, B) = dnorm(A - B) between + this operator and B is returned instead of the diamond norm. + + Returns + ------- + d : float + Either the diamond norm of this operator, or the diamond distance + from this operator to B. + + """ + return mts.dnorm(self, B) + + + @property + def ishp(self): + # FIXME: this needs to be cached in the same ways as isherm. + if self.type in ["super", "oper"]: + try: + J = sr.to_choi(self) + return J.isherm + except: + return False + else: + return False + + @property + def iscp(self): + # FIXME: this needs to be cached in the same ways as isherm. + if self.type in ["super", "oper"]: + try: + J = ( + self + # We can test with either Choi or chi, since the basis + # transformation between them is unitary and hence + # preserves the CP and TP conditions. + if self.superrep in ('choi', 'chi') + else sr.to_choi(self) + ) + # If J isn't hermitian, then that could indicate either + # that J is not normal, or is normal, but has complex eigenvalues. + # In either case, it makes no sense to then demand that the + # eigenvalues be non-negative. + if not J.isherm: + return False + eigs = J.eigenenergies() + return all(eigs >= -settings.atol) + except: + return False + else: + return False + + @property + def istp(self): + import qutip.superop_reps as sr + if self.type in ["super", "oper"]: + try: + # Normalize to a super of type choi or chi. + # We can test with either Choi or chi, since the basis + # transformation between them is unitary and hence + # preserves the CP and TP conditions. + if self.type == "super" and self.superrep in ('choi', 'chi'): + qobj = self + else: + qobj = sr.to_choi(self) + + # Possibly collapse dims. + if any([len(index) > 1 for super_index in qobj.dims + for index in super_index]): + qobj = Qobj(qobj, dims=collapse_dims_super(qobj.dims)) + else: + qobj = qobj + + # We use the condition from John Watrous' lecture notes, + # Tr_1(J(Phi)) = identity_2. + tr_oper = qobj.ptrace([0]) + ident = ops.identity(tr_oper.shape[0]) + return isequal(tr_oper, ident) + except: + return False + else: + return False + + @property + def iscptp(self): + from qutip.superop_reps import to_choi + if self.type == "super" or self.type == "oper": + reps = ('choi', 'chi') + q_oper = to_choi(self) if self.superrep not in reps else self + return q_oper.iscp and q_oper.istp + else: + return False + + @property + def isherm(self): + + if self._isherm is not None: + # used previously computed value + return self._isherm + + self._isherm = bool(zcsr_isherm(self.data)) + + return self._isherm + + @isherm.setter + def isherm(self, isherm): + self._isherm = isherm + + def check_isunitary(self): + """ + Checks whether qobj is a unitary matrix + """ + if self.isoper: + eye_data = fast_identity(self.shape[0]) + return not (np.any(np.abs((self.data*self.dag().data + - eye_data).data) + > settings.atol) + or + np.any(np.abs((self.dag().data*self.data + - eye_data).data) > + settings.atol) + ) + + else: + return False + + @property + def isunitary(self): + if self._isunitary is not None: + # used previously computed value + return self._isunitary + + self._isunitary = self.check_isunitary() + + return self._isunitary + + @isunitary.setter + def isunitary(self, isunitary): + self._isunitary = isunitary + + @property + def type(self): + if not self._type: + self._type = type_from_dims(self.dims) + + return self._type + + @property + def shape(self): + if self.data.shape == (1, 1): + return tuple([np.prod(self.dims[0]), np.prod(self.dims[1])]) + else: + return tuple(self.data.shape) + + @property + def isbra(self): + return self.type == 'bra' + + @property + def isket(self): + return self.type == 'ket' + + @property + def isoperbra(self): + return self.type == 'operator-bra' + + @property + def isoperket(self): + return self.type == 'operator-ket' + + @property + def isoper(self): + return self.type == 'oper' + + @property + def issuper(self): + return self.type == 'super' + + @staticmethod + def evaluate(qobj_list, t, args): + """Evaluate a time-dependent quantum object in list format. For + example, + + qobj_list = [H0, [H1, func_t]] + + is evaluated to + + Qobj(t) = H0 + H1 * func_t(t, args) + + and + + qobj_list = [H0, [H1, 'sin(w * t)']] + + is evaluated to + + Qobj(t) = H0 + H1 * sin(args['w'] * t) + + Parameters + ---------- + qobj_list : list + A nested list of Qobj instances and corresponding time-dependent + coefficients. + t : float + The time for which to evaluate the time-dependent Qobj instance. + args : dictionary + A dictionary with parameter values required to evaluate the + time-dependent Qobj intance. + + Returns + ------- + output : :class:`qutip.Qobj` + A Qobj instance that represents the value of qobj_list at time t. + + """ + + q_sum = 0 + if isinstance(qobj_list, Qobj): + q_sum = qobj_list + elif isinstance(qobj_list, list): + for q in qobj_list: + if isinstance(q, Qobj): + q_sum += q + elif (isinstance(q, list) and len(q) == 2 and + isinstance(q[0], Qobj)): + if isinstance(q[1], types.FunctionType): + q_sum += q[0] * q[1](t, args) + elif isinstance(q[1], str): + args['t'] = t + q_sum += q[0] * float(eval(q[1], globals(), args)) + else: + raise TypeError('Unrecognized format for ' + + 'specification of time-dependent Qobj') + else: + raise TypeError('Unrecognized format for specification ' + + 'of time-dependent Qobj') + else: + raise TypeError( + 'Unrecongized format for specification of time-dependent Qobj') + + return q_sum + + +# ----------------------------------------------------------------------------- +# This functions evaluates a time-dependent quantum object on the list-string +# and list-function formats that are used by the time-dependent solvers. +# Although not used directly in by those solvers, it can for test purposes be +# conventient to be able to evaluate the expressions passed to the solver for +# arbitrary value of time. This function provides this functionality. +# +def qobj_list_evaluate(qobj_list, t, args): + """ + Depracated: See Qobj.evaluate + """ + warnings.warn("Deprecated: Use Qobj.evaluate", DeprecationWarning) + return Qobj.evaluate(qobj_list, t, args) + + +# ----------------------------------------------------------------------------- +# +# A collection of tests used to determine the type of quantum objects, and some +# functions for increased compatibility with quantum optics toolbox. +# + +def dag(A): + """Adjont operator (dagger) of a quantum object. + + Parameters + ---------- + A : :class:`qutip.Qobj` + Input quantum object. + + Returns + ------- + oper : :class:`qutip.Qobj` + Adjoint of input operator + + Notes + ----- + This function is for legacy compatibility only. It is recommended to use + the ``dag()`` Qobj method. + + """ + if not isinstance(A, Qobj): + raise TypeError("Input is not a quantum object") + + return A.dag() + + +def ptrace(Q, sel): + """Partial trace of the Qobj with selected components remaining. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Composite quantum object. + sel : int/list + An ``int`` or ``list`` of components to keep after partial trace. + + Returns + ------- + oper : :class:`qutip.Qobj` + Quantum object representing partial trace with selected components + remaining. + + Notes + ----- + This function is for legacy compatibility only. It is recommended to use + the ``ptrace()`` Qobj method. + + """ + if not isinstance(Q, Qobj): + raise TypeError("Input is not a quantum object") + + return Q.ptrace(sel) + + +def dims(inpt): + """Returns the dims attribute of a quantum object. + + Parameters + ---------- + inpt : :class:`qutip.Qobj` + Input quantum object. + + Returns + ------- + dims : list + A ``list`` of the quantum objects dimensions. + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.dims` + attribute is recommended. + + """ + if isinstance(inpt, Qobj): + return inpt.dims + else: + raise TypeError("Input is not a quantum object") + + +def shape(inpt): + """Returns the shape attribute of a quantum object. + + Parameters + ---------- + inpt : :class:`qutip.Qobj` + Input quantum object. + + Returns + ------- + shape : list + A ``list`` of the quantum objects shape. + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.shape` + attribute is recommended. + + """ + if isinstance(inpt, Qobj): + return Qobj.shape + else: + return np.shape(inpt) + + +def isket(Q): + """ + Determines if given quantum object is a ket-vector. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isket : bool + True if qobj is ket-vector, False otherwise. + + Examples + -------- + >>> psi = basis(5,2) + >>> isket(psi) + True + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isket` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isket else False + + +def isbra(Q): + """Determines if given quantum object is a bra-vector. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isbra : bool + True if Qobj is bra-vector, False otherwise. + + Examples + -------- + >>> psi = basis(5,2) + >>> isket(psi) + False + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isbra` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isbra else False + + +def isoperket(Q): + """Determines if given quantum object is an operator in column vector form + (operator-ket). + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isoperket : bool + True if Qobj is operator-ket, False otherwise. + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isoperket` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isoperket else False + + +def isoperbra(Q): + """Determines if given quantum object is an operator in row vector form + (operator-bra). + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isoperbra : bool + True if Qobj is operator-bra, False otherwise. + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isoperbra` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isoperbra else False + + +def isoper(Q): + """Determines if given quantum object is a operator. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isoper : bool + True if Qobj is operator, False otherwise. + + Examples + -------- + >>> a = destroy(5) + >>> isoper(a) + True + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isoper` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isoper else False + + +def issuper(Q): + """Determines if given quantum object is a super-operator. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + issuper : bool + True if Qobj is superoperator, False otherwise. + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.issuper` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.issuper else False + + +def isequal(A, B, tol=None): + """Determines if two qobj objects are equal to within given tolerance. + + Parameters + ---------- + A : :class:`qutip.Qobj` + Qobj one + B : :class:`qutip.Qobj` + Qobj two + tol : float + Tolerence for equality to be valid + + Returns + ------- + isequal : bool + True if qobjs are equal, False otherwise. + + Notes + ----- + This function is for legacy compatibility only. Instead, it is recommended + to use the equality operator of Qobj instances instead: A == B. + + """ + if tol is None: + tol = settings.atol + + if not isinstance(A, Qobj) or not isinstance(B, Qobj): + return False + + if A.dims != B.dims: + return False + + Adat = A.data + Bdat = B.data + elems = (Adat - Bdat).data + if np.any(np.abs(elems) > tol): + return False + + return True + + +def isherm(Q): + """Determines if given operator is Hermitian. + + Parameters + ---------- + Q : :class:`qutip.Qobj` + Quantum object + + Returns + ------- + isherm : bool + True if operator is Hermitian, False otherwise. + + Examples + -------- + >>> a = destroy(4) + >>> isherm(a) + False + + Notes + ----- + This function is for legacy compatibility only. Using the `Qobj.isherm` + attribute is recommended. + + """ + return True if isinstance(Q, Qobj) and Q.isherm else False + + +# TRAILING IMPORTS +# We do a few imports here to avoid circular dependencies. +from qutip.eseries import eseries +import qutip.superop_reps as sr +import qutip.tensor as tensor +import qutip.operators as ops +import qutip.metrics as mts +import qutip.states +import qutip.superoperator diff --git a/qiskit/providers/aer/openpulse/qutip_lite/settings.py b/qiskit/providers/aer/openpulse/qutip_lite/settings.py new file mode 100755 index 0000000000..2f9b37e85f --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/settings.py @@ -0,0 +1,86 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +This module contains settings for the QuTiP graphics, multiprocessing, and +tidyup functionality, etc. +""" +from __future__ import absolute_import +# use auto tidyup +auto_tidyup = True +# use auto tidyup dims on multiplication +auto_tidyup_dims = True +# detect hermiticity +auto_herm = True +# general absolute tolerance +atol = 1e-12 +# use auto tidyup absolute tolerance +auto_tidyup_atol = 1e-12 +# number of cpus (set at qutip import) +num_cpus = 0 +# flag indicating if fortran module is installed +fortran = False +# path to the MKL library +mkl_lib = None +# Flag if mkl_lib is found +has_mkl = False +# Has OPENMP +has_openmp = False +# debug mode for development +debug = False +# are we in IPython? Note that this cannot be +# set by the RC file. +ipython = False +# define whether log handler should be +# - default: switch based on IPython detection +# - stream: set up non-propagating StreamHandler +# - basic: call basicConfig +# - null: leave logging to the user +log_handler = 'default' +# Allow for a colorblind mode that uses different colormaps +# and plotting options by default. +colorblind_safe = False +# Sets the threshold for matrix NNZ where OPENMP +# turns on. This is automatically calculated and +# put in the qutiprc file. This value is here in case +# that failts +openmp_thresh = 10000 +# Note that since logging depends on settings, +# if we want to do any logging here, it must be manually +# configured, rather than through _logging.get_logger(). +try: + import logging + _logger = logging.getLogger(__name__) + _logger.addHandler(logging.NullHandler()) + del logging # Don't leak names! +except: + _logger = None diff --git a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py new file mode 100755 index 0000000000..f0d9e8e789 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py @@ -0,0 +1,591 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +This module contains a collection of routines for operating on sparse +matrices on the scipy.sparse formats, for use internally by other modules +throughout QuTiP. +""" + +__all__ = ['sp_fro_norm', 'sp_inf_norm', 'sp_L2_norm', 'sp_max_norm', + 'sp_one_norm', 'sp_reshape', 'sp_eigs', 'sp_expm', 'sp_permute', + 'sp_reverse_permute', 'sp_bandwidth', 'sp_profile'] + +import scipy.sparse as sp +import scipy.sparse.linalg as spla +import numpy as np +import scipy.linalg as la +from scipy.linalg.blas import get_blas_funcs +_dznrm2 = get_blas_funcs("znrm2") +from qutip.cy.sparse_utils import (_sparse_profile, _sparse_permute, + _sparse_reverse_permute, _sparse_bandwidth, + _isdiag, zcsr_one_norm, zcsr_inf_norm) +from qutip.fastsparse import fast_csr_matrix +from qutip.cy.spconvert import (arr_coo2fast, zcsr_reshape) +from qutip.settings import debug + +import qutip.logging_utils +logger = qutip.logging_utils.get_logger() + +if debug: + import inspect + + +def sp_fro_norm(data): + """ + Frobius norm for sparse matrix + """ + out = np.sum(np.abs(data.data)**2) + return np.sqrt(out) + + +def sp_inf_norm(A): + """ + Infinity norm for sparse matrix + """ + return zcsr_inf_norm(A.data, A.indices, + A.indptr, A.shape[0], A.shape[1]) + + +def sp_L2_norm(A): + """ + L2 norm sparse vector + """ + if 1 not in A.shape: + raise TypeError("Use L2-norm only for vectors.") + + if len(A.data): + return _dznrm2(A.data) + else: + return 0 + + +def sp_max_norm(A): + """ + Max norm for sparse matrix + """ + return np.max(np.abs(A.data)) if any(A.data) else 0 + + +def sp_one_norm(A): + """ + One norm for sparse matrix + """ + return zcsr_one_norm(A.data, A.indices, + A.indptr, A.shape[0], A.shape[1]) + + +def sp_reshape(A, shape, format='csr'): + """ + Reshapes a sparse matrix. + + Parameters + ---------- + A : sparse_matrix + Input matrix in any format + shape : list/tuple + Desired shape of new matrix + format : string {'csr','coo','csc','lil'} + Optional string indicating desired output format + + Returns + ------- + B : csr_matrix + Reshaped sparse matrix + + References + ---------- + + http://stackoverflow.com/questions/16511879/reshape-sparse-matrix-efficiently-python-scipy-0-12 + + """ + if not hasattr(shape, '__len__') or len(shape) != 2: + raise ValueError('Shape must be a list of two integers') + + if format == 'csr': + return zcsr_reshape(A, shape[0], shape[1]) + + C = A.tocoo() + nrows, ncols = C.shape + size = nrows * ncols + new_size = shape[0] * shape[1] + + if new_size != size: + raise ValueError('Total size of new array must be unchanged.') + + flat_indices = ncols * C.row + C.col + new_row, new_col = divmod(flat_indices, shape[1]) + + B = sp.coo_matrix((C.data, (new_row, new_col)), shape=shape) + if format == 'coo': + return B + elif format == 'csc': + return B.tocsc() + elif format == 'lil': + return B.tolil() + else: + raise ValueError('Return format not valid.') + + +def _dense_eigs(data, isherm, vecs, N, eigvals, num_large, num_small): + """ + Internal functions for computing eigenvalues and eigenstates for a dense + matrix. + """ + if debug: + logger.debug(inspect.stack()[0][3] + ": vectors = " + str(vecs)) + + evecs = None + + if vecs: + if isherm: + if eigvals == 0: + evals, evecs = la.eigh(data) + else: + if num_small > 0: + evals, evecs = la.eigh( + data, eigvals=[0, num_small - 1]) + if num_large > 0: + evals, evecs = la.eigh( + data, eigvals=[N - num_large, N - 1]) + else: + evals, evecs = la.eig(data) + else: + if isherm: + if eigvals == 0: + evals = la.eigvalsh(data) + else: + if num_small > 0: + evals = la.eigvalsh(data, eigvals=[0, num_small - 1]) + if num_large > 0: + evals = la.eigvalsh(data, eigvals=[N - num_large, N - 1]) + else: + evals = la.eigvals(data) + + _zipped = list(zip(evals, range(len(evals)))) + _zipped.sort() + evals, perm = list(zip(*_zipped)) + + if vecs: + evecs = np.array([evecs[:, k] for k in perm]) + + if not isherm and eigvals > 0: + if vecs: + if num_small > 0: + evals, evecs = evals[:num_small], evecs[:num_small] + elif num_large > 0: + evals, evecs = evals[(N - num_large):], evecs[(N - num_large):] + else: + if num_small > 0: + evals = evals[:num_small] + elif num_large > 0: + evals = evals[(N - num_large):] + + return np.array(evals), np.array(evecs) + + +def _sp_eigs(data, isherm, vecs, N, eigvals, num_large, num_small, tol, + maxiter): + """ + Internal functions for computing eigenvalues and eigenstates for a sparse + matrix. + """ + if debug: + print(inspect.stack()[0][3] + ": vectors = " + str(vecs)) + + big_vals = np.array([]) + small_vals = np.array([]) + evecs = None + + remove_one = False + if eigvals == (N - 1): + # calculate all eigenvalues and remove one at output if using sparse + eigvals = 0 + num_small = int(np.ceil(N / 2.0)) + num_large = N - num_small + remove_one = True + + if vecs: + if isherm: + if num_large > 0: + big_vals, big_vecs = sp.linalg.eigsh(data, k=num_large, + which='LA', tol=tol, + maxiter=maxiter) + big_vecs = sp.csr_matrix(big_vecs, dtype=complex) + if num_small > 0: + small_vals, small_vecs = sp.linalg.eigsh( + data, k=num_small, which='SA', + tol=tol, maxiter=maxiter) + + else: + if num_large > 0: + big_vals, big_vecs = sp.linalg.eigs(data, k=num_large, + which='LR', tol=tol, + maxiter=maxiter) + big_vecs = sp.csr_matrix(big_vecs, dtype=complex) + if num_small > 0: + small_vals, small_vecs = sp.linalg.eigs( + data, k=num_small, which='SR', + tol=tol, maxiter=maxiter) + + if num_large != 0 and num_small != 0: + evecs = sp.hstack([small_vecs, big_vecs], format='csr') + elif num_large != 0 and num_small == 0: + evecs = big_vecs + elif num_large == 0 and num_small != 0: + evecs = small_vecs + else: + if isherm: + if num_large > 0: + big_vals = sp.linalg.eigsh( + data, k=num_large, which='LA', + return_eigenvectors=False, tol=tol, maxiter=maxiter) + if num_small > 0: + small_vals = sp.linalg.eigsh( + data, k=num_small, which='SA', + return_eigenvectors=False, tol=tol, maxiter=maxiter) + else: + if num_large > 0: + big_vals = sp.linalg.eigs( + data, k=num_large, which='LR', + return_eigenvectors=False, tol=tol, maxiter=maxiter) + if num_small > 0: + small_vals = sp.linalg.eigs( + data, k=num_small, which='SR', + return_eigenvectors=False, tol=tol, maxiter=maxiter) + + evals = np.hstack((small_vals, big_vals)) + if isherm: + evals = np.real(evals) + + _zipped = list(zip(evals, range(len(evals)))) + _zipped.sort() + evals, perm = list(zip(*_zipped)) + + if vecs: + evecs = np.array([evecs[:, k] for k in perm]) + + # remove last element if requesting N-1 eigs and using sparse + if remove_one: + evals = np.delete(evals, -1) + if vecs: + evecs = np.delete(evecs, -1) + + return np.array(evals), np.array(evecs) + + +def sp_eigs(data, isherm, vecs=True, sparse=False, sort='low', + eigvals=0, tol=0, maxiter=100000): + """Returns Eigenvalues and Eigenvectors for a sparse matrix. + Uses dense eigen-solver unless user sets sparse=True. + + Parameters + ---------- + data : csr_matrix + Input matrix + isherm : bool + Indicate whether the matrix is hermitian or not + vecs : bool {True , False} + Flag for requesting eigenvectors + sparse : bool {False , True} + Flag to use sparse solver + sort : str {'low' , 'high} + Return lowest or highest eigenvals/vecs + eigvals : int + Number of eigenvals/vecs to return. Default = 0 (return all) + tol : float + Tolerance for sparse eigensolver. Default = 0 (Machine precision) + maxiter : int + Max. number of iterations used by sparse sigensolver. + + Returns + ------- + Array of eigenvalues and (by default) array of corresponding Eigenvectors. + + """ + + if debug: + print(inspect.stack()[0][3]) + + if data.shape[0] != data.shape[1]: + raise TypeError("Can only diagonalize square matrices") + + N = data.shape[0] + if eigvals == N: + eigvals = 0 + + if eigvals > N: + raise ValueError("Number of requested eigen vals/vecs must be <= N.") + + # set number of large and small eigenvals/vecs + if eigvals == 0: # user wants all eigs (default) + D = int(np.ceil(N / 2.0)) + num_large = N - D + if not np.mod(N, 2): + M = D + else: + M = D - 1 + num_small = N - M + else: # if user wants only a few eigen vals/vecs + if sort == 'low': + num_small = eigvals + num_large = 0 + elif sort == 'high': + num_large = eigvals + num_small = 0 + else: + raise ValueError("Invalid option for 'sort'.") + + # Dispatch to sparse/dense solvers + if sparse: + evals, evecs = _sp_eigs(data, isherm, vecs, N, eigvals, num_large, + num_small, tol, maxiter) + else: + evals, evecs = _dense_eigs(data.todense(), isherm, vecs, N, eigvals, + num_large, num_small) + + if sort == 'high': # flip arrays to largest values first + if vecs: + evecs = np.flipud(evecs) + evals = np.flipud(evals) + + return (evals, evecs) if vecs else evals + + +def sp_expm(A, sparse=False): + """ + Sparse matrix exponential. + """ + if _isdiag(A.indices, A.indptr, A.shape[0]): + A = sp.diags(np.exp(A.diagonal()), shape=A.shape, + format='csr', dtype=complex) + return A + if sparse: + E = spla.expm(A.tocsc()) + else: + E = spla.expm(A.toarray()) + return sp.csr_matrix(E) + + + +def sp_permute(A, rperm=(), cperm=(), safe=True): + """ + Permutes the rows and columns of a sparse CSR/CSC matrix + according to the permutation arrays rperm and cperm, respectively. + Here, the permutation arrays specify the new order of the rows and + columns. i.e. [0,1,2,3,4] -> [3,0,4,1,2]. + + Parameters + ---------- + A : csr_matrix, csc_matrix + Input matrix. + rperm : array_like of integers + Array of row permutations. + cperm : array_like of integers + Array of column permutations. + safe : bool + Check structure of permutation arrays. + + Returns + ------- + perm_csr : csr_matrix, csc_matrix + CSR or CSC matrix with permuted rows/columns. + + """ + rperm = np.asarray(rperm, dtype=np.int32) + cperm = np.asarray(cperm, dtype=np.int32) + nrows = A.shape[0] + ncols = A.shape[1] + if len(rperm) == 0: + rperm = np.arange(nrows, dtype=np.int32) + if len(cperm) == 0: + cperm = np.arange(ncols, dtype=np.int32) + if safe: + if len(np.setdiff1d(rperm, np.arange(nrows))) != 0: + raise Exception('Invalid row permutation array.') + if len(np.setdiff1d(cperm, np.arange(ncols))) != 0: + raise Exception('Invalid column permutation array.') + + shp = A.shape + kind = A.getformat() + if kind == 'csr': + flag = 0 + elif kind == 'csc': + flag = 1 + else: + raise Exception('Input must be Qobj, CSR, or CSC matrix.') + + data, ind, ptr = _sparse_permute(A.data, A.indices, A.indptr, + nrows, ncols, rperm, cperm, flag) + if kind == 'csr': + return fast_csr_matrix((data, ind, ptr), shape=shp) + elif kind == 'csc': + return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) + + +def sp_reverse_permute(A, rperm=(), cperm=(), safe=True): + """ + Performs a reverse permutations of the rows and columns of a sparse CSR/CSC + matrix according to the permutation arrays rperm and cperm, respectively. + Here, the permutation arrays specify the order of the rows and columns used + to permute the original array. + + Parameters + ---------- + A : csr_matrix, csc_matrix + Input matrix. + rperm : array_like of integers + Array of row permutations. + cperm : array_like of integers + Array of column permutations. + safe : bool + Check structure of permutation arrays. + + Returns + ------- + perm_csr : csr_matrix, csc_matrix + CSR or CSC matrix with permuted rows/columns. + + """ + rperm = np.asarray(rperm, dtype=np.int32) + cperm = np.asarray(cperm, dtype=np.int32) + nrows = A.shape[0] + ncols = A.shape[1] + if len(rperm) == 0: + rperm = np.arange(nrows, dtype=np.int32) + if len(cperm) == 0: + cperm = np.arange(ncols, dtype=np.int32) + if safe: + if len(np.setdiff1d(rperm, np.arange(nrows))) != 0: + raise Exception('Invalid row permutation array.') + if len(np.setdiff1d(cperm, np.arange(ncols))) != 0: + raise Exception('Invalid column permutation array.') + + shp = A.shape + kind = A.getformat() + if kind == 'csr': + flag = 0 + elif kind == 'csc': + flag = 1 + else: + raise Exception('Input must be Qobj, CSR, or CSC matrix.') + + data, ind, ptr = _sparse_reverse_permute(A.data, A.indices, A.indptr, + nrows, ncols, rperm, cperm, flag) + + if kind == 'csr': + return fast_csr_matrix((data, ind, ptr), shape=shp) + elif kind == 'csc': + return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) + + +def sp_bandwidth(A): + """ + Returns the max(mb), lower(lb), and upper(ub) bandwidths of a + sparse CSR/CSC matrix. + + If the matrix is symmetric then the upper and lower bandwidths are + identical. Diagonal matrices have a bandwidth equal to one. + + Parameters + ---------- + A : csr_matrix, csc_matrix + Input matrix + + Returns + ------- + mb : int + Maximum bandwidth of matrix. + lb : int + Lower bandwidth of matrix. + ub : int + Upper bandwidth of matrix. + + """ + nrows = A.shape[0] + ncols = A.shape[1] + + if A.getformat() == 'csr': + return _sparse_bandwidth(A.indices, A.indptr, nrows) + elif A.getformat() == 'csc': + # Normal output is mb,lb,ub but since CSC + # is transpose of CSR switch lb and ub + mb, ub, lb = _sparse_bandwidth(A.indices, A.indptr, ncols) + return mb, lb, ub + else: + raise Exception('Invalid sparse input format.') + + +def sp_profile(A): + """Returns the total, lower, and upper profiles of a sparse matrix. + + If the matrix is symmetric then the upper and lower profiles are + identical. Diagonal matrices have zero profile. + + Parameters + ---------- + A : csr_matrix, csc_matrix + Input matrix + """ + if sp.isspmatrix_csr(A): + up = _sparse_profile(A.indices, A.indptr, A.shape[0]) + A = A.tocsc() + lp = _sparse_profile(A.indices, A.indptr, A.shape[0]) + + elif sp.isspmatrix_csc(A): + lp = _sparse_profile(A.indices, A.indptr, A.shape[0]) + A = A.tocsr() + up = _sparse_profile(A.indices, A.indptr, A.shape[0]) + + else: + raise TypeError('Input sparse matrix must be in CSR or CSC format.') + + return up+lp, lp, up + + +def sp_isdiag(A): + """Determine if sparse CSR matrix is diagonal. + + Parameters + ---------- + A : csr_matrix, csc_matrix + Input matrix + + Returns + ------- + isdiag : int + True if matix is diagonal, False otherwise. + + """ + if not sp.isspmatrix_csr(A): + raise TypeError('Input sparse matrix must be in CSR format.') + return _isdiag(A.indices, A.indptr, A.shape[0]) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/states.py b/qiskit/providers/aer/openpulse/qutip_lite/states.py new file mode 100755 index 0000000000..ea6d9d0001 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/states.py @@ -0,0 +1,1213 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +__all__ = ['basis', 'qutrit_basis', 'coherent', 'coherent_dm', 'fock_dm', + 'fock', 'thermal_dm', 'maximally_mixed_dm', 'ket2dm', 'projection', + 'qstate', 'ket', 'bra', 'state_number_enumerate', + 'state_number_index', 'state_index_number', 'state_number_qobj', + 'phase_basis', 'zero_ket', 'spin_state', 'spin_coherent', + 'bell_state', 'singlet_state', 'triplet_states', 'w_state', + 'ghz_state', 'enr_state_dictionaries', 'enr_fock', + 'enr_thermal_dm'] + +import numpy as np +from scipy import arange, conj, prod +import scipy.sparse as sp + +from qutip.qobj import Qobj +from qutip.operators import destroy, jmat +from qutip.tensor import tensor + +from qutip.fastsparse import fast_csr_matrix + + +def basis(N, n=0, offset=0): + """Generates the vector representation of a Fock state. + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + n : int + Integer corresponding to desired number state, defaults + to 0 if omitted. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the state. + + Returns + ------- + state : :class:`qutip.Qobj` + Qobj representing the requested number state ``|n>``. + + Examples + -------- + >>> basis(5,2) + Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket + Qobj data = + [[ 0.+0.j] + [ 0.+0.j] + [ 1.+0.j] + [ 0.+0.j] + [ 0.+0.j]] + + Notes + ----- + + A subtle incompatibility with the quantum optics toolbox: In QuTiP:: + + basis(N, 0) = ground state + + but in the qotoolbox:: + + basis(N, 1) = ground state + + """ + if (not isinstance(N, (int, np.integer))) or N < 0: + raise ValueError("N must be integer N >= 0") + + if (not isinstance(n, (int, np.integer))) or n < offset: + raise ValueError("n must be integer n >= 0") + + if n - offset > (N - 1): # check if n is within bounds + raise ValueError("basis vector index need to be in n <= N-1") + + data = np.array([1], dtype=complex) + ind = np.array([0], dtype=np.int32) + ptr = np.array([0]*((n - offset)+1)+[1]*(N-(n-offset)),dtype=np.int32) + + return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,1)), isherm=False) + + +def qutrit_basis(): + """Basis states for a three level system (qutrit) + + Returns + ------- + qstates : array + Array of qutrit basis vectors + + """ + return np.array([basis(3, 0), basis(3, 1), basis(3, 2)], dtype=object) + + +def coherent(N, alpha, offset=0, method='operator'): + """Generates a coherent state with eigenvalue alpha. + + Constructed using displacement operator on vacuum state. + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + alpha : float/complex + Eigenvalue of coherent state. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the state. Using a non-zero offset will make the + default method 'analytic'. + + method : string {'operator', 'analytic'} + Method for generating coherent state. + + Returns + ------- + state : qobj + Qobj quantum object for coherent state + + Examples + -------- + >>> coherent(5,0.25j) + Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket + Qobj data = + [[ 9.69233235e-01+0.j ] + [ 0.00000000e+00+0.24230831j] + [ -4.28344935e-02+0.j ] + [ 0.00000000e+00-0.00618204j] + [ 7.80904967e-04+0.j ]] + + Notes + ----- + Select method 'operator' (default) or 'analytic'. With the + 'operator' method, the coherent state is generated by displacing + the vacuum state using the displacement operator defined in the + truncated Hilbert space of size 'N'. This method guarantees that the + resulting state is normalized. With 'analytic' method the coherent state + is generated using the analytical formula for the coherent state + coefficients in the Fock basis. This method does not guarantee that the + state is normalized if truncated to a small number of Fock states, + but would in that case give more accurate coefficients. + + """ + if method == "operator" and offset == 0: + + x = basis(N, 0) + a = destroy(N) + D = (alpha * a.dag() - conj(alpha) * a).expm() + return D * x + + elif method == "analytic" or offset > 0: + + sqrtn = np.sqrt(np.arange(offset, offset+N, dtype=complex)) + sqrtn[0] = 1 # Get rid of divide by zero warning + data = alpha/sqrtn + if offset == 0: + data[0] = np.exp(-abs(alpha)**2 / 2.0) + else: + s = np.prod(np.sqrt(np.arange(1, offset + 1))) # sqrt factorial + data[0] = np.exp(-abs(alpha)**2 / 2.0) * alpha**(offset) / s + np.cumprod(data, out=sqrtn) # Reuse sqrtn array + return Qobj(sqrtn) + + else: + raise TypeError( + "The method option can only take values 'operator' or 'analytic'") + + +def coherent_dm(N, alpha, offset=0, method='operator'): + """Density matrix representation of a coherent state. + + Constructed via outer product of :func:`qutip.states.coherent` + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + alpha : float/complex + Eigenvalue for coherent state. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the state. + + method : string {'operator', 'analytic'} + Method for generating coherent density matrix. + + Returns + ------- + dm : qobj + Density matrix representation of coherent state. + + Examples + -------- + >>> coherent_dm(3,0.25j) + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 0.93941695+0.j 0.00000000-0.23480733j -0.04216943+0.j ] + [ 0.00000000+0.23480733j 0.05869011+0.j 0.00000000-0.01054025j] + [-0.04216943+0.j 0.00000000+0.01054025j 0.00189294+0.j\ + ]] + + Notes + ----- + Select method 'operator' (default) or 'analytic'. With the + 'operator' method, the coherent density matrix is generated by displacing + the vacuum state using the displacement operator defined in the + truncated Hilbert space of size 'N'. This method guarantees that the + resulting density matrix is normalized. With 'analytic' method the coherent + density matrix is generated using the analytical formula for the coherent + state coefficients in the Fock basis. This method does not guarantee that + the state is normalized if truncated to a small number of Fock states, + but would in that case give more accurate coefficients. + + """ + if method == "operator": + psi = coherent(N, alpha, offset=offset) + return psi * psi.dag() + + elif method == "analytic": + psi = coherent(N, alpha, offset=offset, method='analytic') + return psi * psi.dag() + + else: + raise TypeError( + "The method option can only take values 'operator' or 'analytic'") + + +def fock_dm(N, n=0, offset=0): + """Density matrix representation of a Fock state + + Constructed via outer product of :func:`qutip.states.fock`. + + Parameters + ---------- + N : int + Number of Fock states in Hilbert space. + + n : int + ``int`` for desired number state, defaults to 0 if omitted. + + Returns + ------- + dm : qobj + Density matrix representation of Fock state. + + Examples + -------- + >>> fock_dm(3,1) + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 0.+0.j 0.+0.j 0.+0.j] + [ 0.+0.j 1.+0.j 0.+0.j] + [ 0.+0.j 0.+0.j 0.+0.j]] + + """ + psi = basis(N, n, offset=offset) + + return psi * psi.dag() + + +def fock(N, n=0, offset=0): + """Bosonic Fock (number) state. + + Same as :func:`qutip.states.basis`. + + Parameters + ---------- + N : int + Number of states in the Hilbert space. + + n : int + ``int`` for desired number state, defaults to 0 if omitted. + + Returns + ------- + Requested number state :math:`\\left|n\\right>`. + + Examples + -------- + >>> fock(4,3) + Quantum object: dims = [[4], [1]], shape = [4, 1], type = ket + Qobj data = + [[ 0.+0.j] + [ 0.+0.j] + [ 0.+0.j] + [ 1.+0.j]] + + """ + return basis(N, n, offset=offset) + + +def thermal_dm(N, n, method='operator'): + """Density matrix for a thermal state of n particles + + Parameters + ---------- + N : int + Number of basis states in Hilbert space. + + n : float + Expectation value for number of particles in thermal state. + + method : string {'operator', 'analytic'} + ``string`` that sets the method used to generate the + thermal state probabilities + + Returns + ------- + dm : qobj + Thermal state density matrix. + + Examples + -------- + >>> thermal_dm(5, 1) + Quantum object: dims = [[5], [5]], \ +shape = [5, 5], type = oper, isHerm = True + Qobj data = + [[ 0.51612903 0. 0. 0. 0. ] + [ 0. 0.25806452 0. 0. 0. ] + [ 0. 0. 0.12903226 0. 0. ] + [ 0. 0. 0. 0.06451613 0. ] + [ 0. 0. 0. 0. 0.03225806]] + + + >>> thermal_dm(5, 1, 'analytic') + Quantum object: dims = [[5], [5]], \ +shape = [5, 5], type = oper, isHerm = True + Qobj data = + [[ 0.5 0. 0. 0. 0. ] + [ 0. 0.25 0. 0. 0. ] + [ 0. 0. 0.125 0. 0. ] + [ 0. 0. 0. 0.0625 0. ] + [ 0. 0. 0. 0. 0.03125]] + + Notes + ----- + The 'operator' method (default) generates + the thermal state using the truncated number operator ``num(N)``. This + is the method that should be used in computations. The + 'analytic' method uses the analytic coefficients derived in + an infinite Hilbert space. The analytic form is not necessarily normalized, + if truncated too aggressively. + + """ + if n == 0: + return fock_dm(N, 0) + else: + i = arange(N) + if method == 'operator': + beta = np.log(1.0 / n + 1.0) + diags = np.exp(-beta * i) + diags = diags / np.sum(diags) + # populates diagonal terms using truncated operator expression + rm = sp.spdiags(diags, 0, N, N, format='csr') + elif method == 'analytic': + # populates diagonal terms using analytic values + rm = sp.spdiags((1.0 + n) ** (-1.0) * (n / (1.0 + n)) ** (i), + 0, N, N, format='csr') + else: + raise ValueError( + "'method' keyword argument must be 'operator' or 'analytic'") + return Qobj(rm) + + +def maximally_mixed_dm(N): + """ + Returns the maximally mixed density matrix for a Hilbert space of + dimension N. + + Parameters + ---------- + N : int + Number of basis states in Hilbert space. + + Returns + ------- + dm : qobj + Thermal state density matrix. + """ + if (not isinstance(N, (int, np.int64))) or N <= 0: + raise ValueError("N must be integer N > 0") + + dm = sp.spdiags(np.ones(N, dtype=complex)/float(N), 0, N, N, format='csr') + + return Qobj(dm, isherm=True) + + +def ket2dm(Q): + """Takes input ket or bra vector and returns density matrix + formed by outer product. + + Parameters + ---------- + Q : qobj + Ket or bra type quantum object. + + Returns + ------- + dm : qobj + Density matrix formed by outer product of `Q`. + + Examples + -------- + >>> x=basis(3,2) + >>> ket2dm(x) + Quantum object: dims = [[3], [3]], \ +shape = [3, 3], type = oper, isHerm = True + Qobj data = + [[ 0.+0.j 0.+0.j 0.+0.j] + [ 0.+0.j 0.+0.j 0.+0.j] + [ 0.+0.j 0.+0.j 1.+0.j]] + + """ + if Q.type == 'ket': + out = Q * Q.dag() + elif Q.type == 'bra': + out = Q.dag() * Q + else: + raise TypeError("Input is not a ket or bra vector.") + return Qobj(out) + + +# +# projection operator +# +def projection(N, n, m, offset=0): + """The projection operator that projects state :math:`|m>` on state :math:`|n>`. + + Parameters + ---------- + N : int + Number of basis states in Hilbert space. + + n, m : float + The number states in the projection. + + offset : int (default 0) + The lowest number state that is included in the finite number state + representation of the projector. + + Returns + ------- + oper : qobj + Requested projection operator. + + """ + ket1 = basis(N, n, offset=offset) + ket2 = basis(N, m, offset=offset) + + return ket1 * ket2.dag() + + +# +# composite qubit states +# +def qstate(string): + """Creates a tensor product for a set of qubits in either + the 'up' :math:`|0>` or 'down' :math:`|1>` state. + + Parameters + ---------- + string : str + String containing 'u' or 'd' for each qubit (ex. 'ududd') + + Returns + ------- + qstate : qobj + Qobj for tensor product corresponding to input string. + + Notes + ----- + Look at ket and bra for more general functions + creating multiparticle states. + + Examples + -------- + >>> qstate('udu') + Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = [8, 1], type = ket + Qobj data = + [[ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 1.] + [ 0.] + [ 0.]] + + """ + n = len(string) + if n != (string.count('u') + string.count('d')): + raise TypeError('String input to QSTATE must consist ' + + 'of "u" and "d" elements only') + else: + up = basis(2, 1) + dn = basis(2, 0) + lst = [] + for k in range(n): + if string[k] == 'u': + lst.append(up) + else: + lst.append(dn) + return tensor(lst) + + +# +# different qubit notation dictionary +# +_qubit_dict = {'g': 0, # ground state + 'e': 1, # excited state + 'u': 0, # spin up + 'd': 1, # spin down + 'H': 0, # horizontal polarization + 'V': 1} # vertical polarization + + +def _character_to_qudit(x): + """ + Converts a character representing a one-particle state into int. + """ + if x in _qubit_dict: + return _qubit_dict[x] + else: + return int(x) + + +def ket(seq, dim=2): + """ + Produces a multiparticle ket state for a list or string, + where each element stands for state of the respective particle. + + Parameters + ---------- + seq : str / list of ints or characters + Each element defines state of the respective particle. + (e.g. [1,1,0,1] or a string "1101"). + For qubits it is also possible to use the following conventions: + - 'g'/'e' (ground and excited state) + - 'u'/'d' (spin up and down) + - 'H'/'V' (horizontal and vertical polarization) + Note: for dimension > 9 you need to use a list. + + + dim : int (default: 2) / list of ints + Space dimension for each particle: + int if there are the same, list if they are different. + + Returns + ------- + ket : qobj + + Examples + -------- + >>> ket("10") + Quantum object: dims = [[2, 2], [1, 1]], shape = [4, 1], type = ket + Qobj data = + [[ 0.] + [ 0.] + [ 1.] + [ 0.]] + + >>> ket("Hue") + Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = [8, 1], type = ket + Qobj data = + [[ 0.] + [ 1.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.]] + + >>> ket("12", 3) + Quantum object: dims = [[3, 3], [1, 1]], shape = [9, 1], type = ket + Qobj data = + [[ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 1.] + [ 0.] + [ 0.] + [ 0.]] + + >>> ket("31", [5, 2]) + Quantum object: dims = [[5, 2], [1, 1]], shape = [10, 1], type = ket + Qobj data = + [[ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 1.] + [ 0.] + [ 0.]] + """ + if isinstance(dim, int): + dim = [dim] * len(seq) + return tensor([basis(dim[i], _character_to_qudit(x)) + for i, x in enumerate(seq)]) + + +def bra(seq, dim=2): + """ + Produces a multiparticle bra state for a list or string, + where each element stands for state of the respective particle. + + Parameters + ---------- + seq : str / list of ints or characters + Each element defines state of the respective particle. + (e.g. [1,1,0,1] or a string "1101"). + For qubits it is also possible to use the following conventions: + - 'g'/'e' (ground and excited state) + - 'u'/'d' (spin up and down) + - 'H'/'V' (horizontal and vertical polarization) + Note: for dimension > 9 you need to use a list. + + + dim : int (default: 2) / list of ints + Space dimension for each particle: + int if there are the same, list if they are different. + + Returns + ------- + bra : qobj + + Examples + -------- + >>> bra("10") + Quantum object: dims = [[1, 1], [2, 2]], shape = [1, 4], type = bra + Qobj data = + [[ 0. 0. 1. 0.]] + + >>> bra("Hue") + Quantum object: dims = [[1, 1, 1], [2, 2, 2]], shape = [1, 8], type = bra + Qobj data = + [[ 0. 1. 0. 0. 0. 0. 0. 0.]] + + >>> bra("12", 3) + Quantum object: dims = [[1, 1], [3, 3]], shape = [1, 9], type = bra + Qobj data = + [[ 0. 0. 0. 0. 0. 1. 0. 0. 0.]] + + + >>> bra("31", [5, 2]) + Quantum object: dims = [[1, 1], [5, 2]], shape = [1, 10], type = bra + Qobj data = + [[ 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]] + """ + return ket(seq, dim=dim).dag() + + +# +# quantum state number helper functions +# +def state_number_enumerate(dims, excitations=None, state=None, idx=0): + """ + An iterator that enumerate all the state number arrays (quantum numbers on + the form [n1, n2, n3, ...]) for a system with dimensions given by dims. + + Example: + + >>> for state in state_number_enumerate([2,2]): + >>> print(state) + [ 0 0 ] + [ 0 1 ] + [ 1 0 ] + [ 1 1 ] + + Parameters + ---------- + dims : list or array + The quantum state dimensions array, as it would appear in a Qobj. + + state : list + Current state in the iteration. Used internally. + + excitations : integer (None) + Restrict state space to states with excitation numbers below or + equal to this value. + + idx : integer + Current index in the iteration. Used internally. + + Returns + ------- + state_number : list + Successive state number arrays that can be used in loops and other + iterations, using standard state enumeration *by definition*. + + """ + + if state is None: + state = np.zeros(len(dims), dtype=int) + + if excitations and sum(state[0:idx]) > excitations: + pass + elif idx == len(dims): + if excitations is None: + yield np.array(state) + else: + yield tuple(state) + else: + for n in range(dims[idx]): + state[idx] = n + for s in state_number_enumerate(dims, excitations, state, idx + 1): + yield s + + +def state_number_index(dims, state): + """ + Return the index of a quantum state corresponding to state, + given a system with dimensions given by dims. + + Example: + + >>> state_number_index([2, 2, 2], [1, 1, 0]) + 6 + + Parameters + ---------- + dims : list or array + The quantum state dimensions array, as it would appear in a Qobj. + + state : list + State number array. + + Returns + ------- + idx : int + The index of the state given by `state` in standard enumeration + ordering. + + """ + return int( + sum([state[i] * prod(dims[i + 1:]) for i, d in enumerate(dims)])) + + +def state_index_number(dims, index): + """ + Return a quantum number representation given a state index, for a system + of composite structure defined by dims. + + Example: + + >>> state_index_number([2, 2, 2], 6) + [1, 1, 0] + + Parameters + ---------- + dims : list or array + The quantum state dimensions array, as it would appear in a Qobj. + + index : integer + The index of the state in standard enumeration ordering. + + Returns + ------- + state : list + The state number array corresponding to index `index` in standard + enumeration ordering. + + """ + state = np.empty_like(dims) + + D = np.concatenate([np.flipud(np.cumprod(np.flipud(dims[1:]))), [1]]) + + for n in range(len(dims)): + state[n] = index / D[n] + index -= state[n] * D[n] + + return list(state) + + +def state_number_qobj(dims, state): + """ + Return a Qobj representation of a quantum state specified by the state + array `state`. + + Example: + + >>> state_number_qobj([2, 2, 2], [1, 0, 1]) + Quantum object: dims = [[2, 2, 2], [1, 1, 1]], \ +shape = [8, 1], type = ket + Qobj data = + [[ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 0.] + [ 1.] + [ 0.] + [ 0.]] + + Parameters + ---------- + dims : list or array + The quantum state dimensions array, as it would appear in a Qobj. + + state : list + State number array. + + Returns + ------- + state : :class:`qutip.Qobj.qobj` + The state as a :class:`qutip.Qobj.qobj` instance. + + + """ + return tensor([fock(dims[i], s) for i, s in enumerate(state)]) + + +# +# Excitation-number restricted (enr) states +# +def enr_state_dictionaries(dims, excitations): + """ + Return the number of states, and lookup-dictionaries for translating + a state tuple to a state index, and vice versa, for a system with a given + number of components and maximum number of excitations. + + Parameters + ---------- + dims: list + A list with the number of states in each sub-system. + + excitations : integer + The maximum numbers of dimension + + Returns + ------- + nstates, state2idx, idx2state: integer, dict, dict + The number of states `nstates`, a dictionary for looking up state + indices from a state tuple, and a dictionary for looking up state + state tuples from state indices. + """ + nstates = 0 + state2idx = {} + idx2state = {} + + for state in state_number_enumerate(dims, excitations): + state2idx[state] = nstates + idx2state[nstates] = state + nstates += 1 + + return nstates, state2idx, idx2state + + +def enr_fock(dims, excitations, state): + """ + Generate the Fock state representation in a excitation-number restricted + state space. The `dims` argument is a list of integers that define the + number of quantums states of each component of a composite quantum system, + and the `excitations` specifies the maximum number of excitations for + the basis states that are to be included in the state space. The `state` + argument is a tuple of integers that specifies the state (in the number + basis representation) for which to generate the Fock state representation. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + state : list of integers + The state in the number basis representation. + + Returns + ------- + ket : Qobj + A Qobj instance that represent a Fock state in the exication-number- + restricted state space defined by `dims` and `exciations`. + + """ + nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) + + data = sp.lil_matrix((nstates, 1), dtype=np.complex) + + try: + data[state2idx[tuple(state)], 0] = 1 + except: + raise ValueError("The state tuple %s is not in the restricted " + "state space" % str(tuple(state))) + + return Qobj(data, dims=[dims, [1]*len(dims)]) + + +def enr_thermal_dm(dims, excitations, n): + """ + Generate the density operator for a thermal state in the excitation-number- + restricted state space defined by the `dims` and `exciations` arguments. + See the documentation for enr_fock for a more detailed description of + these arguments. The temperature of each mode in dims is specified by + the average number of excitatons `n`. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + n : integer + The average number of exciations in the thermal state. `n` can be + a float (which then applies to each mode), or a list/array of the same + length as dims, in which each element corresponds specifies the + temperature of the corresponding mode. + + Returns + ------- + dm : Qobj + Thermal state density matrix. + """ + nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) + + if not isinstance(n, (list, np.ndarray)): + n = np.ones(len(dims)) * n + else: + n = np.asarray(n) + + diags = [np.prod((n / (n + 1)) ** np.array(state)) + for idx, state in idx2state.items()] + diags /= np.sum(diags) + data = sp.spdiags(diags, 0, nstates, nstates, format='csr') + + return Qobj(data, dims=[dims, dims]) + + +def phase_basis(N, m, phi0=0): + """ + Basis vector for the mth phase of the Pegg-Barnett phase operator. + + Parameters + ---------- + N : int + Number of basis vectors in Hilbert space. + m : int + Integer corresponding to the mth discrete phase phi_m=phi0+2*pi*m/N + phi0 : float (default=0) + Reference phase angle. + + Returns + ------- + state : qobj + Ket vector for mth Pegg-Barnett phase operator basis state. + + Notes + ----- + The Pegg-Barnett basis states form a complete set over the truncated + Hilbert space. + + """ + phim = phi0 + (2.0 * np.pi * m) / N + n = np.arange(N).reshape((N, 1)) + data = 1.0 / np.sqrt(N) * np.exp(1.0j * n * phim) + return Qobj(data) + + +def zero_ket(N, dims=None): + """ + Creates the zero ket vector with shape Nx1 and + dimensions `dims`. + + Parameters + ---------- + N : int + Hilbert space dimensionality + dims : list + Optional dimensions if ket corresponds to + a composite Hilbert space. + + Returns + ------- + zero_ket : qobj + Zero ket on given Hilbert space. + + """ + return Qobj(sp.csr_matrix((N, 1), dtype=complex), dims=dims) + + +def spin_state(j, m, type='ket'): + """Generates the spin state |j, m>, i.e. the eigenstate + of the spin-j Sz operator with eigenvalue m. + + Parameters + ---------- + j : float + The spin of the state (). + + m : int + Eigenvalue of the spin-j Sz operator. + + type : string {'ket', 'bra', 'dm'} + Type of state to generate. + + Returns + ------- + state : qobj + Qobj quantum object for spin state + + """ + J = 2 * j + 1 + + if type == 'ket': + return basis(int(J), int(j - m)) + elif type == 'bra': + return basis(int(J), int(j - m)).dag() + elif type == 'dm': + return fock_dm(int(J), int(j - m)) + else: + raise ValueError("invalid value keyword argument 'type'") + + +def spin_coherent(j, theta, phi, type='ket'): + """Generate the coherent spin state |theta, phi>. + + Parameters + ---------- + j : float + The spin of the state. + + theta : float + Angle from z axis. + + phi : float + Angle from x axis. + + type : string {'ket', 'bra', 'dm'} + Type of state to generate. + + Returns + ------- + state : qobj + Qobj quantum object for spin coherent state + + """ + Sp = jmat(j, '+') + Sm = jmat(j, '-') + psi = (0.5 * theta * np.exp(1j * phi) * Sm - + 0.5 * theta * np.exp(-1j * phi) * Sp).expm() * spin_state(j, j) + + if type == 'ket': + return psi + elif type == 'bra': + return psi.dag() + elif type == 'dm': + return ket2dm(psi) + else: + raise ValueError("invalid value keyword argument 'type'") + + +def bell_state(state='00'): + """ + Returns the Bell state: + + |B00> = 1 / sqrt(2)*[|0>|0>+|1>|1>] + |B01> = 1 / sqrt(2)*[|0>|0>-|1>|1>] + |B10> = 1 / sqrt(2)*[|0>|1>+|1>|0>] + |B11> = 1 / sqrt(2)*[|0>|1>-|1>|0>] + + Returns + ------- + Bell_state : qobj + Bell state + + """ + if state == '00': + Bell_state = tensor( + basis(2), basis(2))+tensor(basis(2, 1), basis(2, 1)) + elif state == '01': + Bell_state = tensor( + basis(2), basis(2))-tensor(basis(2, 1), basis(2, 1)) + elif state == '10': + Bell_state = tensor( + basis(2), basis(2, 1))+tensor(basis(2, 1), basis(2)) + elif state == '11': + Bell_state = tensor( + basis(2), basis(2, 1))-tensor(basis(2, 1), basis(2)) + + return Bell_state.unit() + + +def singlet_state(): + """ + Returns the two particle singlet-state: + + |S>=1/sqrt(2)*[|0>|1>-|1>|0>] + + that is identical to the fourth bell state. + + Returns + ------- + Bell_state : qobj + |B11> Bell state + + """ + return bell_state('11') + + +def triplet_states(): + """ + Returns the two particle triplet-states: + + |T>= |1>|1> + = 1 / sqrt(2)*[|0>|1>-|1>|0>] + = |0>|0> + that is identical to the fourth bell state. + + Returns + ------- + trip_states : list + 2 particle triplet states + + """ + trip_states = [] + trip_states.append(tensor(basis(2, 1), basis(2, 1))) + trip_states.append( + (tensor(basis(2), basis(2, 1)) + tensor(basis(2, 1), basis(2))).unit() + ) + trip_states.append(tensor(basis(2), basis(2))) + return trip_states + + +def w_state(N=3): + """ + Returns the N-qubit W-state. + + Parameters + ---------- + N : int (default=3) + Number of qubits in state + + Returns + ------- + W : qobj + N-qubit W-state + + """ + inds = np.zeros(N, dtype=int) + inds[0] = 1 + state = tensor([basis(2, x) for x in inds]) + for kk in range(1, N): + perm_inds = np.roll(inds, kk) + state += tensor([basis(2, x) for x in perm_inds]) + return state.unit() + + +def ghz_state(N=3): + """ + Returns the N-qubit GHZ-state. + + Parameters + ---------- + N : int (default=3) + Number of qubits in state + + Returns + ------- + G : qobj + N-qubit GHZ-state + + """ + state = (tensor([basis(2) for k in range(N)]) + + tensor([basis(2, 1) for k in range(N)])) + return state/np.sqrt(2) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py new file mode 100755 index 0000000000..ca39535482 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -0,0 +1,408 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### + +__all__ = ['liouvillian', 'liouvillian_ref', 'lindblad_dissipator', + 'operator_to_vector', 'vector_to_operator', 'mat2vec', 'vec2mat', + 'vec2mat_index', 'mat2vec_index', 'spost', 'spre', 'sprepost'] + +import scipy.sparse as sp +import numpy as np +from qutip.qobj import Qobj +from qutip.fastsparse import fast_csr_matrix, fast_identity +from qutip.sparse import sp_reshape +from qutip.cy.spmath import zcsr_kron +from functools import partial + + +def liouvillian(H, c_ops=[], data_only=False, chi=None): + """Assembles the Liouvillian superoperator from a Hamiltonian + and a ``list`` of collapse operators. Like liouvillian, but with an + experimental implementation which avoids creating extra Qobj instances, + which can be advantageous for large systems. + + Parameters + ---------- + H : Qobj or QobjEvo + System Hamiltonian. + + c_ops : array_like of Qobj or QobjEvo + A ``list`` or ``array`` of collapse operators. + + Returns + ------- + L : Qobj or QobjEvo + Liouvillian superoperator. + + """ + if isinstance(c_ops, (Qobj, QobjEvo)): + c_ops = [c_ops] + if chi and len(chi) != len(c_ops): + raise ValueError('chi must be a list with same length as c_ops') + + h = None + if H is not None: + if isinstance(H, QobjEvo): + h = H.cte + else: + h = H + if h.isoper: + op_dims = h.dims + op_shape = h.shape + elif h.issuper: + op_dims = h.dims[0] + op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] + else: + raise TypeError("Invalid type for Hamiltonian.") + else: + # no hamiltonian given, pick system size from a collapse operator + if isinstance(c_ops, list) and len(c_ops) > 0: + if isinstance(c_ops[0], QobjEvo): + c = c_ops[0].cte + else: + c = c_ops[0] + if c.isoper: + op_dims = c.dims + op_shape = c.shape + elif c.issuper: + op_dims = c.dims[0] + op_shape = [np.prod(op_dims[0]), np.prod(op_dims[0])] + else: + raise TypeError("Invalid type for collapse operator.") + else: + raise TypeError("Either H or c_ops must be given.") + + sop_dims = [[op_dims[0], op_dims[0]], [op_dims[1], op_dims[1]]] + sop_shape = [np.prod(op_dims), np.prod(op_dims)] + + spI = fast_identity(op_shape[0]) + + td = False + L = None + if isinstance(H, QobjEvo): + td = True + + def H2L(H): + if H.isoper: + return -1.0j * (spre(H) - spost(H)) + else: + return H + + L = H.apply(H2L) + data = L.cte.data + elif isinstance(H, Qobj): + if H.isoper: + Ht = H.data.T + data = -1j * zcsr_kron(spI, H.data) + data += 1j * zcsr_kron(Ht, spI) + else: + data = H.data + else: + data = fast_csr_matrix(shape=(sop_shape[0], sop_shape[1])) + + td_c_ops = [] + for idx, c_op in enumerate(c_ops): + if isinstance(c_op, QobjEvo): + td = True + if c_op.const: + c_ = c_op.cte + elif chi: + td_c_ops.append(lindblad_dissipator(c_op, chi=chi[idx])) + continue + else: + td_c_ops.append(lindblad_dissipator(c_op)) + continue + else: + c_ = c_op + + if c_.issuper: + data = data + c_.data + else: + cd = c_.data.H + c = c_.data + if chi: + data = data + np.exp(1j * chi[idx]) * \ + zcsr_kron(c.conj(), c) + else: + data = data + zcsr_kron(c.conj(), c) + cdc = cd * c + cdct = cdc.T + data = data - 0.5 * zcsr_kron(spI, cdc) + data = data - 0.5 * zcsr_kron(cdct, spI) + + if not td: + if data_only: + return data + else: + L = Qobj() + L.dims = sop_dims + L.data = data + L.superrep = 'super' + return L + else: + if not L: + l = Qobj() + l.dims = sop_dims + l.data = data + l.superrep = 'super' + L = QobjEvo(l) + else: + L.cte.data = data + for c_op in td_c_ops: + L += c_op + return L + + +def liouvillian_ref(H, c_ops=[]): + """Assembles the Liouvillian superoperator from a Hamiltonian + and a ``list`` of collapse operators. + + Parameters + ---------- + H : qobj + System Hamiltonian. + + c_ops : array_like + A ``list`` or ``array`` of collapse operators. + + Returns + ------- + L : qobj + Liouvillian superoperator. + """ + + L = -1.0j * (spre(H) - spost(H)) if H else 0 + + for c in c_ops: + if c.issuper: + L += c + else: + cdc = c.dag() * c + L += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) - 0.5 * spost(cdc) + + return L + + +def lindblad_dissipator(a, b=None, data_only=False, chi=None): + """ + Lindblad dissipator (generalized) for a single pair of collapse operators + (a, b), or for a single collapse operator (a) when b is not specified: + + .. math:: + + \\mathcal{D}[a,b]\\rho = a \\rho b^\\dagger - + \\frac{1}{2}a^\\dagger b\\rho - \\frac{1}{2}\\rho a^\\dagger b + + Parameters + ---------- + a : Qobj or QobjEvo + Left part of collapse operator. + + b : Qobj or QobjEvo (optional) + Right part of collapse operator. If not specified, b defaults to a. + + Returns + ------- + D : qobj, QobjEvo + Lindblad dissipator superoperator. + """ + if b is None: + b = a + ad_b = a.dag() * b + if chi: + D = spre(a) * spost(b.dag()) * np.exp(1j * chi) \ + - 0.5 * spre(ad_b) - 0.5 * spost(ad_b) + else: + D = spre(a) * spost(b.dag()) - 0.5 * spre(ad_b) - 0.5 * spost(ad_b) + + if isinstance(a, QobjEvo) or isinstance(b, QobjEvo): + return D + else: + return D.data if data_only else D + + +def operator_to_vector(op): + """ + Create a vector representation of a quantum operator given + the matrix representation. + """ + if isinstance(op, QobjEvo): + return op.apply(operator_to_vector) + + q = Qobj() + q.dims = [op.dims, [1]] + q.data = sp_reshape(op.data.T, (np.prod(op.shape), 1)) + return q + + +def vector_to_operator(op): + """ + Create a matrix representation given a quantum operator in + vector form. + """ + if isinstance(op, QobjEvo): + return op.apply(vector_to_operator) + + q = Qobj() + q.dims = op.dims[0] + n = int(np.sqrt(op.shape[0])) + q.data = sp_reshape(op.data.T, (n, n)).T + return q + + +def mat2vec(mat): + """ + Private function reshaping matrix to vector. + """ + return mat.T.reshape(np.prod(np.shape(mat)), 1) + + +def vec2mat(vec): + """ + Private function reshaping vector to matrix. + """ + n = int(np.sqrt(len(vec))) + return vec.reshape((n, n)).T + + +def vec2mat_index(N, I): + """ + Convert a vector index to a matrix index pair that is compatible with the + vector to matrix rearrangement done by the vec2mat function. + """ + j = int(I / N) + i = I - N * j + return i, j + + +def mat2vec_index(N, i, j): + """ + Convert a matrix index pair to a vector index that is compatible with the + matrix to vector rearrangement done by the mat2vec function. + """ + return i + N * j + + +def spost(A): + """Superoperator formed from post-multiplication by operator A + + Parameters + ---------- + A : Qobj or QobjEvo + Quantum operator for post multiplication. + + Returns + ------- + super : Qobj or QobjEvo + Superoperator formed from input qauntum object. + """ + if isinstance(A, QobjEvo): + return A.apply(spost) + + if not isinstance(A, Qobj): + raise TypeError('Input is not a quantum object') + + if not A.isoper: + raise TypeError('Input is not a quantum operator') + + S = Qobj(isherm=A.isherm, superrep='super') + S.dims = [[A.dims[0], A.dims[1]], [A.dims[0], A.dims[1]]] + S.data = zcsr_kron(A.data.T, + fast_identity(np.prod(A.shape[0]))) + return S + + +def spre(A): + """Superoperator formed from pre-multiplication by operator A. + + Parameters + ---------- + A : Qobj or QobjEvo + Quantum operator for pre-multiplication. + + Returns + -------- + super :Qobj or QobjEvo + Superoperator formed from input quantum object. + """ + if isinstance(A, QobjEvo): + return A.apply(spre) + + if not isinstance(A, Qobj): + raise TypeError('Input is not a quantum object') + + if not A.isoper: + raise TypeError('Input is not a quantum operator') + + S = Qobj(isherm=A.isherm, superrep='super') + S.dims = [[A.dims[0], A.dims[1]], [A.dims[0], A.dims[1]]] + S.data = zcsr_kron(fast_identity(np.prod(A.shape[1])), A.data) + return S + + +def _drop_projected_dims(dims): + """ + Eliminate subsystems that has been collapsed to only one state due to + a projection. + """ + return [d for d in dims if d != 1] + + +def sprepost(A, B): + """Superoperator formed from pre-multiplication by operator A and post- + multiplication of operator B. + + Parameters + ---------- + A : Qobj or QobjEvo + Quantum operator for pre-multiplication. + + B : Qobj or QobjEvo + Quantum operator for post-multiplication. + + Returns + -------- + super : Qobj or QobjEvo + Superoperator formed from input quantum objects. + """ + if isinstance(A, QobjEvo) or isinstance(B, QobjEvo): + return spre(A) * spost(B) + + else: + dims = [[_drop_projected_dims(A.dims[0]), + _drop_projected_dims(B.dims[1])], + [_drop_projected_dims(A.dims[1]), + _drop_projected_dims(B.dims[0])]] + data = zcsr_kron(B.data.T, A.data) + return Qobj(data, dims=dims, superrep='super') + +from qutip.qobjevo import QobjEvo diff --git a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py new file mode 100755 index 0000000000..32cdc41f47 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py @@ -0,0 +1,387 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +Module for the creation of composite quantum objects via the tensor product. +""" + +__all__ = [ + 'tensor', 'super_tensor', 'composite', 'tensor_swap', 'tensor_contract' +] + +import numpy as np +import scipy.sparse as sp +from qutip.cy.spmath import zcsr_kron +from qutip.qobj import Qobj +from qutip.permute import reshuffle +from qutip.superoperator import operator_to_vector +from qutip.dimensions import ( + flatten, enumerate_flat, unflatten, deep_remove, + dims_to_tensor_shape, dims_idxs_to_tensor_idxs +) + +import qutip.settings +import qutip.superop_reps # Avoid circular dependency here. + + +def tensor(*args): + """Calculates the tensor product of input operators. + + Parameters + ---------- + args : array_like + ``list`` or ``array`` of quantum objects for tensor product. + + Returns + ------- + obj : qobj + A composite quantum object. + + Examples + -------- + >>> tensor([sigmax(), sigmax()]) + Quantum object: dims = [[2, 2], [2, 2]], \ +shape = [4, 4], type = oper, isHerm = True + Qobj data = + [[ 0.+0.j 0.+0.j 0.+0.j 1.+0.j] + [ 0.+0.j 0.+0.j 1.+0.j 0.+0.j] + [ 0.+0.j 1.+0.j 0.+0.j 0.+0.j] + [ 1.+0.j 0.+0.j 0.+0.j 0.+0.j]] + """ + + if not args: + raise TypeError("Requires at least one input argument") + + if len(args) == 1 and isinstance(args[0], (list, np.ndarray)): + # this is the case when tensor is called on the form: + # tensor([q1, q2, q3, ...]) + qlist = args[0] + + elif len(args) == 1 and isinstance(args[0], Qobj): + # tensor is called with a single Qobj as an argument, do nothing + return args[0] + + else: + # this is the case when tensor is called on the form: + # tensor(q1, q2, q3, ...) + qlist = args + + if not all([isinstance(q, Qobj) for q in qlist]): + # raise error if one of the inputs is not a quantum object + raise TypeError("One of inputs is not a quantum object") + + out = Qobj() + + if qlist[0].issuper: + out.superrep = qlist[0].superrep + if not all([q.superrep == out.superrep for q in qlist]): + raise TypeError("In tensor products of superroperators, all must" + + "have the same representation") + + out.isherm = True + for n, q in enumerate(qlist): + if n == 0: + out.data = q.data + out.dims = q.dims + else: + out.data = zcsr_kron(out.data, q.data) + + out.dims = [out.dims[0] + q.dims[0], out.dims[1] + q.dims[1]] + + out.isherm = out.isherm and q.isherm + + if not out.isherm: + out._isherm = None + + return out.tidyup() if qutip.settings.auto_tidyup else out + + +def super_tensor(*args): + """Calculates the tensor product of input superoperators, by tensoring + together the underlying Hilbert spaces on which each vectorized operator + acts. + + Parameters + ---------- + args : array_like + ``list`` or ``array`` of quantum objects with ``type="super"``. + + Returns + ------- + obj : qobj + A composite quantum object. + + """ + if isinstance(args[0], list): + args = args[0] + + # Check if we're tensoring vectors or superoperators. + if all(arg.issuper for arg in args): + if not all(arg.superrep == "super" for arg in args): + raise TypeError( + "super_tensor on type='super' is only implemented for " + "superrep='super'." + ) + + # Reshuffle the superoperators. + shuffled_ops = list(map(reshuffle, args)) + + # Tensor the result. + shuffled_tensor = tensor(shuffled_ops) + + # Unshuffle and return. + out = reshuffle(shuffled_tensor) + out.superrep = args[0].superrep + return out + + elif all(arg.isoperket for arg in args): + + # Reshuffle the superoperators. + shuffled_ops = list(map(reshuffle, args)) + + # Tensor the result. + shuffled_tensor = tensor(shuffled_ops) + + # Unshuffle and return. + out = reshuffle(shuffled_tensor) + return out + + elif all(arg.isoperbra for arg in args): + return super_tensor(*(arg.dag() for arg in args)).dag() + + else: + raise TypeError( + "All arguments must be the same type, " + "either super, operator-ket or operator-bra." + ) + + +def _isoperlike(q): + return q.isoper or q.issuper + + +def _isketlike(q): + return q.isket or q.isoperket + + +def _isbralike(q): + return q.isbra or q.isoperbra + + +def composite(*args): + """ + Given two or more operators, kets or bras, returns the Qobj + corresponding to a composite system over each argument. + For ordinary operators and vectors, this is the tensor product, + while for superoperators and vectorized operators, this is + the column-reshuffled tensor product. + + If a mix of Qobjs supported on Hilbert and Liouville spaces + are passed in, the former are promoted. Ordinary operators + are assumed to be unitaries, and are promoted using ``to_super``, + while kets and bras are promoted by taking their projectors and + using ``operator_to_vector(ket2dm(arg))``. + """ + # First step will be to ensure everything is a Qobj at all. + if not all(isinstance(arg, Qobj) for arg in args): + raise TypeError("All arguments must be Qobjs.") + + # Next, figure out if we have something oper-like (isoper or issuper), + # or something ket-like (isket or isoperket). Bra-like we'll deal with + # by turning things into ket-likes and back. + if all(map(_isoperlike, args)): + # OK, we have oper/supers. + if any(arg.issuper for arg in args): + # Note that to_super does nothing to things + # that are already type=super, while it will + # promote unitaries to superunitaries. + return super_tensor(*map(qutip.superop_reps.to_super, args)) + + else: + # Everything's just an oper, so ordinary tensor products work. + return tensor(*args) + + elif all(map(_isketlike, args)): + # Ket-likes. + if any(arg.isoperket for arg in args): + # We have a vectorized operator, we we may need to promote + # something. + return super_tensor(*( + arg if arg.isoperket + else operator_to_vector(qutip.states.ket2dm(arg)) + for arg in args + )) + + else: + # Everything's ordinary, so we can use the tensor product here. + return tensor(*args) + + elif all(map(_isbralike, args)): + # Turn into ket-likes and recurse. + return composite(*(arg.dag() for arg in args)).dag() + + else: + raise TypeError("Unsupported Qobj types [{}].".format( + ", ".join(arg.type for arg in args) + )) + + +def _tensor_contract_single(arr, i, j): + """ + Contracts a dense tensor along a single index pair. + """ + if arr.shape[i] != arr.shape[j]: + raise ValueError("Cannot contract over indices of different length.") + idxs = np.arange(arr.shape[i]) + sl = tuple(slice(None, None, None) + if idx not in (i, j) else idxs for idx in range(arr.ndim)) + contract_at = i if j == i + 1 else 0 + return np.sum(arr[sl], axis=contract_at) + + +def _tensor_contract_dense(arr, *pairs): + """ + Contracts a dense tensor along one or more index pairs, + keeping track of how the indices are relabeled by the removal + of other indices. + """ + axis_idxs = list(range(arr.ndim)) + for pair in pairs: + # axis_idxs.index effectively evaluates the mapping from + # original index labels to the labels after contraction. + arr = _tensor_contract_single(arr, *map(axis_idxs.index, pair)) + list(map(axis_idxs.remove, pair)) + return arr + + +def tensor_swap(q_oper, *pairs): + """Transposes one or more pairs of indices of a Qobj. + Note that this uses dense representations and thus + should *not* be used for very large Qobjs. + + Parameters + ---------- + + pairs : tuple + One or more tuples ``(i, j)`` indicating that the + ``i`` and ``j`` dimensions of the original qobj + should be swapped. + + Returns + ------- + + sqobj : Qobj + The original Qobj with all named index pairs swapped with each other + """ + dims = q_oper.dims + tensor_pairs = dims_idxs_to_tensor_idxs(dims, pairs) + + data = q_oper.data.toarray() + + # Reshape into tensor indices + data = data.reshape(dims_to_tensor_shape(dims)) + + # Now permute the dims list so we know how to get back. + flat_dims = flatten(dims) + perm = list(range(len(flat_dims))) + for i, j in pairs: + flat_dims[i], flat_dims[j] = flat_dims[j], flat_dims[i] + for i, j in tensor_pairs: + perm[i], perm[j] = perm[j], perm[i] + dims = unflatten(flat_dims, enumerate_flat(dims)) + + # Next, permute the actual indices of the dense tensor. + data = data.transpose(perm) + + # Reshape back, using the left and right of dims. + data = data.reshape(list(map(np.prod, dims))) + + return Qobj(inpt=data, dims=dims, superrep=q_oper.superrep) + + +def tensor_contract(qobj, *pairs): + """Contracts a qobj along one or more index pairs. + Note that this uses dense representations and thus + should *not* be used for very large Qobjs. + + Parameters + ---------- + + pairs : tuple + One or more tuples ``(i, j)`` indicating that the + ``i`` and ``j`` dimensions of the original qobj + should be contracted. + + Returns + ------- + + cqobj : Qobj + The original Qobj with all named index pairs contracted + away. + + """ + # Record and label the original dims. + dims = qobj.dims + dims_idxs = enumerate_flat(dims) + tensor_dims = dims_to_tensor_shape(dims) + + # Convert to dense first, since sparse won't support the reshaping we need. + qtens = qobj.data.toarray() + + # Reshape by the flattened dims. + qtens = qtens.reshape(tensor_dims) + + # Contract out the indices from the flattened object. + # Note that we need to feed pairs through dims_idxs_to_tensor_idxs + # to ensure that we are contracting the right indices. + qtens = _tensor_contract_dense(qtens, *dims_idxs_to_tensor_idxs(dims, pairs)) + + # Remove the contracted indexes from dims so we know how to + # reshape back. + # This concerns dims, and not the tensor indices, so we need + # to make sure to use the original dims indices and not the ones + # generated by dims_to_* functions. + contracted_idxs = deep_remove(dims_idxs, *flatten(list(map(list, pairs)))) + contracted_dims = unflatten(flatten(dims), contracted_idxs) + + # We don't need to check for tensor idxs versus dims idxs here, + # as column- versus row-stacking will never move an index for the + # vectorized operator spaces all the way from the left to the right. + l_mtx_dims, r_mtx_dims = map(np.product, map(flatten, contracted_dims)) + + # Reshape back into a 2D matrix. + qmtx = qtens.reshape((l_mtx_dims, r_mtx_dims)) + + # Return back as a qobj. + return Qobj(qmtx, dims=contracted_dims, superrep=qobj.superrep) + +import qutip.states diff --git a/qiskit/providers/aer/openpulse/cython/setup.py b/qiskit/providers/aer/openpulse/setup.py similarity index 69% rename from qiskit/providers/aer/openpulse/cython/setup.py rename to qiskit/providers/aer/openpulse/setup.py index d4abe2e767..c8dfca7caf 100644 --- a/qiskit/providers/aer/openpulse/cython/setup.py +++ b/qiskit/providers/aer/openpulse/setup.py @@ -20,9 +20,12 @@ from Cython.Distutils import build_ext INCLUDE_DIRS = [np.get_include()] -# Add Cython extensions here -cython_exts = ['channel_value', 'measure', - 'memory', 'utils'] +# Add Cython OP extensions here +cython_op_exts = ['channel_value', 'measure', 'memory', 'utils'] + +# Add Cython QuTiP extensions here +cython_qutip_exts = ['graph_utils', 'math', 'ptrace', 'sparse_utils', + 'spconvert', 'spmatfuncs', 'spmath'] # Extra link args _link_flags = [] @@ -45,10 +48,20 @@ EXT_MODULES = [] -# Add Cython files from qutip/cy -for ext in cython_exts: - _mod = Extension(ext, - sources=[ext+'.pyx'], +# Add Cython files from cy +for ext in cython_op_exts: + _mod = Extension("cy."+ext, + sources=['cy/'+ext+'.pyx'], + include_dirs=[np.get_include()], + extra_compile_args=_compiler_flags, + extra_link_args=_link_flags, + language='c++') + EXT_MODULES.append(_mod) + +# Add Cython files from qutip_lite/cy +for ext in cython_qutip_exts: + _mod = Extension("qutip_lite.cy."+ext, + sources=['qutip_lite/cy/'+ext+'.pyx'], include_dirs=[np.get_include()], extra_compile_args=_compiler_flags, extra_link_args=_link_flags, From 289b208b283e786722b5e188791048a9b6e14bdc Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 2 Jul 2019 10:29:10 -0400 Subject: [PATCH 15/31] fix imports --- qiskit/providers/aer/openpulse/__init__.py | 2 +- qiskit/providers/aer/openpulse/qobj/digest.py | 2 +- qiskit/providers/aer/openpulse/solver/codegen.py | 4 ++-- qiskit/providers/aer/openpulse/solver/monte_carlo.py | 4 ++-- qiskit/providers/aer/openpulse/solver/unitary.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/providers/aer/openpulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py index 1b64695c11..7c63cf6d57 100644 --- a/qiskit/providers/aer/openpulse/__init__.py +++ b/qiskit/providers/aer/openpulse/__init__.py @@ -15,7 +15,7 @@ import distutils.sysconfig import numpy as np -from .qutip_lite import pyxbuilder as pbldr +from .qutip_lite.cy import pyxbuilder as pbldr # Remove -Wstrict-prototypes from cflags cfg_vars = distutils.sysconfig.get_config_vars() diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 1ccace4545..469d9d6127 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -20,7 +20,7 @@ from openpulse.qobj.opparse import HamiltonianParser, NoiseParser from openpulse.qobj.operators import init_fock_state, qubit_occ_oper # pylint: disable=no-name-in-module,import-error -from openpulse.cython.utils import oplist_to_array +from openpulse.cy.utils import oplist_to_array """A module of routines for digesting a PULSE qobj into something we can actually use. diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index 4c472b29a1..889238a815 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -207,7 +207,7 @@ def cython_preamble(): #cython: language_level=3 # This code is part of Qiskit. # -# (C) Copyright IBM 2019. +# (C) Copyright IBM 2017, 2019. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -229,7 +229,7 @@ def cython_preamble(): from qutip.cy.math cimport erf from libc.math cimport pi -from openpulse.cython.channel_value cimport channel_value +from openpulse.cy.channel_value cimport channel_value include """+_include_string+""" """] diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index cf1c56f70f..f2a4aa9224 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -24,8 +24,8 @@ from scipy.linalg.blas import get_blas_funcs from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from openpulse.solver.zvode import qiskit_zvode -from openpulse.cython.memory import write_memory -from openpulse.cython.measure import occ_probabilities, write_shots_memory +from openpulse.cy.memory import write_memory +from openpulse.cy.measure import occ_probabilities, write_shots_memory dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index fc84784c07..6f56e84285 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -16,8 +16,8 @@ import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs -from openpulse.cython.memory import write_memory -from openpulse.cython.measure import occ_probabilities, write_shots_memory +from openpulse.cy.memory import write_memory +from openpulse.cy.measure import occ_probabilities, write_shots_memory dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) From 769b541f6149fdcc7e065b0824bc94798c5074a3 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 3 Jul 2019 12:27:45 -0400 Subject: [PATCH 16/31] formatted results --- qiskit/providers/aer/openpulse/qobj/digest.py | 1 + .../providers/aer/openpulse/solver/opsolve.py | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 469d9d6127..232dc4afae 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -282,6 +282,7 @@ def build_pulse_arrays(qobj): def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): max_time = 0 structs = {} + structs['name'] = experiment['header']['name'] structs['channels'] = OrderedDict() for chan_name in ham_chans: structs['channels'][chan_name] = [[], []] diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index 28c4de3a55..20dba5590d 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -14,6 +14,7 @@ import os import sys import math +import time import numpy as np from numpy.random import RandomState, randint from scipy.integrate import ode @@ -118,8 +119,9 @@ def run(self): # need to simulate each trajectory, so shots*len(experiments) times # Do a for-loop over experiments, and do shots in parallel_map else: - results = [] + all_results = [] for exp in self.op_system.experiments: + start = time.time() rng = np.random.RandomState(exp['seed']) seeds = rng.randint(np.iinfo(np.int32).max-1, size=self.op_system.global_data['shots']) @@ -134,10 +136,21 @@ def run(self): for kk in range(unique[0].shape[0]): key = hex(unique[0][kk]) hex_dict[key] = unique[1][kk] - results.append(hex_dict) + end = time.time() + results = {'name': exp['name'], + 'seed_simulator': exp['seed'], + 'shots': self.op_system.global_data['shots'], + 'status': 'DONE', + 'success': True, + 'time_taken': (end - start), + 'header': {}} + + results['data'] = {'counts': hex_dict} + + all_results.append(results) _cython_build_cleanup(self.op_system.global_data['rhs_file_name']) - return results + return all_results # Measurement From bc71fc4ce9bda2cfcedce06cbf600db9b82740ee Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Fri, 5 Jul 2019 15:35:05 -0400 Subject: [PATCH 17/31] pulse backend --- CMakeLists.txt | 1 - qiskit/__init__.py | 13 +++ qiskit/providers/__init__.py | 13 +++ qiskit/providers/aer/aerprovider.py | 4 +- .../providers/aer/backends/pulse_simulator.py | 87 +++++++++++++++++++ .../providers/aer/backends/qasm_simulator.py | 8 +- .../aer/openpulse/cy/channel_value.pxd | 14 +-- .../aer/openpulse/cy/channel_value.pyx | 14 +-- qiskit/providers/aer/openpulse/qobj/digest.py | 12 +-- .../providers/aer/openpulse/qobj/operators.py | 2 +- .../providers/aer/openpulse/qobj/opparse.py | 6 +- .../providers/aer/openpulse/solver/codegen.py | 7 +- .../aer/openpulse/solver/monte_carlo.py | 7 +- .../providers/aer/openpulse/solver/opsolve.py | 69 +++++++-------- .../aer/openpulse/solver/rhs_utils.py | 4 +- .../providers/aer/openpulse/solver/unitary.py | 31 ++++--- 16 files changed, 199 insertions(+), 93 deletions(-) create mode 100644 qiskit/__init__.py create mode 100644 qiskit/providers/__init__.py create mode 100644 qiskit/providers/aer/backends/pulse_simulator.py diff --git a/CMakeLists.txt b/CMakeLists.txt index a586292b5d..04d4f50fc2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -185,7 +185,6 @@ set(AER_LIBRARIES # Cython build is only enabled if building through scikit-build. if(SKBUILD) # Terra Addon build add_subdirectory(qiskit/providers/aer/backends/wrappers) - add_subdirectory(qiskit/providers/aer/pulse/cython) else() # Standalone build add_executable(qasm_simulator ${AER_SIMULATOR_CPP_MAIN}) set_target_properties(qasm_simulator PROPERTIES diff --git a/qiskit/__init__.py b/qiskit/__init__.py new file mode 100644 index 0000000000..7909fc6dac --- /dev/null +++ b/qiskit/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/__init__.py b/qiskit/providers/__init__.py new file mode 100644 index 0000000000..7909fc6dac --- /dev/null +++ b/qiskit/providers/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index 38ba05ac0c..2fdba50080 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -20,6 +20,7 @@ from .backends.qasm_simulator import QasmSimulator from .backends.statevector_simulator import StatevectorSimulator from .backends.unitary_simulator import UnitarySimulator +from .backends.pulse_simulator import PulseSimulator class AerProvider(BaseProvider): @@ -31,7 +32,8 @@ def __init__(self, *args, **kwargs): # Populate the list of Aer simulator providers. self._backends = [QasmSimulator(provider=self), StatevectorSimulator(provider=self), - UnitarySimulator(provider=self)] + UnitarySimulator(provider=self), + PulseSimulator(provider=self)] def get_backend(self, name=None, **kwargs): return super().get_backend(name=name, **kwargs) diff --git a/qiskit/providers/aer/backends/pulse_simulator.py b/qiskit/providers/aer/backends/pulse_simulator.py new file mode 100644 index 0000000000..abd2dfde50 --- /dev/null +++ b/qiskit/providers/aer/backends/pulse_simulator.py @@ -0,0 +1,87 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +# pylint: disable=arguments-differ + +""" +Qiskit Aer OpenPulse simulator backend. +""" + +import uuid +import time +import datetime +import logging +from qiskit.result import Result +from qiskit.providers.models import BackendConfiguration +from .aerbackend import AerBackend +from ..aerjob import AerJob +from ..version import __version__ +from ..openpulse.qobj.digest import digest_pulse_obj +from ..openpulse.solver.opsolve import opsolve + +logger = logging.getLogger(__name__) + +class PulseSimulator(AerBackend): + """Aer OpenPulse simulator + """ + DEFAULT_CONFIGURATION = { + 'backend_name': 'pulse_simulator', + 'backend_version': "0.0.1", + 'n_qubits': 20, + 'url': 'https://github.com/Qiskit/qiskit-aer', + 'simulator': True, + 'local': True, + 'conditional': True, + 'open_pulse': True, + 'memory': False, + 'max_shots': 50000, + 'description': 'A pulse-based Hamiltonian simulator', + 'gates': [], + 'basis_gates': [] + } + + def __init__(self, configuration=None, provider=None): + super().__init__(self, + BackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION), + provider=provider) + + def run(self, qobj, validate=False): + """Run a qobj on the backend.""" + # Submit job + job_id = str(uuid.uuid4()) + aer_job = AerJob(self, job_id, self._run_job, qobj, + None, None, validate) + aer_job.submit() + return aer_job + + def _run_job(self, job_id, qobj, backend_options, noise_model, validate): + """Run a qobj job""" + start = time.time() + if validate: + self._validate(qobj, backend_options, noise_model) + openpulse_system = digest_pulse_obj(qobj.to_dict()) + results = opsolve(openpulse_system) + end = time.time() + return self._format_results(job_id, results, end - start, qobj.qobj_id) + + def _format_results(self, job_id, results, time_taken, qobj_id): + """Construct Result object from simulator output.""" + # Add result metadata + output = {} + output['qobj_id'] = qobj_id + output['results'] = results + output['success'] = True + output["job_id"] = job_id + output["date"] = datetime.datetime.now().isoformat() + output["backend_name"] = self.name() + output["backend_version"] = self.configuration().backend_version + output["time_taken"] = time_taken + return Result.from_dict(output) diff --git a/qiskit/providers/aer/backends/qasm_simulator.py b/qiskit/providers/aer/backends/qasm_simulator.py index 7fdef0a38a..c632e36d33 100644 --- a/qiskit/providers/aer/backends/qasm_simulator.py +++ b/qiskit/providers/aer/backends/qasm_simulator.py @@ -217,14 +217,14 @@ def _validate(self, qobj, backend_options, noise_model): ch_supported = False ch_supported = method in ["extended_stabilizer", "automatic"] clifford = False if method == "statevector" else clifford_noise - for op in experiment.instructions: + for inst in experiment.instructions: if not clifford and not no_measure: break # we don't need to check any more ops - if clifford and op.name not in clifford_instructions: + if clifford and inst.name not in clifford_instructions: clifford = False - if no_measure and op.name == "measure": + if no_measure and inst.name == "measure": no_measure = False - if ch_supported and op.name in unsupported_ch_instructions: + if ch_supported and inst.name in unsupported_ch_instructions: ch_supported = False # Print warning if clbits but no measure if no_measure: diff --git a/qiskit/providers/aer/openpulse/cy/channel_value.pxd b/qiskit/providers/aer/openpulse/cy/channel_value.pxd index a3faf0e350..d6a5d0d71e 100644 --- a/qiskit/providers/aer/openpulse/cy/channel_value.pxd +++ b/qiskit/providers/aer/openpulse/cy/channel_value.pxd @@ -15,10 +15,10 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -cdef complex channel_value(double t, - unsigned int chan_num, - double[::1] chan_pulse_times, - complex[::1] pulse_array, - unsigned int[::1] pulse_ints, - double[::1] fc_array, - unsigned char[::1] register) \ No newline at end of file +cdef complex chan_value(double t, + unsigned int chan_num, + double[::1] chan_pulse_times, + complex[::1] pulse_array, + unsigned int[::1] pulse_ints, + double[::1] fc_array, + unsigned char[::1] register) \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cy/channel_value.pyx b/qiskit/providers/aer/openpulse/cy/channel_value.pyx index a98dd7935e..a00b10fd43 100644 --- a/qiskit/providers/aer/openpulse/cy/channel_value.pyx +++ b/qiskit/providers/aer/openpulse/cy/channel_value.pyx @@ -39,13 +39,13 @@ cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): return floor(((t-start)/(stop-start)*len_arr)) @cython.boundscheck(False) -cdef complex channel_value(double t, - unsigned int chan_num, - double[::1] chan_pulse_times, - complex[::1] pulse_array, - unsigned int[::1] pulse_ints, - double[::1] fc_array, - unsigned char[::1] register): +cdef complex chan_value(double t, + unsigned int chan_num, + double[::1] chan_pulse_times, + complex[::1] pulse_array, + unsigned int[::1] pulse_ints, + double[::1] fc_array, + unsigned char[::1] register): """Computes the value of a given channel at time `t`. Args: diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 232dc4afae..4258ede99e 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -12,15 +12,15 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -import numpy as np from collections import OrderedDict +import numpy as np from qiskit.qiskiterror import QiskitError -from openpulse.qobj.op_system import OPSystem -from openpulse.solver.options import OPoptions -from openpulse.qobj.opparse import HamiltonianParser, NoiseParser -from openpulse.qobj.operators import init_fock_state, qubit_occ_oper +from .op_system import OPSystem +from .opparse import HamiltonianParser, NoiseParser +from .operators import init_fock_state, qubit_occ_oper +from ..solver.options import OPoptions # pylint: disable=no-name-in-module,import-error -from openpulse.cy.utils import oplist_to_array +from ..cy.utils import oplist_to_array """A module of routines for digesting a PULSE qobj into something we can actually use. diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index b180128c45..e7b0d9c1ca 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -13,7 +13,7 @@ # that they have been altered from the originals. import numpy as np import scipy.linalg as la -from openpulse.qobj import op_qobj as op +from ..qobj import op_qobj as op def gen_oper(opname, index, h_osc, h_qub, states=None): diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index e8b79f353d..1e18dfa858 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -17,8 +17,8 @@ import copy from collections import namedtuple, OrderedDict import numpy as np -from openpulse.qobj.operators import gen_oper -from openpulse.qobj import op_qobj as op +from .op_qobj import get_func +from .operators import gen_oper Token = namedtuple('Token', ('type', 'name')) @@ -267,7 +267,7 @@ def _token2qobj(self, tokens): elif token.name == '/': stack.append(op1 / op2) elif token.type in ['Func', 'Ext']: - stack.append(op.get_func(token.name, stack.pop(-1))) + stack.append(get_func(token.name, stack.pop(-1))) else: raise Exception('Invalid token %s is found' % token.name) diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index 889238a815..a2d632a91e 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -19,9 +19,8 @@ import os import sys -import numpy as np import qutip as qt -import openpulse.solver.settings as op_set +import qiskit.providers.aer.openpulse.solver.settings as op_set _cython_path = os.path.abspath(qt.cy.__file__).replace('__init__.py', '') _cython_path = _cython_path.replace("\\", "/") @@ -131,7 +130,7 @@ def channels(self): channel_lines.append("# Compute complex channel values at time `t`") for chan, idx in self.op_system.channels.items(): - chan_str = "%s = channel_value(t, %s, " %(chan, idx) + \ + chan_str = "%s = chan_value(t, %s, " %(chan, idx) + \ "%s_pulses, pulse_array, pulse_indices, " % chan + \ "%s_fc, register)" % chan channel_lines.append(chan_str) @@ -229,7 +228,7 @@ def cython_preamble(): from qutip.cy.math cimport erf from libc.math cimport pi -from openpulse.cy.channel_value cimport channel_value +from qiskit.providers.aer.openpulse.cy.channel_value cimport chan_value include """+_include_string+""" """] diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index f2a4aa9224..f30a384bec 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -23,9 +23,10 @@ from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr -from openpulse.solver.zvode import qiskit_zvode -from openpulse.cy.memory import write_memory -from openpulse.cy.measure import occ_probabilities, write_shots_memory +from qiskit.providers.aer.openpulse.solver.zvode import qiskit_zvode +from qiskit.providers.aer.openpulse.cy.memory import write_memory +from qiskit.providers.aer.openpulse.cy.measure import (occ_probabilities, + write_shots_memory) dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index 20dba5590d..8004ddde9d 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -11,30 +11,24 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -import os -import sys -import math + +"""The main OpenPulse solver routine. +""" + import time import numpy as np from numpy.random import RandomState, randint -from scipy.integrate import ode -import scipy.sparse as sp -from scipy.integrate._ode import zvode from scipy.linalg.blas import get_blas_funcs from collections import OrderedDict -import qutip as qt -from qutip.qobj import Qobj from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from qutip.cy.utilities import _cython_build_cleanup -from openpulse.qobj.operators import apply_projector -import qutip.settings -from openpulse.solver.codegen import OPCodegen -from openpulse.solver.rhs_utils import _op_generate_rhs, _op_func_load -from openpulse.solver.data_config import op_data_config -import openpulse.solver.settings as settings -from openpulse.solver.unitary import unitary_evolution -from openpulse.solver.monte_carlo import monte_carlo -from qiskit.tools.parallel import parallel_map +from ..qobj.operators import apply_projector +from .codegen import OPCodegen +from .rhs_utils import _op_generate_rhs, _op_func_load +from .data_config import op_data_config +from .unitary import unitary_evolution +from .monte_carlo import monte_carlo +from qiskit.tools.parallel import parallel_map, CPU_COUNT dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -51,9 +45,9 @@ def opsolve(op_system): if not op_system.initial_state.isket: raise Exception("Initial state must be a state vector.") - # set num_cpus to the value given in qutip.settings if none in Options + # set num_cpus to the value given in settings if none in Options if not op_system.ode_options.num_cpus: - op_system.ode_options.num_cpus = qutip.settings.num_cpus + op_system.ode_options.num_cpus = CPU_COUNT # build Hamiltonian data structures op_data_config(op_system) @@ -62,9 +56,9 @@ def opsolve(op_system): # Load cython function _op_func_load(op_system) # load monte carlo class - mc = OP_mcwf(op_system) + montecarlo = OP_mcwf(op_system) # Run the simulation - out = mc.run() + out = montecarlo.run() # Results are stored in ophandler.result return out @@ -112,8 +106,9 @@ def run(self): results = parallel_map(unitary_evolution, self.op_system.experiments, task_args=(self.op_system.global_data, - self.op_system.ode_options - ), **map_kwargs + self.op_system.ode_options + ), + **map_kwargs ) # need to simulate each trajectory, so shots*len(experiments) times @@ -126,16 +121,17 @@ def run(self): seeds = rng.randint(np.iinfo(np.int32).max-1, size=self.op_system.global_data['shots']) exp_res = parallel_map(monte_carlo, - seeds, - task_args=(exp, self.op_system.global_data, - self.op_system.ode_options - ), **map_kwargs - ) + seeds, + task_args=(exp, self.op_system.global_data, + self.op_system.ode_options + ), + **map_kwargs + ) unique = np.unique(exp_res, return_counts=True) hex_dict = {} - for kk in range(unique[0].shape[0]): - key = hex(unique[0][kk]) - hex_dict[key] = unique[1][kk] + for idx in range(unique[0].shape[0]): + key = hex(unique[0][idx]) + hex_dict[key] = unique[1][idx] end = time.time() results = {'name': exp['name'], 'seed_simulator': exp['seed'], @@ -144,8 +140,8 @@ def run(self): 'success': True, 'time_taken': (end - start), 'header': {}} - - results['data'] = {'counts': hex_dict} + + results['data'] = {'counts': hex_dict} all_results.append(results) @@ -200,10 +196,3 @@ def _proj_measurement(pid, ophandler, tt, state, memory, register=None): psi_proj = state return psi_proj - - -# ----------------------------------------------------------------------------- -# single-trajectory for monte carlo -# ----------------------------------------------------------------------------- - - diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index fd147f3916..36852a8c3a 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -13,8 +13,8 @@ # that they have been altered from the originals. import os -from openpulse.solver.codegen import OPCodegen -import openpulse.solver.settings as op_set +from qiskit.providers.aer.openpulse.solver.codegen import OPCodegen +import qiskit.providers.aer.openpulse.solver.settings as op_set def _op_generate_rhs(op_system): diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index 6f56e84285..703b9cd69a 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -11,13 +11,17 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable = unused-variable, no-name-in-module +# pylint: disable=unused-variable, no-name-in-module, protected-access, +# pylint: disable=invalid-name + +"""Module for unitary pulse evolution. +""" import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs -from openpulse.cy.memory import write_memory -from openpulse.cy.measure import occ_probabilities, write_shots_memory +from qiskit.providers.aer.openpulse.cy.measure import (occ_probabilities, + write_shots_memory) dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -36,7 +40,7 @@ def unitary_evolution(exp, global_data, ode_options): Returns: Stuff """ - cy_rhs_func = global_data['rhs_func'] + cy_rhs_func = global_data['rhs_func'] rng = np.random.RandomState(exp['seed']) tlist = exp['tlist'] snapshots = [] @@ -62,17 +66,17 @@ def unitary_evolution(exp, global_data, ode_options): _inst = 'ODE.set_f_params(%s)' % global_data['string'] code = compile(_inst, '', 'exec') - exec(code) + exec(code) # pylint disable=exec-used - if not len(ODE._y): + if not ODE._y: ODE.t = 0.0 ODE._y = np.array([0.0], complex) ODE._integrator.reset(len(ODE._y), ODE.jac is not None) # Since all experiments are defined to start at zero time. - ODE.set_initial_value(global_data['initial_state'], 0) - for kk in tlist[1:]: - ODE.integrate(kk, step=0) + ODE.set_initial_value(global_data['initial_state'], 0) + for time in tlist[1:]: + ODE.integrate(time, step=0) if ODE.successful(): psi = ODE.y / dznrm2(ODE.y) else: @@ -82,8 +86,7 @@ def unitary_evolution(exp, global_data, ode_options): # Do any snapshots here # set channel and frame change indexing arrays - - + # Do final measurement at end qubits = exp['acquire'][0][1] memory_slots = exp['acquire'][0][2] @@ -91,14 +94,14 @@ def unitary_evolution(exp, global_data, ode_options): rand_vals = rng.rand(memory_slots.shape[0]*shots) write_shots_memory(memory, memory_slots, probs, rand_vals) int_mem = memory.dot(np.power(2.0, - np.arange(memory.shape[1]-1,-1,-1))).astype(int) + np.arange(memory.shape[1]-1, -1, -1))).astype(int) if global_data['memory']: hex_mem = [hex(val) for val in int_mem] return hex_mem # Get hex counts dict - unique = np.unique(int_mem, return_counts = True) + unique = np.unique(int_mem, return_counts=True) hex_dict = {} for kk in range(unique[0].shape[0]): key = hex(unique[0][kk]) hex_dict[key] = unique[1][kk] - return hex_dict \ No newline at end of file + return hex_dict From 24d492a8ae803a57b46ad64911ffc9b7bb2bb7ca Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Sun, 7 Jul 2019 04:50:03 -0400 Subject: [PATCH 18/31] license updates --- .../aer/openpulse/qutip_lite/__init__.py | 15 ++++++++++ .../aer/openpulse/qutip_lite/cy/pyxbuilder.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/cy/utilities.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/expect.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/fastsparse.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/graph.py | 15 ++++++++++ .../aer/openpulse/qutip_lite/hardware_info.py | 15 ++++++++++ .../aer/openpulse/qutip_lite/operators.py | 15 ++++++++++ .../aer/openpulse/qutip_lite/permute.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/propagator.py | 15 ++++++++++ .../aer/openpulse/qutip_lite/qobj.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/settings.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/sparse.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/states.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/superoperator.py | 14 ++++++++++ .../aer/openpulse/qutip_lite/tensor.py | 28 ++++++++++++++----- 16 files changed, 236 insertions(+), 7 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/__init__.py b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py index b751727579..cd51b99d40 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/__init__.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/__init__.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py index 57c72d2e69..acd1e9fbe0 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, The QuTiP Project diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py index 4cc23b7002..f79772eaa5 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/expect.py b/qiskit/providers/aer/openpulse/qutip_lite/expect.py index 60f4091cfc..35c1bb22ae 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/expect.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/expect.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py index 23833797ff..3ced24f3b9 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, The QuTiP Project. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/graph.py b/qiskit/providers/aer/openpulse/qutip_lite/graph.py index bac840c452..f0aea3e0ce 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/graph.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/graph.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py b/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py index 02d10ac009..aef2c269d4 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/operators.py b/qiskit/providers/aer/openpulse/qutip_lite/operators.py index dfeeac4886..c4589027a9 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/operators.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/operators.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/permute.py b/qiskit/providers/aer/openpulse/qutip_lite/permute.py index 15d684221a..de41f3a3b7 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/permute.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/permute.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/propagator.py b/qiskit/providers/aer/openpulse/qutip_lite/propagator.py index 0d35b24a8c..db257bf15b 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/propagator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/propagator.py @@ -1,3 +1,18 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index f774b9e12e..234d836e9d 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/settings.py b/qiskit/providers/aer/openpulse/qutip_lite/settings.py index 2f9b37e85f..ab17dd3c89 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/settings.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/settings.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py index f0d9e8e789..42321085ac 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/states.py b/qiskit/providers/aer/openpulse/qutip_lite/states.py index ea6d9d0001..e01572dd0b 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/states.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/states.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py index ca39535482..12f4a707ac 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. diff --git a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py index 32cdc41f47..81a18e4ce6 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. @@ -39,7 +53,6 @@ ] import numpy as np -import scipy.sparse as sp from qutip.cy.spmath import zcsr_kron from qutip.qobj import Qobj from qutip.permute import reshuffle @@ -113,8 +126,8 @@ def tensor(*args): out.data = q.data out.dims = q.dims else: - out.data = zcsr_kron(out.data, q.data) - + out.data = zcsr_kron(out.data, q.data) + out.dims = [out.dims[0] + q.dims[0], out.dims[1] + q.dims[1]] out.isherm = out.isherm and q.isherm @@ -262,10 +275,10 @@ def _tensor_contract_single(arr, i, j): if arr.shape[i] != arr.shape[j]: raise ValueError("Cannot contract over indices of different length.") idxs = np.arange(arr.shape[i]) - sl = tuple(slice(None, None, None) - if idx not in (i, j) else idxs for idx in range(arr.ndim)) + sltuple = tuple(slice(None, None, None) + if idx not in (i, j) else idxs for idx in range(arr.ndim)) contract_at = i if j == i + 1 else 0 - return np.sum(arr[sl], axis=contract_at) + return np.sum(arr[sltuple], axis=contract_at) def _tensor_contract_dense(arr, *pairs): @@ -384,4 +397,5 @@ def tensor_contract(qobj, *pairs): # Return back as a qobj. return Qobj(qmtx, dims=contracted_dims, superrep=qobj.superrep) -import qutip.states +# pylint: disable=wrong-import-position +import states From 2f4188d7bec5368afa1cdb4094077a47a6583b95 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 29 Jul 2019 13:58:26 -0400 Subject: [PATCH 19/31] add cython build to setup --- setup.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 188d7a1045..f17ede47ac 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,15 @@ +# pylint: disable=invalid-name + +""" +Main setup file for qiskit-aer +""" + import os +import sys try: from skbuild import setup dummy_install = False -except: +except ImportError: print(""" WARNING ======= scikit-build package is needed to build Aer sources. @@ -23,6 +30,8 @@ def find_qiskit_aer_packages(): + """Finds qiskit aer packages. + """ location = 'qiskit/providers' prefix = 'qiskit.providers' aer_packages = find_packages(where=location) @@ -32,12 +41,58 @@ def find_qiskit_aer_packages(): ) return pkg_list +EXT_MODULES = [] +# OpenPulse setup +if "--with-openpulse" in sys.argv: + sys.argv.remove("--with-openpulse") + # pylint: disable=ungrouped-imports + import numpy as np + import distutils.sysconfig + from setuptools import Extension + from Cython.Build import cythonize + from Cython.Distutils import build_ext # pylint: disable=unused-import + INCLUDE_DIRS = [np.get_include()] + # Add Cython OP extensions here + OP_EXTS = ['channel_value', 'measure', 'memory', 'utils'] + + # Extra link args + link_flags = [] + # If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) + if (sys.platform == 'win32' and int(str(sys.version_info[0])+str(sys.version_info[1])) >= 35 + and os.environ.get('MSYSTEM') is None): + compiler_flags = [] + # Everything else + else: + compiler_flags = ['-O2', '-funroll-loops'] + if sys.platform == 'darwin': + # These are needed for compiling on OSX 10.14+ + compiler_flags.append('-mmacosx-version-min=10.9') + link_flags.append('-mmacosx-version-min=10.9') + + # Remove -Wstrict-prototypes from cflags + CFG_VARS = distutils.sysconfig.get_config_vars() + if "CFLAGS" in CFG_VARS: + CFG_VARS["CFLAGS"] = CFG_VARS["CFLAGS"].replace("-Wstrict-prototypes", "") + + # Add Cython files from cy + for ext in OP_EXTS: + _mod = Extension("qiskit.providers.aer.openpulse.cy."+ext, + sources=['qiskit/providers/aer/openpulse/cy/'+ext+'.pyx'], + include_dirs=[np.get_include()], + extra_compile_args=compiler_flags, + extra_link_args=link_flags, + language='c++') + EXT_MODULES.append(_mod) + + # Cythonize + EXT_MODULES = cythonize(EXT_MODULES) setup( name='qiskit-aer', version=VERSION, packages=find_qiskit_aer_packages() if not dummy_install else [], cmake_source_dir='.', + ext_modules=EXT_MODULES, description="Qiskit Aer - High performance simulators for Qiskit", url="https://github.com/Qiskit/qiskit-aer", author="AER Development Team", From 749a02b53f64373ff785b74f3d2149e479af7b58 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 29 Jul 2019 14:12:58 -0400 Subject: [PATCH 20/31] build qutip_lite local --- .../aer/openpulse/qutip_lite/cy/ptrace.pyx | 4 ++-- .../openpulse/qutip_lite/cy/sparse_routines.pxi | 4 ++-- .../aer/openpulse/qutip_lite/cy/sparse_utils.pyx | 2 +- .../aer/openpulse/qutip_lite/cy/spconvert.pxd | 2 +- .../aer/openpulse/qutip_lite/cy/spconvert.pyx | 2 +- .../aer/openpulse/qutip_lite/cy/spmath.pxd | 2 +- .../aer/openpulse/qutip_lite/cy/spmath.pyx | 3 +-- setup.py | 16 +++++++++++++++- 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx index 866c55d01e..0fe48f14a2 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx @@ -33,8 +33,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### import numpy as np -from qutip.cy.spconvert import zcsr_reshape -from qutip.cy.spmath import zcsr_mult +from qiskit.providers.aer.openpulse.qutip_lite.cy.spconvert import zcsr_reshape +from qiskit.providers.aer.openpulse.qutip_lite.cy.spmath import zcsr_mult from qutip.fastsparse import fast_csr_matrix, csr2fast cimport numpy as cnp cimport cython diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi index 0564c3b8cb..983d21b908 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_routines.pxi @@ -34,12 +34,12 @@ ############################################################################### import numpy as np from scipy.sparse import coo_matrix -from qutip.fastsparse import fast_csr_matrix +from qiskit.providers.aer.openpulse.qutip_lite.fastsparse import fast_csr_matrix cimport numpy as np cimport cython from libcpp.algorithm cimport sort from libcpp.vector cimport vector -from qutip.cy.sparse_structs cimport CSR_Matrix, COO_Matrix +from qiskit.providers.aer.openpulse.qutip_lite.cy.sparse_structs cimport CSR_Matrix, COO_Matrix np.import_array() cdef extern from "numpy/arrayobject.h" nogil: diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx index beb04563e1..c0d925bb2b 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/sparse_utils.pyx @@ -33,7 +33,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### import numpy as np -from qutip.fastsparse import fast_csr_matrix +from qiskit.providers.aer.openpulse.qutip_lite.fastsparse import fast_csr_matrix cimport numpy as cnp from libc.math cimport abs, fabs, sqrt from libcpp cimport bool diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd index df2785fb77..8c74a9d3c0 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pxd @@ -33,7 +33,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -from qutip.cy.sparse_structs cimport CSR_Matrix +from qiskit.providers.aer.openpulse.qutip_lite.cy.sparse_structs cimport CSR_Matrix cdef void fdense2D_to_CSR(complex[::1, :] mat, CSR_Matrix * out, unsigned int nrows, unsigned int ncols) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx index 0a4abe24e4..eeb5f0d51a 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spconvert.pyx @@ -33,7 +33,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### import numpy as np -from qutip.fastsparse import fast_csr_matrix +from qiskit.providers.aer.openpulse.qutip_lite.fastsparse import fast_csr_matrix cimport numpy as cnp cimport cython from libc.stdlib cimport div, malloc, free diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd index cccc021a97..789c2c9fcc 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pxd @@ -33,7 +33,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -from qutip.cy.sparse_structs cimport CSR_Matrix +from qiskit.providers.aer.openpulse.qutip_lite.cy.sparse_structs cimport CSR_Matrix cdef void _zcsr_add(CSR_Matrix * A, CSR_Matrix * B, CSR_Matrix * C, double complex alpha) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx index ae8ffad3e2..aa0273787c 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmath.pyx @@ -33,7 +33,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### import numpy as np -import qutip.settings as qset cimport numpy as cnp cimport cython from libcpp cimport bool @@ -556,7 +555,7 @@ cdef void _zcsr_adjoint_core(double complex * data, int * ind, int * ptr, @cython.boundscheck(False) @cython.wraparound(False) -def zcsr_isherm(object A not None, double tol = qset.atol): +def zcsr_isherm(object A not None, double tol = 1e-12): """ Determines if a given input sparse CSR matrix is Hermitian to within a specified floating-point tolerance. diff --git a/setup.py b/setup.py index f17ede47ac..8c76d8993e 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,8 @@ from setuptools import find_packages requirements = [ - "numpy>=1.13" + "numpy>=1.13", + "cython>=0.27.1", ] VERSION_PATH = os.path.join(os.path.dirname(__file__), @@ -55,6 +56,9 @@ def find_qiskit_aer_packages(): # Add Cython OP extensions here OP_EXTS = ['channel_value', 'measure', 'memory', 'utils'] + Q_EXTS = ['spmatfuncs', 'sparse_utils', 'graph_utils', + 'spmath', 'math', 'spconvert', 'ptrace'] + # Extra link args link_flags = [] # If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) @@ -84,6 +88,16 @@ def find_qiskit_aer_packages(): language='c++') EXT_MODULES.append(_mod) + for ext in Q_EXTS: + _mod = Extension('qiskit.providers.aer.openpulse.qutip_lite.cy.'+ext, + sources=['qiskit/providers/aer/openpulse/qutip_lite/cy/'+ext+'.pyx', + 'qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp'], + include_dirs = [np.get_include()], + extra_compile_args=compiler_flags, + extra_link_args=link_flags, + language='c++') + EXT_MODULES.append(_mod) + # Cythonize EXT_MODULES = cythonize(EXT_MODULES) From 89b3e370dd514639a224e159243d907a93cb961c Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Mon, 29 Jul 2019 14:13:39 -0400 Subject: [PATCH 21/31] lint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8c76d8993e..447ec236f4 100644 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ def find_qiskit_aer_packages(): _mod = Extension('qiskit.providers.aer.openpulse.qutip_lite.cy.'+ext, sources=['qiskit/providers/aer/openpulse/qutip_lite/cy/'+ext+'.pyx', 'qiskit/providers/aer/openpulse/qutip_lite/cy/src/zspmv.cpp'], - include_dirs = [np.get_include()], + include_dirs=[np.get_include()], extra_compile_args=compiler_flags, extra_link_args=link_flags, language='c++') From 1ee8de68eba2adb653b0e147a9052b19bed3debb Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 30 Jul 2019 14:18:07 -0400 Subject: [PATCH 22/31] remove qutip dependency --- .gitignore | 6 +- .../providers/aer/backends/pulse_simulator.py | 1 + qiskit/providers/aer/openpulse/cy/measure.pyx | 2 +- qiskit/providers/aer/openpulse/qobj/digest.py | 2 +- .../providers/aer/openpulse/qobj/op_qobj.py | 46 +- .../openpulse/qutip_lite/cy/graph_utils.pyx | 430 --------- .../aer/openpulse/qutip_lite/cy/ptrace.pyx | 262 ------ .../aer/openpulse/qutip_lite/dimensions.py | 382 ++++++++ .../aer/openpulse/qutip_lite/expect.py | 106 +-- .../aer/openpulse/qutip_lite/fastsparse.py | 84 +- .../aer/openpulse/qutip_lite/graph.py | 310 ------- .../aer/openpulse/qutip_lite/hardware_info.py | 150 --- .../aer/openpulse/qutip_lite/operators.py | 147 +-- .../aer/openpulse/qutip_lite/permute.py | 188 ---- .../aer/openpulse/qutip_lite/propagator.py | 335 ------- .../aer/openpulse/qutip_lite/qobj.py | 873 ++---------------- .../aer/openpulse/qutip_lite/settings.py | 45 +- .../aer/openpulse/qutip_lite/sparse.py | 16 +- .../aer/openpulse/qutip_lite/states.py | 724 +-------------- .../aer/openpulse/qutip_lite/superop_reps.py | 575 ++++++++++++ .../aer/openpulse/qutip_lite/superoperator.py | 106 +-- .../aer/openpulse/qutip_lite/tensor.py | 279 +----- qiskit/providers/aer/openpulse/setup.py | 74 -- .../providers/aer/openpulse/solver/codegen.py | 8 +- .../aer/openpulse/solver/monte_carlo.py | 2 +- .../providers/aer/openpulse/solver/opsolve.py | 4 +- setup.py | 6 +- 27 files changed, 1208 insertions(+), 3955 deletions(-) delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/dimensions.py delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/graph.py delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/permute.py delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/propagator.py create mode 100755 qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py delete mode 100644 qiskit/providers/aer/openpulse/setup.py diff --git a/.gitignore b/.gitignore index 01641bb54d..d113627f10 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,7 @@ contrib/standalone/version.hpp *.env *.idea/ .DS_Store -qiskit/providers/aer/openpulse/cython/channel_value.cpp -qiskit/providers/aer/openpulse/cython/measure.cpp -qiskit/providers/aer/openpulse/cython/memory.cpp -qiskit/providers/aer/openpulse/cython/utils.cpp *.ipynb test/.asv +*.cpp +qiskit/providers/aer/backends/libomp.dylib diff --git a/qiskit/providers/aer/backends/pulse_simulator.py b/qiskit/providers/aer/backends/pulse_simulator.py index abd2dfde50..63cc9f6eeb 100644 --- a/qiskit/providers/aer/backends/pulse_simulator.py +++ b/qiskit/providers/aer/backends/pulse_simulator.py @@ -36,6 +36,7 @@ class PulseSimulator(AerBackend): 'backend_name': 'pulse_simulator', 'backend_version': "0.0.1", 'n_qubits': 20, + 'coupling_map': None, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, diff --git a/qiskit/providers/aer/openpulse/cy/measure.pyx b/qiskit/providers/aer/openpulse/cy/measure.pyx index 0c5fda6be1..dd5f64c7a6 100644 --- a/qiskit/providers/aer/openpulse/cy/measure.pyx +++ b/qiskit/providers/aer/openpulse/cy/measure.pyx @@ -20,7 +20,7 @@ cimport cython import numpy as np cimport numpy as np -from qutip.cy.spmatfuncs import cy_expect_psi_csr +from qiskit.providers.aer.openpulse.qutip_lite.cy.spmatfuncs import cy_expect_psi_csr @cython.boundscheck(False) def occ_probabilities(unsigned int[::1] qubits, complex[::1] state, list meas_ops): diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 4258ede99e..eba21ca962 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -14,7 +14,7 @@ from collections import OrderedDict import numpy as np -from qiskit.qiskiterror import QiskitError +from qiskit.exceptions import QiskitError from .op_system import OPSystem from .opparse import HamiltonianParser, NoiseParser from .operators import init_fock_state, qubit_occ_oper diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py index a3d94eb111..ae77d5f1a9 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_qobj.py +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -14,17 +14,17 @@ # pylint: disable=invalid-name import numpy as np -import qutip as qt - -from qutip.cy.spmatfuncs import (spmv_csr, cy_expect_psi_csr) -from qutip.fastsparse import fast_csr_matrix - +import qiskit.providers.aer.openpulse.qutip_lite.operators as ops +import qiskit.providers.aer.openpulse.qutip_lite.states as st +import qiskit.providers.aer.openpulse.qutip_lite.tensor as ten +from ..qutip_lite.qobj import Qobj +from ..qutip_lite.cy.spmatfuncs import (spmv_csr, cy_expect_psi_csr) def sigmax(dim=2): """Qiskit wrapper of sigma-X operator. """ if dim == 2: - return qt.sigmax() + return ops.sigmax() else: raise Exception('Invalid level specification of the qubit subspace') @@ -33,7 +33,7 @@ def sigmay(dim=2): """Qiskit wrapper of sigma-Y operator. """ if dim == 2: - return qt.sigmay() + return ops.sigmay() else: raise Exception('Invalid level specification of the qubit subspace') @@ -42,7 +42,7 @@ def sigmaz(dim=2): """Qiskit wrapper of sigma-Z operator. """ if dim == 2: - return qt.sigmaz() + return ops.sigmaz() else: raise Exception('Invalid level specification of the qubit subspace') @@ -50,37 +50,37 @@ def sigmaz(dim=2): def sigmap(dim=2): """Qiskit wrapper of sigma-plus operator. """ - return qt.create(dim) + return ops.create(dim) def sigmam(dim=2): """Qiskit wrapper of sigma-minus operator. """ - return qt.destroy(dim) + return ops.destroy(dim) def create(dim): """Qiskit wrapper of creation operator. """ - return qt.create(dim) + return ops.create(dim) def destroy(dim): """Qiskit wrapper of annihilation operator. """ - return qt.destroy(dim) + return ops.destroy(dim) def num(dim): """Qiskit wrapper of number operator. """ - return qt.num(dim) + return ops.num(dim) def qeye(dim): """Qiskit wrapper of identity operator. """ - return qt.qeye(dim) + return ops.qeye(dim) def project(dim, states): @@ -88,7 +88,7 @@ def project(dim, states): """ ket, bra = states if ket in range(dim) and bra in range(dim): - return qt.basis(dim, ket) * qt.basis(dim, bra).dag() + return st.basis(dim, ket) * st.basis(dim, bra).dag() else: raise Exception('States are specified on the outside of Hilbert space %s', states) @@ -96,13 +96,13 @@ def project(dim, states): def tensor(list_qobj): """ Qiskit wrapper of tensor product """ - return qt.tensor(list_qobj) + return ten.tensor(list_qobj) def conj(val): """ Qiskit wrapper of conjugate """ - if isinstance(val, qt.qobj.Qobj): + if isinstance(val, Qobj): return val.conj() else: return np.conj(val) @@ -111,7 +111,7 @@ def conj(val): def sin(val): """ Qiskit wrapper of sine function """ - if isinstance(val, qt.qobj.Qobj): + if isinstance(val, Qobj): return val.sinm() else: return np.sin(val) @@ -120,7 +120,7 @@ def sin(val): def cos(val): """ Qiskit wrapper of cosine function """ - if isinstance(val, qt.qobj.Qobj): + if isinstance(val, Qobj): return val.cosm() else: return np.cos(val) @@ -129,7 +129,7 @@ def cos(val): def exp(val): """ Qiskit wrapper of exponential function """ - if isinstance(val, qt.qobj.Qobj): + if isinstance(val, Qobj): return val.expm() else: return np.exp(val) @@ -138,7 +138,7 @@ def exp(val): def sqrt(val): """ Qiskit wrapper of square root """ - if isinstance(val, qt.qobj.Qobj): + if isinstance(val, Qobj): return val.sqrtm() else: return np.sqrt(val) @@ -159,13 +159,13 @@ def dammy(qobj): def basis(level, pos): """ Qiskit wrapper of basis """ - return qt.basis(level, pos) + return st.basis(level, pos) def fock_dm(level, eigv): """ Qiskit wrapper of fock_dm """ - return qt.fock_dm(level, eigv) + return st.fock_dm(level, eigv) def opr_prob(opr, state_vec): diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx deleted file mode 100755 index f87988a301..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/graph_utils.pyx +++ /dev/null @@ -1,430 +0,0 @@ -#!python -#cython: language_level=3 -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### -import numpy as np -cimport numpy as cnp -cimport cython -from libcpp.algorithm cimport sort -from libcpp.vector cimport vector -cnp.import_array() - -include "parameters.pxi" - -cdef extern from "numpy/arrayobject.h" nogil: - void PyArray_ENABLEFLAGS(cnp.ndarray arr, int flags) - void PyDataMem_FREE(void * ptr) - void PyDataMem_RENEW(void * ptr, size_t size) - void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) - void PyDataMem_NEW(size_t size) - -#Struct used for arg sorting -cdef struct _int_pair: - int data - int idx - -ctypedef _int_pair int_pair -ctypedef int (*cfptr)(int_pair, int_pair) - -@cython.boundscheck(False) -@cython.wraparound(False) -cdef int int_sort(int_pair x, int_pair y): - return x.data < y.data - - -@cython.boundscheck(False) -@cython.wraparound(False) -cdef int * int_argsort(int * x, int nrows): - cdef vector[int_pair] pairs - cdef cfptr cfptr_ = &int_sort - cdef size_t kk - pairs.resize(nrows) - for kk in range(nrows): - pairs[kk].data = x[kk] - pairs[kk].idx = kk - - sort(pairs.begin(),pairs.end(),cfptr_) - cdef int * out = PyDataMem_NEW(nrows *sizeof(int)) - for kk in range(nrows): - out[kk] = pairs[kk].idx - return out - - -@cython.boundscheck(False) -@cython.wraparound(False) -cpdef int[::1] _node_degrees(int[::1] ind, int[::1] ptr, - unsigned int num_rows): - - cdef size_t ii, jj - cdef int[::1] degree = np.zeros(num_rows, dtype=np.int32) - - for ii in range(num_rows): - degree[ii] = ptr[ii + 1] - ptr[ii] - for jj in range(ptr[ii], ptr[ii + 1]): - if ind[jj] == ii: - # add one if the diagonal is in row ii - degree[ii] += 1 - break - - return degree - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _breadth_first_search( - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ind, - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptr, - int num_rows, int seed): - """ - Does a breath first search (BSF) of a graph in sparse CSR format matrix - form starting at a given seed node. - """ - - cdef unsigned int i, j, ii, jj, N = 1 - cdef unsigned int level_start = 0 - cdef unsigned int level_end = N - cdef unsigned int current_level = 1 - cdef cnp.ndarray[ITYPE_t] order = -1 * np.ones(num_rows, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] level = -1 * np.ones(num_rows, dtype=ITYPE) - - level[seed] = 0 - order[0] = seed - - while level_start < level_end: - # for nodes of the last level - for ii in range(level_start, level_end): - i = order[ii] - # add unvisited neighbors to queue - for jj in range(ptr[i], ptr[i + 1]): - j = ind[jj] - if level[j] == -1: - order[N] = j - level[j] = current_level - N += 1 - - level_start = level_end - level_end = N - current_level += 1 - - return order, level - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _reverse_cuthill_mckee(int[::1] ind, int[::1] ptr, int num_rows): - """ - Reverse Cuthill-McKee ordering of a sparse csr or csc matrix. - """ - cdef unsigned int N = 0, N_old, seed, level_start, level_end - cdef unsigned int zz, i, j, ii, jj, kk, ll, level_len, temp, temp2 - cdef cnp.ndarray[int, ndim=1] order = np.zeros(num_rows, dtype=np.int32) - cdef int[::1] degree = _node_degrees(ind, ptr, num_rows) - cdef int * inds = int_argsort(°ree[0], num_rows) - cdef int * rev_inds = int_argsort(inds, num_rows) - cdef int * temp_degrees = NULL - - # loop over zz takes into account possible disconnected graph. - for zz in range(num_rows): - if inds[zz] != -1: # Do BFS with seed=inds[zz] - seed = inds[zz] - order[N] = seed - N += 1 - inds[rev_inds[seed]] = -1 - level_start = N - 1 - level_end = N - - while level_start < level_end: - for ii in range(level_start, level_end): - i = order[ii] - N_old = N - - # add unvisited neighbors - for jj in range(ptr[i], ptr[i + 1]): - # j is node number connected to i - j = ind[jj] - if inds[rev_inds[j]] != -1: - inds[rev_inds[j]] = -1 - order[N] = j - N += 1 - - # Add values to temp_degrees array for insertion sort - temp_degrees = PyDataMem_RENEW(temp_degrees, (N-N_old)*sizeof(int)) - level_len = 0 - for kk in range(N_old, N): - temp_degrees[level_len] = degree[order[kk]] - level_len += 1 - - # Do insertion sort for nodes from lowest to highest degree - for kk in range(1, level_len): - temp = temp_degrees[kk] - temp2 = order[N_old+kk] - ll = kk - while (ll > 0) and (temp < temp_degrees[ll-1]): - temp_degrees[ll] = temp_degrees[ll-1] - order[N_old+ll] = order[N_old+ll-1] - ll -= 1 - temp_degrees[ll] = temp - order[N_old+ll] = temp2 - - # set next level start and end ranges - level_start = level_end - level_end = N - - if N == num_rows: - break - PyDataMem_FREE(inds) - PyDataMem_FREE(rev_inds) - PyDataMem_FREE(temp_degrees) - # return reversed order for RCM ordering - return order[::-1] - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _pseudo_peripheral_node( - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ind, - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptr, - int num_rows): - """ - Find a pseudo peripheral node of a graph represented by a sparse - csr_matrix. - """ - - cdef unsigned int ii, jj, delta, flag, node, start - cdef int maxlevel, minlevel, minlastnodesdegree - cdef cnp.ndarray[cnp.intp_t] lastnodes - cdef cnp.ndarray[cnp.intp_t] lastnodesdegree - cdef cnp.ndarray[cnp.intp_t] degree = np.zeros(num_rows, dtype=ITYPE) - - degree = _node_degrees(ind, ptr, num_rows).astype(ITYPE) - start = 0 - delta = 0 - flag = 1 - - while flag: - # do a level-set traversal from x - order, level = _breadth_first_search(ind, ptr, num_rows, start) - - # select node in last level with min degree - maxlevel = max(level) - lastnodes = np.where(level == maxlevel)[0] - lastnodesdegree = degree[lastnodes] - minlastnodesdegree = min(lastnodesdegree) - node = np.where(lastnodesdegree == minlastnodesdegree)[0][0] - node = lastnodes[node] - - # if d(x,y) > delta, set, and do another BFS from this minimal node - if level[node] > delta: - start = node - delta = level[node] - else: - flag = 0 - - return start, order, level - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _maximum_bipartite_matching( - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] inds, - cnp.ndarray[ITYPE_t, ndim=1, mode="c"] ptrs, - int n): - - cdef cnp.ndarray[ITYPE_t] visited = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] queue = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] previous = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] match = -1 * np.ones(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] row_match = -1 * np.ones(n, dtype=ITYPE) - cdef int queue_ptr, queue_col, ptr, i, j, queue_size - cdef int row, col, temp, eptr, next_num = 1 - - for i in range(n): - if match[i] == -1 and (ptrs[i] != ptrs[i + 1]): - queue[0] = i - queue_ptr = 0 - queue_size = 1 - while (queue_size > queue_ptr): - queue_col = queue[queue_ptr] - queue_ptr += 1 - eptr = ptrs[queue_col + 1] - for ptr in range(ptrs[queue_col], eptr): - row = inds[ptr] - temp = visited[row] - if (temp != next_num and temp != -1): - previous[row] = queue_col - visited[row] = next_num - col = row_match[row] - if (col == -1): - while (row != -1): - col = previous[row] - temp = match[col] - match[col] = row - row_match[row] = col - row = temp - next_num += 1 - queue_size = 0 - break - else: - queue[queue_size] = col - queue_size += 1 - - if match[i] == -1: - for j in range(1, queue_size): - visited[match[queue[j]]] = -1 - - return match - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _max_row_weights( - double[::1] data, - int[::1] inds, - int[::1] ptrs, - int ncols): - """ - Finds the largest abs value in each matrix column - and the max. total number of elements in the cols (given by weights[-1]). - - Here we assume that the user already took the ABS value of the data. - This keeps us from having to call abs over and over. - - """ - cdef cnp.ndarray[DTYPE_t] weights = np.zeros(ncols + 1, dtype=DTYPE) - cdef int ln, mx, ii, jj - cdef DTYPE_t weight, current - - mx = 0 - for jj in range(ncols): - ln = (ptrs[jj + 1] - ptrs[jj]) - if ln > mx: - mx = ln - - weight = data[ptrs[jj]] - for ii in range(ptrs[jj] + 1, ptrs[jj + 1]): - current = data[ii] - if current > weight: - weight = current - - weights[jj] = weight - - weights[ncols] = mx - return weights - - -@cython.boundscheck(False) -@cython.wraparound(False) -def _weighted_bipartite_matching( - double[::1] data, - int[::1] inds, - int[::1] ptrs, - int n): - """ - Here we assume that the user already took the ABS value of the data. - This keeps us from having to call abs over and over. - """ - - cdef cnp.ndarray[ITYPE_t] visited = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] queue = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] previous = np.zeros(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] match = -1 * np.ones(n, dtype=ITYPE) - cdef cnp.ndarray[ITYPE_t] row_match = -1 * np.ones(n, dtype=ITYPE) - cdef cnp.ndarray[DTYPE_t] weights = _max_row_weights(data, inds, ptrs, n) - cdef cnp.ndarray[ITYPE_t] order = np.argsort(-weights[0:n]).astype(ITYPE) - cdef cnp.ndarray[ITYPE_t] row_order = np.zeros(int(weights[n]), dtype=ITYPE) - cdef cnp.ndarray[DTYPE_t] temp_weights = np.zeros(int(weights[n]), dtype=DTYPE) - cdef int queue_ptr, queue_col, queue_size, next_num - cdef int i, j, zz, ll, kk, row, col, temp, eptr, temp2 - - next_num = 1 - for i in range(n): - zz = order[i] # cols with largest abs values first - if (match[zz] == -1 and (ptrs[zz] != ptrs[zz + 1])): - queue[0] = zz - queue_ptr = 0 - queue_size = 1 - - while (queue_size > queue_ptr): - queue_col = queue[queue_ptr] - queue_ptr += 1 - eptr = ptrs[queue_col + 1] - - # get row inds in current column - temp = ptrs[queue_col] - for kk in range(eptr - ptrs[queue_col]): - row_order[kk] = inds[temp] - temp_weights[kk] = data[temp] - temp += 1 - - # linear sort by row weight - for kk in range(1, (eptr - ptrs[queue_col])): - val = temp_weights[kk] - row_val = row_order[kk] - ll = kk - 1 - while (ll >= 0) and (temp_weights[ll] > val): - temp_weights[ll + 1] = temp_weights[ll] - row_order[ll + 1] = row_order[ll] - ll -= 1 - - temp_weights[ll + 1] = val - row_order[ll + 1] = row_val - - # go through rows by decending weight - temp2 = (eptr - ptrs[queue_col]) - 1 - for kk in range(eptr - ptrs[queue_col]): - row = row_order[temp2 - kk] - temp = visited[row] - if temp != next_num and temp != -1: - previous[row] = queue_col - visited[row] = next_num - col = row_match[row] - if col == -1: - while row != -1: - col = previous[row] - temp = match[col] - match[col] = row - row_match[row] = col - row = temp - - next_num += 1 - queue_size = 0 - break - else: - queue[queue_size] = col - queue_size += 1 - - if match[zz] == -1: - for j in range(1, queue_size): - visited[match[queue[j]]] = -1 - - return match diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx deleted file mode 100755 index 0fe48f14a2..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/ptrace.pyx +++ /dev/null @@ -1,262 +0,0 @@ -#!python -#cython: language_level=3 -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, The QuTiP Project. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### -import numpy as np -from qiskit.providers.aer.openpulse.qutip_lite.cy.spconvert import zcsr_reshape -from qiskit.providers.aer.openpulse.qutip_lite.cy.spmath import zcsr_mult -from qutip.fastsparse import fast_csr_matrix, csr2fast -cimport numpy as cnp -cimport cython -from libc.math cimport floor, trunc -import scipy.sparse as sp - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def _ptrace_legacy(object rho, _sel): - """ - Private function calculating the partial trace. - """ - if np.prod(rho.dims[1]) == 1: - rho = rho * rho.dag() - - cdef size_t mm, ii - cdef int _tmp - cdef cnp.ndarray[int, ndim=1, mode='c'] drho = np.asarray(rho.dims[0], dtype=np.int32).ravel() - - if isinstance(_sel, int): - _sel = np.array([_sel], dtype=np.int32) - else: - _sel = np.asarray(_sel, dtype = np.int32) - - cdef int[::1] sel = _sel - - for mm in range(sel.shape[0]): - if (sel[mm] < 0) or (sel[mm] >= drho.shape[0]): - raise TypeError("Invalid selection index in ptrace.") - - cdef int[::1] rest = np.delete(np.arange(drho.shape[0],dtype=np.int32),sel) - cdef int N = np.prod(drho) - cdef int M = np.prod(drho.take(sel)) - cdef int R = np.prod(drho.take(rest)) - - cdef int[:,::1] ilistsel = _select(sel, drho, M) - cdef int[::1] indsel = _list2ind(ilistsel, drho) - cdef int[:,::1] ilistrest = _select(rest, drho, R) - cdef int[::1] indrest = _list2ind(ilistrest, drho) - - for mm in range(indrest.shape[0]): - _tmp = indrest[mm] * N + indrest[mm]-1 - indrest[mm] = _tmp - - cdef cnp.ndarray[int, ndim=1, mode='c'] ind = np.zeros(M**2*indrest.shape[0],dtype=np.int32) - for mm in range(M**2): - for ii in range(indrest.shape[0]): - ind[mm*indrest.shape[0]+ii] = indrest[ii] + \ - N*indsel[floor(mm / M)] + \ - indsel[(mm % M)]+1 - - data = np.ones_like(ind,dtype=complex) - ptr = np.arange(0,(M**2+1)*indrest.shape[0],indrest.shape[0], dtype=np.int32) - perm = fast_csr_matrix((data,ind,ptr),shape=(M * M, N * N)) - # No need to sort here, will be sorted in reshape - rhdata = zcsr_mult(perm, zcsr_reshape(rho.data, np.prod(rho.shape), 1), sorted=0) - rho1_data = zcsr_reshape(rhdata, M, M) - dims_kept0 = np.asarray(rho.dims[0], dtype=np.int32).take(sel) - rho1_dims = [dims_kept0.tolist(), dims_kept0.tolist()] - rho1_shape = [np.prod(dims_kept0), np.prod(dims_kept0)] - return rho1_data, rho1_dims, rho1_shape - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cpdef cnp.ndarray[int, ndim=1, mode='c'] _list2ind(int[:,::1] ilist, int[::1] dims): - """! - Private function returning indicies - """ - cdef size_t kk, ll - cdef int[::1] fact = np.ones(dims.shape[0],dtype=np.int32) - for kk in range(dims.shape[0]): - for ll in range(kk+1,dims.shape[0]): - fact[kk] *= dims[ll] - # If we make ilist a csr_matrix, then this is just spmv then sort - return np.sort(np.dot(ilist, fact), 0) - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cpdef cnp.ndarray[int, ndim=2, mode='c'] _select(int[::1] sel, int[::1] dims, int M): - """ - Private function finding selected components - """ - cdef size_t ii, jj, kk - cdef int _sel, _prd - cdef cnp.ndarray[int, ndim=2, mode='c'] ilist = np.zeros((M, dims.shape[0]), dtype=np.int32) - for jj in range(sel.shape[0]): - _sel = sel[jj] - _prd = 1 - for kk in range(jj+1,sel.shape[0]): - _prd *= dims[sel[kk]] - for ii in range(M): - ilist[ii, _sel] = (trunc(ii / _prd) % dims[_sel]) - return ilist - - -@cython.boundscheck(False) -@cython.wraparound(False) -cdef int _in(int val, int[::1] vec): - # val in vec in pure cython - cdef int ii - for ii in range(vec.shape[0]): - if val == vec[ii]: - return 1 - return 0 - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cdef void _i2_k_t(int N, - int[:, ::1] tensor_table, - int[::1] out): - # indices determining function for ptrace - cdef int ii, t1, t2 - out[0] = 0 - out[1] = 0 - for ii in range(tensor_table.shape[1]): - t1 = tensor_table[0, ii] - t2 = N / t1 - N = N % t1 - out[0] += tensor_table[1, ii] * t2 - out[1] += tensor_table[2, ii] * t2 - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -def _ptrace(object rho, sel): # work for N<= 26 on 16G Ram - cdef int[::1] _sel - cdef object _oper - cdef size_t ii - cdef size_t factor_keep = 1, factor_trace = 1, factor_tensor = 1 - cdef cnp.ndarray[int, ndim=1, mode='c'] drho = np.asarray(rho.dims[0], dtype=np.int32).ravel() - cdef int num_dims = drho.shape[0] - cdef int[:, ::1] tensor_table = np.zeros((3, num_dims), dtype=np.int32) - - if isinstance(sel, int): - _sel = np.array([sel], dtype=np.int32) - else: - _sel = np.asarray(sel, dtype=np.int32) - - for ii in range(_sel.shape[0]): - if _sel[ii] < 0 or _sel[ii] >= num_dims: - raise TypeError("Invalid selection index in ptrace.") - - if np.prod(rho.shape[1]) == 1: - _oper = (rho * rho.dag()).data - else: - _oper = rho.data - - for ii in range(num_dims-1,-1,-1): - tensor_table[0, ii] = factor_tensor - factor_tensor *= drho[ii] - if _in(ii, _sel): - tensor_table[1, ii] = factor_keep - factor_keep *= drho[ii] - else: - tensor_table[2, ii] = factor_trace - factor_trace *= drho[ii] - - dims_kept0 = drho.take(_sel).tolist() - rho1_dims = [dims_kept0, dims_kept0] - rho1_shape = [np.prod(dims_kept0), np.prod(dims_kept0)] - - # Try to evaluate how sparse the result will be. - if factor_keep*factor_keep > _oper.nnz: - return csr2fast(_ptrace_core_sp(_oper, tensor_table, factor_keep)), rho1_dims, rho1_shape - else: - return csr2fast(_ptrace_core_dense(_oper, tensor_table, factor_keep)), rho1_dims, rho1_shape - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cdef object _ptrace_core_sp(rho, int[:, ::1] tensor_table, int num_sel_dims): - cdef int p = 0, nnz = rho.nnz, ii, jj, nrow = rho.shape[0] - cdef int[::1] pos_c = np.empty(2, dtype=np.int32) - cdef int[::1] pos_r = np.empty(2, dtype=np.int32) - cdef cnp.ndarray[complex, ndim=1, mode='c'] new_data = np.zeros(nnz, dtype=complex) - cdef cnp.ndarray[int, ndim=1, mode='c'] new_col = np.zeros(nnz, dtype=np.int32) - cdef cnp.ndarray[int, ndim=1, mode='c'] new_row = np.zeros(nnz, dtype=np.int32) - cdef cnp.ndarray[complex, ndim=1, mode='c'] data = rho.data - cdef cnp.ndarray[int, ndim=1, mode='c'] ptr = rho.indptr - cdef cnp.ndarray[int, ndim=1, mode='c'] ind = rho.indices - - for ii in range(nrow): - for jj in range(ptr[ii], ptr[ii+1]): - _i2_k_t(ind[jj], tensor_table, pos_c) - _i2_k_t(ii, tensor_table, pos_r) - if pos_c[1] == pos_r[1]: - new_data[p] = data[jj] - new_row[p] = (pos_r[0]) - new_col[p] = (pos_c[0]) - p += 1 - - return sp.coo_matrix((new_data, [new_row, new_col]), - shape=(num_sel_dims,num_sel_dims)).tocsr() - - -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) -cdef object _ptrace_core_dense(rho, int[:, ::1] tensor_table, int num_sel_dims): - cdef int nnz = rho.nnz, ii, jj, nrow = rho.shape[0] - cdef int[::1] pos_c = np.empty(2, dtype=np.int32) - cdef int[::1] pos_r = np.empty(2, dtype=np.int32) - cdef cnp.ndarray[complex, ndim=1, mode='c'] data = rho.data - cdef cnp.ndarray[int, ndim=1, mode='c'] ptr = rho.indptr - cdef cnp.ndarray[int, ndim=1, mode='c'] ind = rho.indices - cdef complex[:, ::1] data_mat = np.zeros((num_sel_dims, num_sel_dims), - dtype=complex) - - for ii in range(nrow): - for jj in range(ptr[ii], ptr[ii+1]): - _i2_k_t(ind[jj], tensor_table, pos_c) - _i2_k_t(ii, tensor_table, pos_r) - if pos_c[1] == pos_r[1]: - data_mat[pos_r[0], pos_c[0]] += data[jj] - - return sp.coo_matrix(data_mat).tocsr() diff --git a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py new file mode 100755 index 0000000000..5a7ff4c0ba --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py @@ -0,0 +1,382 @@ +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +""" +Internal use module for manipulating dims specifications. +""" + +__all__ = [] # Everything should be explicitly imported, not made available + # by default. + +import numpy as np +from operator import getitem +from functools import partial + +def is_scalar(dims): + """ + Returns True if a dims specification is effectively + a scalar (has dimension 1). + """ + return np.prod(flatten(dims)) == 1 + +def is_vector(dims): + return ( + isinstance(dims, list) and + isinstance(dims[0], (int, np.integer)) + ) + +def is_vectorized_oper(dims): + return ( + isinstance(dims, list) and + isinstance(dims[0], list) + ) + + +def type_from_dims(dims, enforce_square=True): + bra_like, ket_like = map(is_scalar, dims) + + if bra_like: + if is_vector(dims[1]): + return 'bra' + elif is_vectorized_oper(dims[1]): + return 'operator-bra' + + if ket_like: + if is_vector(dims[0]): + return 'ket' + elif is_vectorized_oper(dims[0]): + return 'operator-ket' + + elif is_vector(dims[0]) and (dims[0] == dims[1] or not enforce_square): + return 'oper' + + elif ( + is_vectorized_oper(dims[0]) and + ( + ( + dims[0] == dims[1] and + dims[0][0] == dims[1][0] + ) or not enforce_square + ) + ): + return 'super' + + return 'other' + + +def flatten(l): + """Flattens a list of lists to the first level. + + Given a list containing a mix of scalars and lists, + flattens down to a list of the scalars within the original + list. + + Examples + -------- + + >>> print(flatten([[[0], 1], 2])) + [0, 1, 2] + + """ + if not isinstance(l, list): + return [l] + else: + return sum(map(flatten, l), []) + + +def deep_remove(l, *what): + """Removes scalars from all levels of a nested list. + + Given a list containing a mix of scalars and lists, + returns a list of the same structure, but where one or + more scalars have been removed. + + Examples + -------- + + >>> print(deep_remove([[[[0, 1, 2]], [3, 4], [5], [6, 7]]], 0, 5)) + [[[[1, 2]], [3, 4], [], [6, 7]]] + + """ + if isinstance(l, list): + # Make a shallow copy at this level. + l = l[:] + for to_remove in what: + if to_remove in l: + l.remove(to_remove) + else: + l = list(map(lambda elem: deep_remove(elem, to_remove), l)) + return l + + +def unflatten(l, idxs): + """Unflattens a list by a given structure. + + Given a list of scalars and a deep list of indices + as produced by `flatten`, returns an "unflattened" + form of the list. This perfectly inverts `flatten`. + + Examples + -------- + + >>> l = [[[10, 20, 30], [40, 50, 60]], [[70, 80, 90], [100, 110, 120]]] + >>> idxs = enumerate_flat(l) + >>> print(unflatten(flatten(l)), idxs) == l + True + + """ + acc = [] + for idx in idxs: + if isinstance(idx, list): + acc.append(unflatten(l, idx)) + else: + acc.append(l[idx]) + return acc + + +def _enumerate_flat(l, idx=0): + if not isinstance(l, list): + # Found a scalar, so return and increment. + return idx, idx + 1 + else: + # Found a list, so append all the scalars + # from it and recurse to keep the increment + # correct. + acc = [] + for elem in l: + labels, idx = _enumerate_flat(elem, idx) + acc.append(labels) + return acc, idx + +def _collapse_composite_index(dims): + """ + Given the dimensions specification for a composite index + (e.g.: [2, 3] for the right index of a ket with dims [[1], [2, 3]]), + returns a dimensions specification for an index of the same shape, + but collapsed to a single "leg." In the previous example, [2, 3] + would collapse to [6]. + """ + return [np.prod(dims)] + +def _collapse_dims_to_level(dims, level=1): + """ + Recursively collapses all indices in a dimensions specification + appearing at a given level, such that the returned dimensions + specification does not represent any composite systems. + """ + if level == 0: + return _collapse_composite_index(dims) + else: + return [_collapse_dims_to_level(index, level=level - 1) for index in dims] + +def collapse_dims_oper(dims): + """ + Given the dimensions specifications for a ket-, bra- or oper-type + Qobj, returns a dimensions specification describing the same shape + by collapsing all composite systems. For instance, the bra-type + dimensions specification ``[[2, 3], [1]]`` collapses to + ``[[6], [1]]``. + + Parameters + ---------- + + dims : list of lists of ints + Dimensions specifications to be collapsed. + + Returns + ------- + + collapsed_dims : list of lists of ints + Collapsed dimensions specification describing the same shape + such that ``len(collapsed_dims[0]) == len(collapsed_dims[1]) == 1``. + """ + return _collapse_dims_to_level(dims, 1) + +def collapse_dims_super(dims): + """ + Given the dimensions specifications for an operator-ket-, operator-bra- or + super-type Qobj, returns a dimensions specification describing the same shape + by collapsing all composite systems. For instance, the super-type + dimensions specification ``[[[2, 3], [2, 3]], [[2, 3], [2, 3]]]`` collapses to + ``[[[6], [6]], [[6], [6]]]``. + + Parameters + ---------- + + dims : list of lists of ints + Dimensions specifications to be collapsed. + + Returns + ------- + + collapsed_dims : list of lists of ints + Collapsed dimensions specification describing the same shape + such that ``len(collapsed_dims[i][j]) == 1`` for ``i`` and ``j`` + in ``range(2)``. + """ + return _collapse_dims_to_level(dims, 2) + +def enumerate_flat(l): + """Labels the indices at which scalars occur in a flattened list. + + Given a list containing a mix of scalars and lists, + returns a list of the same structure, where each scalar + has been replaced by an index into the flattened list. + + Examples + -------- + + >>> print(enumerate_flat([[[10], [20, 30]], 40])) + [[[0], [1, 2]], 3] + + """ + return _enumerate_flat(l)[0] + + +def deep_map(fn, collection, over=(tuple, list)): + if isinstance(collection, over): + return type(collection)(deep_map(fn, el, over) for el in collection) + else: + return fn(collection) + + +def dims_to_tensor_perm(dims): + """ + Given the dims of a Qobj instance, returns a list representing + a permutation from the flattening of that dims specification to + the corresponding tensor indices. + + Parameters + ---------- + + dims : list + Dimensions specification for a Qobj. + + Returns + ------- + + perm : list + A list such that ``data[flatten(dims)[idx]]`` gives the + index of the tensor ``data`` corresponding to the ``idx``th + dimension of ``dims``. + """ + # We figure out the type of the dims specification, + # relaxing the requirement that operators be square. + # This means that dims_type need not coincide with + # Qobj.type, but that works fine for our purposes here. + dims_type = type_from_dims(dims, enforce_square=False) + perm = enumerate_flat(dims) + + # If type is oper, ket or bra, we don't need to do anything. + if dims_type in ('oper', 'ket', 'bra'): + return flatten(perm) + + # If the type is other, we need to figure out if the + # dims is superlike on its outputs and inputs + # This is the case if the dims type for left or right + # are, respectively, oper-like. + if dims_type == 'other': + raise NotImplementedError("Not yet implemented for type='other'.") + + # If we're still here, the story is more complicated. We'll + # follow the strategy of creating a permutation by using + # enumerate_flat then transforming the result to swap + # input and output indices of vectorized matrices, then flattening + # the result. We'll then rebuild indices using this permutation. + + + if dims_type in ('operator-ket', 'super'): + # Swap the input and output spaces of the right part of + # perm. + perm[1] = list(reversed(perm[1])) + + if dims_type in ('operator-bra', 'super'): + # Ditto, but for the left indices. + perm[0] = list(reversed(perm[0])) + + return flatten(perm) + +def dims_to_tensor_shape(dims): + """ + Given the dims of a Qobj instance, returns the shape of the + corresponding tensor. This helps, for instance, resolve the + column-stacking convention for superoperators. + + Parameters + ---------- + + dims : list + Dimensions specification for a Qobj. + + Returns + ------- + + tensor_shape : tuple + NumPy shape of the corresponding tensor. + """ + + perm = dims_to_tensor_perm(dims) + dims = flatten(dims) + + return tuple(map(partial(getitem, dims), perm)) + + +def dims_idxs_to_tensor_idxs(dims, indices): + """ + Given the dims of a Qobj instance, and some indices into + dims, returns the corresponding tensor indices. This helps + resolve, for instance, that column-stacking for superoperators, + oper-ket and oper-bra implies that the input and output tensor + indices are reversed from their order in dims. + + Parameters + ---------- + + dims : list + Dimensions specification for a Qobj. + + indices : int, list or tuple + Indices to convert to tensor indices. Can be specified + as a single index, or as a collection of indices. + In the latter case, this can be nested arbitrarily + deep. For instance, [0, [0, (2, 3)]]. + + Returns + ------- + + tens_indices : int, list or tuple + Container of the same structure as indices containing + the tensor indices for each element of indices. + """ + + perm = dims_to_tensor_perm(dims) + return deep_map(partial(getitem, perm), indices) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/expect.py b/qiskit/providers/aer/openpulse/qutip_lite/expect.py index 35c1bb22ae..4fc74bddda 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/expect.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/expect.py @@ -44,52 +44,45 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name + +""" +Module for expectation values. +""" __all__ = ['expect', 'variance'] import numpy as np -import scipy.sparse as sp - -from qutip.qobj import Qobj, isoper -from qutip.eseries import eseries -from qutip.cy.spmatfuncs import (cy_expect_rho_vec, cy_expect_psi, cy_spmm_tr, - expect_csr_ket) - +from .qobj import Qobj, isoper +from .cy.spmatfuncs import (cy_expect_rho_vec, cy_expect_psi, cy_spmm_tr, + expect_csr_ket) expect_rho_vec = cy_expect_rho_vec expect_psi = cy_expect_psi def expect(oper, state): - '''Calculates the expectation value for operator(s) and state(s). + """Calculates the expectation value for operator(s) and state(s). - Parameters - ---------- - oper : qobj/array-like - A single or a `list` or operators for expectation value. + Args: + oper (qobj.Qobj or list): A single or a `list` or operators + for expectation value. - state : qobj/array-like - A single or a `list` of quantum states or density matrices. + state (qobj.Qobj or list): A single or a `list` of quantum states + or density matrices. - Returns - ------- - expt : float/complex/array-like - Expectation value. ``real`` if `oper` is Hermitian, ``complex`` - otherwise. A (nested) array of expectaction values of state or operator - are arrays. + Returns: + real or complex or ndarray: Expectation value. ``real`` if `oper` is + Hermitian, ``complex`` otherwise. A (nested) + array of expectaction values of state or + operator are arrays. - Examples - -------- - >>> expect(num(4), basis(4, 3)) - 3 - - ''' + Raises: + TypeError: Inputs are not quantum objects. + """ if isinstance(state, Qobj) and isinstance(oper, Qobj): return _single_qobj_expect(oper, state) - elif isinstance(oper, Qobj) and isinstance(state, eseries): - return _single_eseries_expect(oper, state) - elif isinstance(oper, (list, np.ndarray)): if isinstance(state, Qobj): if (all([op.isherm for op in oper]) and @@ -109,9 +102,9 @@ def expect(oper, state): return np.array([_single_qobj_expect(oper, x) for x in state], dtype=complex) else: - raise TypeError('Arguments must be quantum objects or eseries') - + raise TypeError('Arguments must be quantum objects') +# pylint: disable=inconsistent-return-statements def _single_qobj_expect(oper, state): """ Private function used by expect to calculate expectation values of Qobjs. @@ -130,59 +123,20 @@ def _single_qobj_expect(oper, state): elif state.type == 'ket': # calculates expectation value via return expect_csr_ket(oper.data, state.data, - oper.isherm) + oper.isherm) else: raise TypeError('Invalid operand types') -def _single_eseries_expect(oper, state): - """ - Private function used by expect to calculate expectation values for - eseries. - """ - - out = eseries() - - if isoper(state.ampl[0]): - out.rates = state.rates - out.ampl = np.array([expect(oper, a) for a in state.ampl]) - - else: - out.rates = np.array([]) - out.ampl = np.array([]) - - for m in range(len(state.rates)): - op_m = state.ampl[m].data.conj().T * oper.data - - for n in range(len(state.rates)): - a = op_m * state.ampl[n].data - - if isinstance(a, sp.spmatrix): - a = a.todense() - - out.rates = np.append(out.rates, state.rates[n] - - state.rates[m]) - out.ampl = np.append(out.ampl, a) - - return out - - def variance(oper, state): """ Variance of an operator for the given state vector or density matrix. - Parameters - ---------- - oper : qobj - Operator for expectation value. - - state : qobj/list - A single or `list` of quantum states or density matrices.. - - Returns - ------- - var : float - Variance of operator 'oper' for given state. + Args: + oper (qobj.Qobj): Operator for expectation value. + state (qobj.Qobj or list): A single or `list` of quantum states or density matrices.. + Returns: + float: Variance of operator 'oper' for given state. """ return expect(oper ** 2, state) - expect(oper, state) ** 2 diff --git a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py index 3ced24f3b9..110602827d 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py @@ -44,20 +44,25 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -import numpy as np +# pylint: disable=invalid-name + +""" +Module for fast versions of SciPy sparse CSR matrices +""" + import operator -from scipy.sparse import (_sparsetools, isspmatrix, isspmatrix_csr, - csr_matrix, coo_matrix, csc_matrix, dia_matrix) -from scipy.sparse.sputils import (upcast, upcast_char, to_native, isdense, isshape, - getdtype, isscalarlike, get_index_dtype) -from scipy.sparse.base import spmatrix, isspmatrix, SparseEfficiencyWarning from warnings import warn +import numpy as np +from scipy.sparse import (_sparsetools, isspmatrix, csr_matrix, dia_matrix) +from scipy.sparse.sputils import (upcast, isdense, isscalarlike, get_index_dtype) +from scipy.sparse.base import SparseEfficiencyWarning class fast_csr_matrix(csr_matrix): """ A subclass of scipy.sparse.csr_matrix that skips the data format checks that are run everytime a new csr_matrix is created. """ + # pylint: disable=super-init-not-called def __init__(self, args=None, shape=None, dtype=None, copy=False): if args is None: #Build zero matrix if shape is None: @@ -145,9 +150,9 @@ def multiply(self, other): other = csr_matrix(other) return self._binopt(other, '_elmul_') # Single element. - elif other.shape == (1,1): + elif other.shape == (1, 1): return self._mul_scalar(other.toarray()[0, 0]) - elif self.shape == (1,1): + elif self.shape == (1, 1): return other._mul_scalar(self.toarray()[0, 0]) # A row times a column. elif self.shape[1] == other.shape[0] and self.shape[1] == 1: @@ -157,22 +162,22 @@ def multiply(self, other): # Row vector times matrix. other is a row. elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: other = dia_matrix((other.toarray().ravel(), [0]), - shape=(other.shape[1], other.shape[1])) + shape=(other.shape[1], other.shape[1])) return self._mul_sparse_matrix(other) # self is a row. elif self.shape[0] == 1 and self.shape[1] == other.shape[1]: copy = dia_matrix((self.toarray().ravel(), [0]), - shape=(self.shape[1], self.shape[1])) + shape=(self.shape[1], self.shape[1])) return other._mul_sparse_matrix(copy) # Column vector times matrix. other is a column. elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: other = dia_matrix((other.toarray().ravel(), [0]), - shape=(other.shape[0], other.shape[0])) + shape=(other.shape[0], other.shape[0])) return other._mul_sparse_matrix(self) # self is a column. elif self.shape[1] == 1 and self.shape[0] == other.shape[0]: copy = dia_matrix((self.toarray().ravel(), [0]), - shape=(self.shape[0], self.shape[0])) + shape=(self.shape[0], self.shape[0])) return copy._mul_sparse_matrix(other) else: raise ValueError("inconsistent shapes") @@ -194,10 +199,10 @@ def _mul_sparse_matrix(self, other): Do the sparse matrix mult returning fast_csr_matrix only when other is also fast_csr_matrix. """ - M, K1 = self.shape - K2, N = other.shape + M, _ = self.shape + _, N = other.shape - major_axis = self._swap((M,N))[0] + major_axis = self._swap((M, N))[0] if isinstance(other, fast_csr_matrix): A = zcsr_mult(self, other, sorted=1) return A @@ -232,7 +237,7 @@ def _mul_sparse_matrix(self, other): np.asarray(other.indices, dtype=idx_dtype), other.data, indptr, indices, data) - A = csr_matrix((data,indices,indptr),shape=(M,N)) + A = csr_matrix((data, indices, indptr), shape=(M, N)) return A def _scalar_binopt(self, other, op): @@ -252,7 +257,7 @@ def __eq__(self, other): if other == 0: warn("Comparing a sparse matrix with 0 using == is inefficient" - ", try using != instead.", SparseEfficiencyWarning) + ", try using != instead.", SparseEfficiencyWarning) all_true = _all_true(self.shape) inv = self._scalar_binopt(other, operator.ne) return all_true - inv @@ -264,13 +269,13 @@ def __eq__(self, other): # Sparse other. elif isspmatrix(other): warn("Comparing sparse matrices using == is inefficient, try using" - " != instead.", SparseEfficiencyWarning) + " != instead.", SparseEfficiencyWarning) #TODO sparse broadcasting if self.shape != other.shape: return False elif self.format != other.format: other = other.asformat(self.format) - res = self._binopt(other,'_ne_') + res = self._binopt(other, '_ne_') all_true = _all_true(self.shape) return all_true - res else: @@ -302,14 +307,14 @@ def __ne__(self, other): return True elif self.format != other.format: other = other.asformat(self.format) - return self._binopt(other,'_ne_') + return self._binopt(other, '_ne_') else: return True def _inequality(self, other, op, op_name, bad_scalar_msg): # Scalar other. if isscalarlike(other): - if 0 == other and op_name in ('_le_', '_ge_'): + if other == 0 and op_name in ('_le_', '_ge_'): raise NotImplementedError(" >= and <= don't work with 0.") elif op(0, other): warn(bad_scalar_msg, SparseEfficiencyWarning) @@ -340,7 +345,7 @@ def _inequality(self, other, op, op_name, bad_scalar_msg): else: raise ValueError("Operands could not be compared.") - def _with_data(self,data,copy=True): + def _with_data(self, data, copy=True): """Returns a matrix with the same sparsity structure as self, but with different data. By default the structure arrays (i.e. .indptr and .indices) are copied. @@ -349,12 +354,12 @@ def _with_data(self,data,copy=True): # does nothing if data.dtype is complex. data = np.asarray(data, dtype=complex) if copy: - return fast_csr_matrix((data,self.indices.copy(),self.indptr.copy()), - shape=self.shape,dtype=data.dtype) + return fast_csr_matrix((data, self.indices.copy(), self.indptr.copy()), + shape=self.shape, dtype=data.dtype) else: - return fast_csr_matrix((data,self.indices,self.indptr), - shape=self.shape,dtype=data.dtype) - + return fast_csr_matrix((data, self.indices, self.indptr), + shape=self.shape, dtype=data.dtype) + # pylint: disable=arguments-differ def transpose(self): """ Returns the transpose of the matrix, keeping @@ -383,11 +388,21 @@ def adjoint(self): def csr2fast(A, copy=False): + """Converts a SciPy CSR matrix + to the internal fast version. + + Args: + A (csr_matrx): Input csr_matrix. + copy (bool): Make a copy of the data arrays. + + Returns: + fast_csr: The equivilent fast CSR matrix. + """ if (not isinstance(A, fast_csr_matrix)) or copy: # Do not need to do any type checking here # since fast_csr_matrix does that. - return fast_csr_matrix((A.data,A.indices,A.indptr), - shape=A.shape,copy=copy) + return fast_csr_matrix((A.data, A.indices, A.indptr), + shape=A.shape, copy=copy) else: return A @@ -400,21 +415,20 @@ def fast_identity(N): ind = np.arange(N, dtype=np.int32) ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N - return fast_csr_matrix((data,ind,ptr),shape=(N,N)) - + return fast_csr_matrix((data, ind, ptr), shape=(N, N)) #Convenience functions #-------------------- def _all_true(shape): A = csr_matrix((np.ones(np.prod(shape), dtype=np.bool_), - np.tile(np.arange(shape[1],dtype=np.int32),shape[0]), - np.arange(0,np.prod(shape)+1,shape[1],dtype=np.int32)), - shape=shape) + np.tile(np.arange(shape[1], dtype=np.int32), shape[0]), + np.arange(0, np.prod(shape)+1, shape[1], dtype=np.int32)), + shape=shape) return A #Need to do some trailing imports here #------------------------------------- -from qutip.cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_mult) +from .cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_mult) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/graph.py b/qiskit/providers/aer/openpulse/qutip_lite/graph.py deleted file mode 100755 index f0aea3e0ce..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/graph.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### -""" -This module contains a collection of graph theory routines used mainly -to reorder matrices for iterative steady state solvers. -""" - -__all__ = ['graph_degree', 'column_permutation', 'breadth_first_search', - 'reverse_cuthill_mckee', 'maximum_bipartite_matching', - 'weighted_bipartite_matching'] - -import numpy as np -import scipy.sparse as sp -from qutip.cy.graph_utils import ( - _breadth_first_search, _node_degrees, - _reverse_cuthill_mckee, _maximum_bipartite_matching, - _weighted_bipartite_matching) - - -def graph_degree(A): - """ - Returns the degree for the nodes (rows) of a symmetric - graph in sparse CSR or CSC format, or a qobj. - - Parameters - ---------- - A : qobj, csr_matrix, csc_matrix - Input quantum object or csr_matrix. - - Returns - ------- - degree : array - Array of integers giving the degree for each node (row). - - """ - if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): - raise TypeError('Input must be CSC or CSR sparse matrix.') - return np.asarray(_node_degrees(A.indices, A.indptr, A.shape[0])) - - -def breadth_first_search(A, start): - """ - Breadth-First-Search (BFS) of a graph in CSR or CSC matrix format starting - from a given node (row). Takes Qobjs and CSR or CSC matrices as inputs. - - This function requires a matrix with symmetric structure. - Use A+trans(A) if original matrix is not symmetric or not sure. - - Parameters - ---------- - A : csc_matrix, csr_matrix - Input graph in CSC or CSR matrix format - start : int - Staring node for BFS traversal. - - Returns - ------- - order : array - Order in which nodes are traversed from starting node. - levels : array - Level of the nodes in the order that they are traversed. - - """ - if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): - raise TypeError('Input must be CSC or CSR sparse matrix.') - - num_rows = A.shape[0] - start = int(start) - order, levels = _breadth_first_search(A.indices, A.indptr, num_rows, start) - # since maybe not all nodes are in search, check for unused entires in - # arrays - return order[order != -1], levels[levels != -1] - - -def column_permutation(A): - """ - Finds the non-symmetric column permutation of A such that the columns - are given in ascending order according to the number of nonzero entries. - This is sometimes useful for decreasing the fill-in of sparse LU - factorization. - - Parameters - ---------- - A : csc_matrix - Input sparse CSC sparse matrix. - - Returns - ------- - perm : array - Array of permuted row and column indices. - - """ - if not sp.isspmatrix_csc(A): - A = sp.csc_matrix(A) - count = np.diff(A.indptr) - perm = np.argsort(count) - return perm - - -def reverse_cuthill_mckee(A, sym=False): - """ - Returns the permutation array that orders a sparse CSR or CSC matrix - in Reverse-Cuthill McKee ordering. Since the input matrix must be - symmetric, this routine works on the matrix A+Trans(A) if the sym flag is - set to False (Default). - - It is assumed by default (*sym=False*) that the input matrix is not - symmetric. This is because it is faster to do A+Trans(A) than it is to - check for symmetry for a generic matrix. If you are guaranteed that the - matrix is symmetric in structure (values of matrix element do not matter) - then set *sym=True* - - Parameters - ---------- - A : csc_matrix, csr_matrix - Input sparse CSC or CSR sparse matrix format. - sym : bool {False, True} - Flag to set whether input matrix is symmetric. - - Returns - ------- - perm : array - Array of permuted row and column indices. - - Notes - ----- - This routine is used primarily for internal reordering of Lindblad - superoperators for use in iterative solver routines. - - References - ---------- - E. Cuthill and J. McKee, "Reducing the Bandwidth of Sparse Symmetric - Matrices", ACM '69 Proceedings of the 1969 24th national conference, - (1969). - - """ - if not (sp.isspmatrix_csc(A) or sp.isspmatrix_csr(A)): - raise TypeError('Input must be CSC or CSR sparse matrix.') - - nrows = A.shape[0] - - if not sym: - A = A + A.transpose() - - return _reverse_cuthill_mckee(A.indices, A.indptr, nrows) - - -def maximum_bipartite_matching(A, perm_type='row'): - """ - Returns an array of row or column permutations that removes nonzero - elements from the diagonal of a nonsingular square CSC sparse matrix. Such - a permutation is always possible provided that the matrix is nonsingular. - This function looks at the structure of the matrix only. - - The input matrix will be converted to CSC matrix format if - necessary. - - Parameters - ---------- - A : sparse matrix - Input matrix - - perm_type : str {'row', 'column'} - Type of permutation to generate. - - Returns - ------- - perm : array - Array of row or column permutations. - - Notes - ----- - This function relies on a maximum cardinality bipartite matching algorithm - based on a breadth-first search (BFS) of the underlying graph[1]_. - - References - ---------- - I. S. Duff, K. Kaya, and B. Ucar, "Design, Implementation, and - Analysis of Maximum Transversal Algorithms", ACM Trans. Math. Softw. - 38, no. 2, (2011). - - """ - nrows = A.shape[0] - if A.shape[0] != A.shape[1]: - raise ValueError( - 'Maximum bipartite matching requires a square matrix.') - - if sp.isspmatrix_csr(A) or sp.isspmatrix_coo(A): - A = A.tocsc() - elif not sp.isspmatrix_csc(A): - raise TypeError("matrix must be in CSC, CSR, or COO format.") - - if perm_type == 'column': - A = A.transpose().tocsc() - - perm = _maximum_bipartite_matching(A.indices, A.indptr, nrows) - - if np.any(perm == -1): - raise Exception('Possibly singular input matrix.') - - return perm - - -def weighted_bipartite_matching(A, perm_type='row'): - """ - Returns an array of row permutations that attempts to maximize - the product of the ABS values of the diagonal elements in - a nonsingular square CSC sparse matrix. Such a permutation is - always possible provided that the matrix is nonsingular. - - This function looks at both the structure and ABS values of the - underlying matrix. - - Parameters - ---------- - A : csc_matrix - Input matrix - - perm_type : str {'row', 'column'} - Type of permutation to generate. - - Returns - ------- - perm : array - Array of row or column permutations. - - Notes - ----- - This function uses a weighted maximum cardinality bipartite matching - algorithm based on breadth-first search (BFS). The columns are weighted - according to the element of max ABS value in the associated rows and - are traversed in descending order by weight. When performing the BFS - traversal, the row associated to a given column is the one with maximum - weight. Unlike other techniques[1]_, this algorithm does not guarantee the - product of the diagonal is maximized. However, this limitation is offset - by the substantially faster runtime of this method. - - References - ---------- - I. S. Duff and J. Koster, "The design and use of algorithms for - permuting large entries to the diagonal of sparse matrices", SIAM J. - Matrix Anal. and Applics. 20, no. 4, 889 (1997). - - """ - - nrows = A.shape[0] - if A.shape[0] != A.shape[1]: - raise ValueError('weighted_bfs_matching requires a square matrix.') - - if sp.isspmatrix_csr(A) or sp.isspmatrix_coo(A): - A = A.tocsc() - elif not sp.isspmatrix_csc(A): - raise TypeError("matrix must be in CSC, CSR, or COO format.") - - if perm_type == 'column': - A = A.transpose().tocsc() - - perm = _weighted_bipartite_matching( - np.asarray(np.abs(A.data), dtype=float), - A.indices, A.indptr, nrows) - - if np.any(perm == -1): - raise Exception('Possibly singular input matrix.') - - return perm diff --git a/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py b/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py deleted file mode 100755 index aef2c269d4..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/hardware_info.py +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### - -__all__ = ['hardware_info'] - -import os -import sys -import multiprocessing -import numpy as np - -def _mac_hardware_info(): - info = dict() - results = dict() - for l in [l.split(':') for l in os.popen('sysctl hw').readlines()[1:]]: - info[l[0].strip(' "').replace(' ', '_').lower().strip('hw.')] = \ - l[1].strip('.\n ') - results.update({'cpus': int(info['physicalcpu'])}) - results.update({'cpu_freq': int(float(os.popen('sysctl -n machdep.cpu.brand_string') - .readlines()[0].split('@')[1][:-4])*1000)}) - results.update({'memsize': int(int(info['memsize']) / (1024 ** 2))}) - # add OS information - results.update({'os': 'Mac OSX'}) - return results - - -def _linux_hardware_info(): - results = {} - # get cpu number - sockets = 0 - cores_per_socket = 0 - frequency = 0.0 - for l in [l.split(':') for l in open("/proc/cpuinfo").readlines()]: - if (l[0].strip() == "physical id"): - sockets = np.maximum(sockets,int(l[1].strip())+1) - if (l[0].strip() == "cpu cores"): - cores_per_socket = int(l[1].strip()) - if (l[0].strip() == "cpu MHz"): - frequency = float(l[1].strip()) / 1000. - results.update({'cpus': sockets * cores_per_socket}) - # get cpu frequency directly (bypasses freq scaling) - try: - file = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" - line = open(file).readlines()[0] - frequency = float(line.strip('\n')) / 1000000. - except: - pass - results.update({'cpu_freq': frequency}) - - # get total amount of memory - mem_info = dict() - for l in [l.split(':') for l in open("/proc/meminfo").readlines()]: - mem_info[l[0]] = l[1].strip('.\n ').strip('kB') - results.update({'memsize': int(mem_info['MemTotal']) / 1024}) - # add OS information - results.update({'os': 'Linux'}) - return results - -def _freebsd_hardware_info(): - results = {} - results.update({'cpus': int(os.popen('sysctl -n hw.ncpu').readlines()[0])}) - results.update({'cpu_freq': int(os.popen('sysctl -n dev.cpu.0.freq').readlines()[0])}) - results.update({'memsize': int(os.popen('sysctl -n hw.realmem').readlines()[0]) / 1024}) - results.update({'os': 'FreeBSD'}) - return results - -def _win_hardware_info(): - try: - from comtypes.client import CoGetObject - winmgmts_root = CoGetObject("winmgmts:root\cimv2") - cpus = winmgmts_root.ExecQuery("Select * from Win32_Processor") - ncpus = 0 - for cpu in cpus: - ncpus += int(cpu.Properties_['NumberOfCores'].Value) - except: - ncpus = int(multiprocessing.cpu_count()) - return {'os': 'Windows', 'cpus': ncpus} - - -def hardware_info(): - """ - Returns basic hardware information about the computer. - - Gives actual number of CPU's in the machine, even when hyperthreading is - turned on. - - Returns - ------- - info : dict - Dictionary containing cpu and memory information. - - """ - if sys.platform == 'darwin': - out = _mac_hardware_info() - elif sys.platform == 'win32': - out = _win_hardware_info() - elif sys.platform in ['linux', 'linux2']: - out = _linux_hardware_info() - elif sys.platform.startswith('freebsd'): - out = _freebsd_hardware_info() - else: - out = {} - return out - -if __name__ == '__main__': - print(hardware_info()) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/operators.py b/qiskit/providers/aer/openpulse/qutip_lite/operators.py index c4589027a9..6dac4db91e 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/operators.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/operators.py @@ -50,18 +50,10 @@ of commonly occuring quantum operators. """ -__all__ = ['jmat', 'spin_Jx', 'spin_Jy', 'spin_Jz', 'spin_Jm', 'spin_Jp', - 'spin_J_set', 'sigmap', 'sigmam', 'sigmax', 'sigmay', 'sigmaz', - 'destroy', 'create', 'qeye', 'identity', 'position', 'momentum', - 'num', 'squeeze', 'squeezing', 'displace', 'commutator', - 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'enr_destroy', - 'enr_identity', 'charge', 'tunneling'] - import numpy as np import scipy import scipy.sparse as sp -from qutip.qobj import Qobj -from qutip.fastsparse import fast_csr_matrix, fast_identity +from .fastsparse import fast_csr_matrix, fast_identity # # Spin operators @@ -395,7 +387,7 @@ def destroy(N, offset=0): ind = np.arange(1,N, dtype=np.int32) ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N-1 - return Qobj(fast_csr_matrix((data,ind,ptr),shape=(N,N)), isherm=False) + return Qobj(fast_csr_matrix((data, ind, ptr),shape=(N,N)), isherm=False) # @@ -468,8 +460,6 @@ def qeye(N): [ 0. 0. 1.]] """ - if isinstance(N, list): - return tensor(*[identity(n) for n in N]) N = int(N) if N < 0: raise ValueError("N must be integer N>=0") @@ -772,36 +762,6 @@ def qdiags(diagonals, offsets, dims=None, shape=None): shape = [] return Qobj(data, dims, list(shape)) - -def phase(N, phi0=0): - """ - Single-mode Pegg-Barnett phase operator. - - Parameters - ---------- - N : int - Number of basis states in Hilbert space. - phi0 : float - Reference phase. - - Returns - ------- - oper : qobj - Phase operator with respect to reference phase. - - Notes - ----- - The Pegg-Barnett phase operator is Hermitian on a truncated Hilbert space. - - """ - phim = phi0 + (2.0 * np.pi * np.arange(N)) / N # discrete phase angles - n = np.arange(N).reshape((N, 1)) - states = np.array([np.sqrt(kk) / np.sqrt(N) * np.exp(1.0j * n * kk) - for kk in phim]) - ops = np.array([np.outer(st, st.conj()) for st in states]) - return Qobj(np.sum(ops, axis=0)) - - def qzero(N): """ Zero operator @@ -819,108 +779,12 @@ def qzero(N): Zero operator Qobj. """ - - if isinstance(N, list): - return tensor(*[qzero(n) for n in N]) N = int(N) if (not isinstance(N, (int, np.integer))) or N < 0: raise ValueError("N must be integer N>=0") return Qobj(sp.csr_matrix((N, N), dtype=complex), isherm=True) -def enr_destroy(dims, excitations): - """ - Generate annilation operators for modes in a excitation-number-restricted - state space. For example, consider a system consisting of 4 modes, each - with 5 states. The total hilbert space size is 5**4 = 625. If we are - only interested in states that contain up to 2 excitations, we only need - to include states such as - - (0, 0, 0, 0) - (0, 0, 0, 1) - (0, 0, 0, 2) - (0, 0, 1, 0) - (0, 0, 1, 1) - (0, 0, 2, 0) - ... - - This function creates annihilation operators for the 4 modes that act - within this state space: - - a1, a2, a3, a4 = enr_destroy([5, 5, 5, 5], excitations=2) - - From this point onwards, the annihiltion operators a1, ..., a4 can be - used to setup a Hamiltonian, collapse operators and expectation-value - operators, etc., following the usual pattern. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - Returns - ------- - a_ops : list of qobj - A list of annihilation operators for each mode in the composite - quantum system described by dims. - """ - from qutip.states import enr_state_dictionaries - - nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) - - a_ops = [sp.lil_matrix((nstates, nstates), dtype=np.complex) - for _ in range(len(dims))] - - for n1, state1 in idx2state.items(): - for n2, state2 in idx2state.items(): - for idx, a in enumerate(a_ops): - s1 = [s for idx2, s in enumerate(state1) if idx != idx2] - s2 = [s for idx2, s in enumerate(state2) if idx != idx2] - if (state1[idx] == state2[idx] - 1) and (s1 == s2): - a_ops[idx][n1, n2] = np.sqrt(state2[idx]) - - return [Qobj(a, dims=[dims, dims]) for a in a_ops] - - -def enr_identity(dims, excitations): - """ - Generate the identity operator for the excitation-number restricted - state space defined by the `dims` and `exciations` arguments. See the - docstring for enr_fock for a more detailed description of these arguments. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - state : list of integers - The state in the number basis representation. - - Returns - ------- - op : Qobj - A Qobj instance that represent the identity operator in the - exication-number-restricted state space defined by `dims` and - `exciations`. - """ - from qutip.states import enr_state_dictionaries - - nstates, _, _ = enr_state_dictionaries(dims, excitations) - data = sp.eye(nstates, nstates, dtype=np.complex) - return Qobj(data, dims=[dims, dims]) - - - def charge(Nmax, Nmin=None, frac = 1): """ Generate the diagonal charge operator over charge states @@ -983,9 +847,4 @@ def tunneling(N, m=1): T = sp.diags(diags,[m,-m],format='csr', dtype=complex) return Qobj(T, isherm=True) - - -# Break circular dependencies by a trailing import. -# Note that we use a relative import here to deal with that -# qutip.tensor is the *function* tensor, not the module. -from qutip.tensor import tensor +from .qobj import Qobj \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qutip_lite/permute.py b/qiskit/providers/aer/openpulse/qutip_lite/permute.py deleted file mode 100755 index de41f3a3b7..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/permute.py +++ /dev/null @@ -1,188 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### - -__all__ = ['reshuffle'] - -import numpy as np -import scipy.sparse as sp -from qutip.cy.ptrace import _select -from qutip.cy.spconvert import arr_coo2fast, cy_index_permute - - -def _chunk_dims(dims, order): - lens_order = map(len, order) - - for chunk_len in lens_order: - yield list(dims[:chunk_len]) - dims = dims[chunk_len:] - - -def _permute(Q, order): - Qcoo = Q.data.tocoo() - - if Q.isket: - cy_index_permute(Qcoo.row, - np.array(Q.dims[0], dtype=np.int32), - np.array(order, dtype=np.int32)) - - new_dims = [[Q.dims[0][i] for i in order], Q.dims[1]] - - elif Q.isbra: - cy_index_permute(Qcoo.col, - np.array(Q.dims[1], dtype=np.int32), - np.array(order, dtype=np.int32)) - - new_dims = [Q.dims[0], [Q.dims[1][i] for i in order]] - - elif Q.isoper: - cy_index_permute(Qcoo.row, - np.array(Q.dims[0], dtype=np.int32), - np.array(order, dtype=np.int32)) - cy_index_permute(Qcoo.col, - np.array(Q.dims[1], dtype=np.int32), - np.array(order, dtype=np.int32)) - - new_dims = [[Q.dims[0][i] for i in order], [Q.dims[1][i] for i in order]] - - elif Q.isoperket: - # For superoperators, we expect order to be something like - # [[0, 2], [1, 3]], which tells us to permute according to - # [0, 2, 1 ,3], and then group indices according to the length - # of each sublist. - # As another example, - # permuting [[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]] by - # [[0, 3], [1, 4], [2, 5]] should give - # [[[1, 1], [2, 2], [3, 3]], [[1, 1], [2, 2], [3, 3]]]. - # - # Get the breakout of the left index into dims. - # Since this is a super, the left index itself breaks into left - # and right indices, each of which breaks down further. - # The best way to deal with that here is to flatten dims. - - flat_order = np.array(sum(order, []), dtype=np.int32) - q_dims = np.array(sum(Q.dims[0], []), dtype=np.int32) - - cy_index_permute(Qcoo.row, q_dims, flat_order) - - # Finally, we need to restructure the now-decomposed left index - # into left and right subindices, so that the overall dims we return - # are of the form specified by order. - - new_dims = [q_dims[i] for i in flat_order] - new_dims = list(_chunk_dims(new_dims, order)) - new_dims = [new_dims, [1]] - - elif Q.isoperbra: - flat_order = np.array(sum(order, []), dtype=np.int32) - q_dims = np.array(sum(Q.dims[1], []), dtype=np.int32) - - cy_index_permute(Qcoo.col, q_dims, flat_order) - - new_dims = [q_dims[i] for i in flat_order] - new_dims = list(_chunk_dims(new_dims, order)) - new_dims = [[1], new_dims] - - elif Q.issuper: - flat_order = np.array(sum(order, []), dtype=np.int32) - q_dims = np.array(sum(Q.dims[0], []), dtype=np.int32) - - cy_index_permute(Qcoo.row, q_dims, flat_order) - cy_index_permute(Qcoo.col, q_dims, flat_order) - - new_dims = [q_dims[i] for i in flat_order] - new_dims = list(_chunk_dims(new_dims, order)) - new_dims = [new_dims, new_dims] - - else: - raise TypeError('Invalid quantum object for permutation.') - - return arr_coo2fast(Qcoo.data, Qcoo.row, Qcoo.col, Qcoo.shape[0], Qcoo.shape[1]), new_dims - - -def _perm_inds(dims, order): - """ - Private function giving permuted indices for permute function. - """ - dims = np.asarray(dims,dtype=np.int32) - order = np.asarray(order,dtype=np.int32) - if not np.all(np.sort(order) == np.arange(len(dims))): - raise ValueError( - 'Requested permutation does not match tensor structure.') - sel = _select(order, dims,np.prod(dims)) - irev = np.fliplr(sel) - fact = np.append(np.array([1]), np.cumprod(np.flipud(dims)[:-1])) - fact = fact.reshape(len(fact), 1) - perm_inds = np.dot(irev, fact) - return dims, perm_inds - - -def reshuffle(q_oper): - """ - Column-reshuffles a ``type="super"`` Qobj. - """ - if q_oper.type not in ('super', 'operator-ket'): - raise TypeError("Reshuffling is only supported on type='super' " - "or type='operator-ket'.") - - # How many indices are there, and how many subsystems can we decompose - # each index into? - n_indices = len(q_oper.dims[0]) - n_subsystems = len(q_oper.dims[0][0]) - - # Generate a list of lists (lol) that represents the permutation order we - # need. It's easiest to do so if we make an array, then turn it into a lol - # by using map(list, ...). That array is generated by using reshape and - # transpose to turn an array like [a, b, a, b, ..., a, b] into one like - # [a, a, ..., a, b, b, ..., b]. - perm_idxs = map(list, - np.arange(n_subsystems * n_indices)[ - np.arange(n_subsystems * n_indices).reshape( - (n_indices, n_subsystems)).T.flatten() - ].reshape((n_subsystems, n_indices)) - ) - - return q_oper.permute(list(perm_idxs)) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/propagator.py b/qiskit/providers/aer/openpulse/qutip_lite/propagator.py deleted file mode 100755 index db257bf15b..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/propagator.py +++ /dev/null @@ -1,335 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### - -__all__ = ['propagator', 'propagator_steadystate'] - -import types -import numpy as np -import scipy.linalg as la -import functools -import scipy.sparse as sp -from qutip.qobj import Qobj -from qutip.tensor import tensor -from qutip.operators import qeye -from qutip.rhs_generate import (rhs_generate, rhs_clear, _td_format_check) -from qutip.superoperator import (vec2mat, mat2vec, - vector_to_operator, operator_to_vector) -from qutip.sparse import sp_reshape -from qutip.cy.sparse_utils import unit_row_norm -from qutip.mesolve import mesolve -from qutip.sesolve import sesolve -from qutip.states import basis -from qutip.solver import Options, _solver_safety_check, config -from qutip.parallel import parallel_map, _default_kwargs -from qutip.ui.progressbar import BaseProgressBar, TextProgressBar - - -def propagator(H, t, c_op_list=[], args={}, options=None, - unitary_mode='batch', parallel=False, - progress_bar=None, _safe_mode=True, - **kwargs): - """ - Calculate the propagator U(t) for the density matrix or wave function such - that :math:`\psi(t) = U(t)\psi(0)` or - :math:`\\rho_{\mathrm vec}(t) = U(t) \\rho_{\mathrm vec}(0)` - where :math:`\\rho_{\mathrm vec}` is the vector representation of the - density matrix. - - Parameters - ---------- - H : qobj or list - Hamiltonian as a Qobj instance of a nested list of Qobjs and - coefficients in the list-string or list-function format for - time-dependent Hamiltonians (see description in :func:`qutip.mesolve`). - - t : float or array-like - Time or list of times for which to evaluate the propagator. - - c_op_list : list - List of qobj collapse operators. - - args : list/array/dictionary - Parameters to callback functions for time-dependent Hamiltonians and - collapse operators. - - options : :class:`qutip.Options` - with options for the ODE solver. - - unitary_mode = str ('batch', 'single') - Solve all basis vectors simulaneously ('batch') or individually - ('single'). - - parallel : bool {False, True} - Run the propagator in parallel mode. This will override the - unitary_mode settings if set to True. - - progress_bar: BaseProgressBar - Optional instance of BaseProgressBar, or a subclass thereof, for - showing the progress of the simulation. By default no progress bar - is used, and if set to True a TextProgressBar will be used. - - Returns - ------- - a : qobj - Instance representing the propagator :math:`U(t)`. - - """ - kw = _default_kwargs() - if 'num_cpus' in kwargs: - num_cpus = kwargs['num_cpus'] - else: - num_cpus = kw['num_cpus'] - - if progress_bar is None: - progress_bar = BaseProgressBar() - elif progress_bar is True: - progress_bar = TextProgressBar() - - if options is None: - options = Options() - options.rhs_reuse = True - rhs_clear() - - if isinstance(t, (int, float, np.integer, np.floating)): - tlist = [0, t] - else: - tlist = t - - if _safe_mode: - _solver_safety_check(H, None, c_ops=c_op_list, e_ops=[], args=args) - - td_type = _td_format_check(H, c_op_list, solver='me') - - if isinstance(H, (types.FunctionType, types.BuiltinFunctionType, - functools.partial)): - H0 = H(0.0, args) - if unitary_mode =='batch': - # batch don't work with function Hamiltonian - unitary_mode = 'single' - elif isinstance(H, list): - H0 = H[0][0] if isinstance(H[0], list) else H[0] - else: - H0 = H - - if len(c_op_list) == 0 and H0.isoper: - # calculate propagator for the wave function - - N = H0.shape[0] - dims = H0.dims - - if parallel: - unitary_mode = 'single' - u = np.zeros([N, N, len(tlist)], dtype=complex) - output = parallel_map(_parallel_sesolve, range(N), - task_args=(N, H, tlist, args, options), - progress_bar=progress_bar, num_cpus=num_cpus) - for n in range(N): - for k, t in enumerate(tlist): - u[:, n, k] = output[n].states[k].full().T - else: - if unitary_mode == 'single': - output = sesolve(H, qeye(N), tlist, [], args, options, - _safe_mode=False) - if len(tlist) == 2: - return output.states[-1] - else: - return output.states - - elif unitary_mode =='batch': - u = np.zeros(len(tlist), dtype=object) - _rows = np.array([(N+1)*m for m in range(N)]) - _cols = np.zeros_like(_rows) - _data = np.ones_like(_rows, dtype=complex) - psi0 = Qobj(sp.coo_matrix((_data, (_rows, _cols))).tocsr()) - if td_type[1] > 0 or td_type[2] > 0: - H2 = [] - for k in range(len(H)): - if isinstance(H[k], list): - H2.append([tensor(qeye(N), H[k][0]), H[k][1]]) - else: - H2.append(tensor(qeye(N), H[k])) - else: - H2 = tensor(qeye(N), H) - options.normalize_output = False - output = sesolve(H2, psi0, tlist, [], - args=args, options=options, - _safe_mode=False) - for k, t in enumerate(tlist): - u[k] = sp_reshape(output.states[k].data, (N, N)) - unit_row_norm(u[k].data, u[k].indptr, u[k].shape[0]) - u[k] = u[k].T.tocsr() - - else: - raise Exception('Invalid unitary mode.') - - - elif len(c_op_list) == 0 and H0.issuper: - # calculate the propagator for the vector representation of the - # density matrix (a superoperator propagator) - unitary_mode = 'single' - N = H0.shape[0] - sqrt_N = int(np.sqrt(N)) - dims = H0.dims - - u = np.zeros([N, N, len(tlist)], dtype=complex) - - if parallel: - output = parallel_map(_parallel_mesolve,range(N * N), - task_args=( - sqrt_N, H, tlist, c_op_list, args, - options), - progress_bar=progress_bar, num_cpus=num_cpus) - for n in range(N * N): - for k, t in enumerate(tlist): - u[:, n, k] = mat2vec(output[n].states[k].full()).T - else: - rho0 = qeye(N,N) - rho0.dims = [[sqrt_N, sqrt_N], [sqrt_N, sqrt_N]] - output = mesolve(H, psi0, tlist, [], args, options, - _safe_mode=False) - if len(tlist) == 2: - return output.states[-1] - else: - return output.states - - else: - # calculate the propagator for the vector representation of the - # density matrix (a superoperator propagator) - unitary_mode = 'single' - N = H0.shape[0] - dims = [H0.dims, H0.dims] - - u = np.zeros([N * N, N * N, len(tlist)], dtype=complex) - - if parallel: - output = parallel_map(_parallel_mesolve, range(N * N), - task_args=( - N, H, tlist, c_op_list, args, options), - progress_bar=progress_bar, num_cpus=num_cpus) - for n in range(N * N): - for k, t in enumerate(tlist): - u[:, n, k] = mat2vec(output[n].states[k].full()).T - else: - progress_bar.start(N * N) - for n in range(N * N): - progress_bar.update(n) - col_idx, row_idx = np.unravel_index(n, (N, N)) - rho0 = Qobj(sp.csr_matrix(([1], ([row_idx], [col_idx])), - shape=(N,N), dtype=complex)) - output = mesolve(H, rho0, tlist, c_op_list, [], args, options, - _safe_mode=False) - for k, t in enumerate(tlist): - u[:, n, k] = mat2vec(output.states[k].full()).T - progress_bar.finished() - - if len(tlist) == 2: - if unitary_mode == 'batch': - return Qobj(u[-1], dims=dims) - else: - return Qobj(u[:, :, 1], dims=dims) - else: - if unitary_mode == 'batch': - return np.array([Qobj(u[k], dims=dims) - for k in range(len(tlist))], dtype=object) - else: - return np.array([Qobj(u[:, :, k], dims=dims) - for k in range(len(tlist))], dtype=object) - - -def _get_min_and_index(lst): - """ - Private function for obtaining min and max indicies. - """ - minval, minidx = lst[0], 0 - for i, v in enumerate(lst[1:]): - if v < minval: - minval, minidx = v, i + 1 - return minval, minidx - - -def propagator_steadystate(U): - """Find the steady state for successive applications of the propagator - :math:`U`. - - Parameters - ---------- - U : qobj - Operator representing the propagator. - - Returns - ------- - a : qobj - Instance representing the steady-state density matrix. - - """ - - evals, evecs = la.eig(U.full()) - - shifted_vals = np.abs(evals - 1.0) - ev_idx = np.argmin(shifted_vals) - ev_min = shifted_vals[ev_idx] - evecs = evecs.T - rho = Qobj(vec2mat(evecs[ev_idx]), dims=U.dims[0]) - rho = rho * (1.0 / rho.tr()) - rho = 0.5 * (rho + rho.dag()) # make sure rho is herm - rho.isherm = True - return rho - - -def _parallel_sesolve(n, N, H, tlist, args, options): - psi0 = basis(N, n) - output = sesolve(H, psi0, tlist, [], args, options, _safe_mode=False) - return output - -def _parallel_mesolve(n, N, H, tlist, c_op_list, args, options): - col_idx, row_idx = np.unravel_index(n, (N, N)) - rho0 = Qobj(sp.csr_matrix(([1], ([row_idx], [col_idx])), - shape=(N,N), dtype=complex)) - output = mesolve(H, rho0, tlist, c_op_list, [], args, options, - _safe_mode=False) - return output diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index 234d836e9d..06cf0c2d39 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -44,21 +44,18 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable= invalid-name, redefined-outer-name + """The Quantum Object (Qobj) class, for representing quantum states and operators, and related functions. """ -__all__ = ['Qobj', 'qobj_list_evaluate', 'ptrace', 'dag', 'isequal', - 'issuper', 'isoper', 'isoperket', 'isoperbra', 'isket', 'isbra', - 'isherm', 'shape', 'dims'] +__all__ = ['Qobj'] import warnings import types +import builtins -try: - import builtins -except: - import __builtin__ as builtins # import math functions from numpy.math: required for td string evaluation from numpy import (arccos, arccosh, arcsin, arcsinh, arctan, arctan2, arctanh, @@ -69,29 +66,17 @@ import numpy as np import scipy.sparse as sp import scipy.linalg as la -import qutip.settings as settings -from qutip import __version__ -from qutip.fastsparse import fast_csr_matrix, fast_identity -from qutip.cy.ptrace import _ptrace -from qutip.permute import _permute -from qutip.sparse import (sp_eigs, sp_expm, sp_fro_norm, sp_max_norm, +from .settings import (auto_tidyup, auto_tidyup_dims, atol, auto_tidyup_atol) +from qiskit.providers.aer.version import __version__ +from .fastsparse import fast_csr_matrix, fast_identity +from .sparse import (sp_eigs, sp_expm, sp_fro_norm, sp_max_norm, sp_one_norm, sp_L2_norm) -from qutip.dimensions import type_from_dims, enumerate_flat, collapse_dims_super -from qutip.cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_isherm, - zcsr_trace, zcsr_proj, zcsr_inner) -from qutip.cy.spmatfuncs import zcsr_mat_elem -from qutip.cy.sparse_utils import cy_tidyup -import sys -if sys.version_info.major >= 3: - from itertools import zip_longest -elif sys.version_info.major < 3: - from itertools import izip_longest - zip_longest = izip_longest - -#OPENMP stuff -from qutip.cy.openmp.utilities import use_openmp -if settings.has_openmp: - from qutip.cy.openmp.omp_sparse_utils import omp_tidyup +from .dimensions import type_from_dims, enumerate_flat, collapse_dims_super +from .cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_isherm, + zcsr_trace, zcsr_proj, zcsr_inner) +from .cy.spmatfuncs import zcsr_mat_elem +from .cy.sparse_utils import cy_tidyup +from itertools import zip_longest class Qobj(object): @@ -104,22 +89,6 @@ class Qobj(object): operator/state operations. The Qobj constructor optionally takes a dimension ``list`` and/or shape ``list`` as arguments. - Parameters - ---------- - inpt : array_like - Data for vector/matrix representation of the quantum object. - dims : list - Dimensions of object used for tensor products. - shape : list - Shape of underlying data structure (matrix shape). - copy : bool - Flag specifying whether Qobj should get a copy of the - input data, or use the original. - fast : bool - Flag for fast qobj creation when running ode solvers. - This parameter is used internally only. - - Attributes ---------- data : array_like @@ -194,13 +163,8 @@ class Qobj(object): Returns the matrix element of operator between `bra` and `ket` vectors. norm(norm='tr', sparse=False, tol=0, maxiter=100000) Returns norm of a ket or an operator. - permute(order) - Returns composite qobj with indices reordered. proj() Computes the projector for a ket or bra vector. - ptrace(sel) - Returns quantum object for selected dimensions after performing - partial trace. sinm() Sine of quantum object. sqrtm() @@ -221,12 +185,23 @@ class Qobj(object): """ __array_priority__ = 100 # sets Qobj priority above numpy arrays - + # pylint: disable=dangerous-default-value, redefined-builtin def __init__(self, inpt=None, dims=[[], []], shape=[], type=None, isherm=None, copy=True, fast=False, superrep=None, isunitary=None): """ Qobj constructor. + + Args: + inpt (ndarray): Input array or matrix data. + dims (list): List of Qobj dims. + shape (list): shape of underlying data. + type (str): Is object a ket, bra, oper, super. + isherm (bool): Is object Hermitian. + copy (bool): Copy input data. + fast (str or bool): Fast object instantiation. + superrep (str): Type of super representaiton. + isunitary (bool): Is object unitary. """ self._isherm = isherm self._type = type @@ -362,7 +337,9 @@ def copy(self): return Qobj(inpt=self) def get_data(self): + """Gets underlying data.""" return self._data + #Here we perfrom a check of the csr matrix type during setting of Q.data def set_data(self, data): if not isinstance(data, fast_csr_matrix): @@ -377,9 +354,6 @@ def __add__(self, other): """ self._isunitary = None - if isinstance(other, eseries): - return other.__radd__(self) - if not isinstance(other, Qobj): if isinstance(other, (int, float, complex, np.integer, np.floating, np.complexfloating, np.ndarray, list, tuple)) \ @@ -405,7 +379,7 @@ def __add__(self, other): out.dims = self.dims - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if isinstance(dat, (int, float)): out._isherm = self._isherm @@ -433,7 +407,7 @@ def __add__(self, other): out.data.data = out.data.data + dat out.dims = other.dims - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if isinstance(dat, complex): out._isherm = out.isherm @@ -454,7 +428,7 @@ def __add__(self, other): out = Qobj() out.data = self.data + other.data out.dims = self.dims - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if self.type in ['ket', 'bra', 'operator-ket', 'operator-bra']: out._isherm = False @@ -505,8 +479,8 @@ def __mul__(self, other): out.data = self.data * other.data dims = [self.dims[0], other.dims[1]] out.dims = dims - if settings.auto_tidyup: out.tidyup() - if (settings.auto_tidyup_dims + if auto_tidyup: out.tidyup() + if (auto_tidyup_dims and not isinstance(dims[0][0], list) and not isinstance(dims[1][0], list)): # If neither left or right is a superoperator, @@ -547,13 +521,13 @@ def __mul__(self, other): out = Qobj(other) out.data *= self.data[0, 0] out.superrep = other.superrep - return out.tidyup() if settings.auto_tidyup else out + return out.tidyup() if auto_tidyup else out elif np.prod(other.shape) == 1: out = Qobj(self) out.data *= other.data[0, 0] out.superrep = self.superrep - return out.tidyup() if settings.auto_tidyup else out + return out.tidyup() if auto_tidyup else out else: raise TypeError("Incompatible Qobj shapes") @@ -571,16 +545,13 @@ def __mul__(self, other): return np.array([self * item for item in other], dtype=object) - elif isinstance(other, eseries): - return other.__rmul__(self) - elif isinstance(other, (int, float, complex, np.integer, np.floating, np.complexfloating)): out = Qobj() out.data = self.data * other out.dims = self.dims out.superrep = self.superrep - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -607,16 +578,13 @@ def __rmul__(self, other): return np.array([item * self for item in other], dtype=object) - elif isinstance(other, eseries): - return other.__mul__(self) - elif isinstance(other, (int, float, complex, np.integer, np.floating, np.complexfloating)): out = Qobj() out.data = other * self.data out.dims = self.dims out.superrep = self.superrep - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -643,7 +611,7 @@ def __div__(self, other): out = Qobj() out.data = self.data / other out.dims = self.dims - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -664,7 +632,7 @@ def __neg__(self): out.data = -self.data out.dims = self.dims out.superrep = self.superrep - if settings.auto_tidyup: out.tidyup() + if auto_tidyup: out.tidyup() out._isherm = self._isherm out._isunitary = self._isunitary return out @@ -685,8 +653,7 @@ def __eq__(self, other): """ if (isinstance(other, Qobj) and self.dims == other.dims and - not np.any(np.abs((self.data - other.data).data) > - settings.atol)): + not np.any(np.abs((self.data - other.data).data) > atol)): return True else: return False @@ -712,7 +679,7 @@ def __pow__(self, n, m=None): # calculates powers of Qobj data = self.data ** n out = Qobj(data, dims=self.dims) out.superrep = self.superrep - return out.tidyup() if settings.auto_tidyup else out + return out.tidyup() if auto_tidyup else out except: raise ValueError('Invalid choice of exponent.') @@ -772,17 +739,6 @@ def __call__(self, other): if not isinstance(other, Qobj): raise TypeError("Only defined for quantum objects.") - if self.type == "super": - if other.type == "ket": - other = qutip.states.ket2dm(other) - - if other.type == "oper": - return qutip.superoperator.vector_to_operator( - self * qutip.superoperator.operator_to_vector(other) - ) - else: - raise TypeError("Can only act super on oper or ket.") - elif self.type == "oper": if other.type == "ket": return self * other @@ -791,13 +747,13 @@ def __call__(self, other): def __getstate__(self): # defines what happens when Qobj object gets pickled - self.__dict__.update({'qutip_version': __version__[:5]}) + self.__dict__.update({'qiskit_version': __version__[:5]}) return self.__dict__ def __setstate__(self, state): # defines what happens when loading a pickled Qobj - if 'qutip_version' in state.keys(): - del state['qutip_version'] + if 'qiskit_version' in state.keys(): + del state['qiskit_version'] (self.__dict__).update(state) def _repr_latex_(self): @@ -844,9 +800,9 @@ def _format_element(m, n, d): if type(d) == str: return s + d else: - if abs(np.imag(d)) < settings.atol: + if abs(np.imag(d)) < atol: return s + _format_float(np.real(d)) - elif abs(np.real(d)) < settings.atol: + elif abs(np.real(d)) < atol: return s + _format_float(np.imag(d)) + "j" else: s_re = _format_float(np.real(d)) @@ -927,24 +883,6 @@ def dag(self): out.superrep = self.superrep return out - def dual_chan(self): - """Dual channel of quantum object representing a completely positive - map. - """ - # Uses the technique of Johnston and Kribs (arXiv:1102.0948), which - # is only valid for completely positive maps. - if not self.iscp: - raise ValueError("Dual channels are only implemented for CP maps.") - J = sr.to_choi(self) - tensor_idxs = enumerate_flat(J.dims) - J_dual = tensor.tensor_swap(J, *( - list(zip(tensor_idxs[0][1], tensor_idxs[0][0])) + - list(zip(tensor_idxs[1][1], tensor_idxs[1][0])) - )).trans() - J_dual.superrep = 'choi' - return J_dual - - def conj(self): """Conjugate operator of quantum object. """ @@ -1017,15 +955,10 @@ def norm(self, norm=None, sparse=False, tol=0, maxiter=100000): def proj(self): """Form the projector from a given ket or bra vector. - Parameters - ---------- - Q : :class:`qutip.Qobj` - Input bra or ket vector - - Returns - ------- - P : :class:`qutip.Qobj` - Projection operator. + Returns: + qobj.Qobj: Projection operator. + Raises: + TypeError: Project from only bra or ket. """ if self.isket: _out = zcsr_proj(self.data,1) @@ -1071,6 +1004,7 @@ def full(self, order='C', squeeze=False): else: return self.data.toarray(order=order) + # pylint: disable=unused-argument def __array__(self, *arg, **kwarg): """Numpy array from Qobj For compatibility with np.array @@ -1088,7 +1022,7 @@ def diag(self): """ out = self.data.diagonal() - if np.any(np.imag(out) > settings.atol) or not self.isherm: + if np.any(np.imag(out) > atol) or not self.isherm: return out else: return np.real(out) @@ -1130,7 +1064,7 @@ def expm(self, method='dense'): raise ValueError("method must be 'dense' or 'sparse'.") out = Qobj(F, dims=self.dims) - return out.tidyup() if settings.auto_tidyup else out + return out.tidyup() if auto_tidyup else out def check_herm(self): """Check if the quantum object is hermitian. @@ -1185,7 +1119,7 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): spDv = dV.dot(np.linalg.inv(evecs.T)) out = Qobj(evecs.T.dot(spDv), dims=self.dims) - return out.tidyup() if settings.auto_tidyup else out + return out.tidyup() if auto_tidyup else out else: raise TypeError('Invalid operand for matrix square root') @@ -1212,7 +1146,7 @@ def cosm(self): """ if self.dims[0][0] == self.dims[1][0]: - return 0.5 * ((1j * self).expm() + (-1j * self).expm()) + return 0.5 * ((1j * self).expm() + (-1j * self).expm()) else: raise TypeError('Invalid operand for matrix square root') @@ -1251,25 +1185,18 @@ def unit(self, inplace=False, Uses norm from Qobj.norm(). - Parameters - ---------- - inplace : bool - Do an in-place normalization - norm : str - Requested norm for states / operators. - sparse : bool - Use sparse eigensolver for trace norm. Does not affect other norms. - tol : float - Tolerance used by sparse eigensolver. - maxiter : int - Number of maximum iterations performed by sparse eigensolver. + Args: + inplace (bool): Do an in-place normalization + norm (str): Requested norm for states / operators. + sparse (bool): Use sparse eigensolver for trace norm. Does not affect other norms. + tol (float): Tolerance used by sparse eigensolver. + maxiter (int): Number of maximum iterations performed by sparse eigensolver. - Returns - ------- - oper : :class:`qutip.Qobj` - Normalized quantum object if not in-place, - else None. + Returns: + qobj.Qobj: Normalized quantum object if not in-place, else None. + Raises: + TypeError: Inplace value must be a bool. """ if inplace: nrm = self.norm(norm=norm, sparse=sparse, @@ -1279,56 +1206,14 @@ def unit(self, inplace=False, elif not inplace: out = self / self.norm(norm=norm, sparse=sparse, tol=tol, maxiter=maxiter) - if settings.auto_tidyup: + if auto_tidyup: return out.tidyup() else: return out else: - raise Exception('inplace kwarg must be bool.') + raise TypeError('inplace kwarg must be bool.') - def ptrace(self, sel): - """Partial trace of the quantum object. - - Parameters - ---------- - sel : int/list - An ``int`` or ``list`` of components to keep after partial trace. - - Returns - ------- - oper : :class:`qutip.Qobj` - Quantum object representing partial trace with selected components - remaining. - - Notes - ----- - This function is identical to the :func:`qutip.qobj.ptrace` function - that has been deprecated. - - """ - q = Qobj() - q.data, q.dims, _ = _ptrace(self, sel) - return q.tidyup() if settings.auto_tidyup else q - - def permute(self, order): - """Permutes a composite quantum object. - - Parameters - ---------- - order : list/array - List specifying new tensor order. - - Returns - ------- - P : :class:`qutip.Qobj` - Permuted quantum object. - - """ - q = Qobj() - q.data, q.dims = _permute(self, order) - return q.tidyup() if settings.auto_tidyup else q - - def tidyup(self, atol=settings.auto_tidyup_atol): + def tidyup(self, atol=auto_tidyup_atol): """Removes small elements from the quantum object. Parameters @@ -1346,13 +1231,8 @@ def tidyup(self, atol=settings.auto_tidyup_atol): if self.data.nnz: #This does the tidyup and returns True if #The sparse data needs to be shortened - if use_openmp() and self.data.nnz > 500: - if omp_tidyup(self.data.data,atol,self.data.nnz, - settings.num_cpus): - self.data.eliminate_zeros() - else: - if cy_tidyup(self.data.data,atol,self.data.nnz): - self.data.eliminate_zeros() + if cy_tidyup(self.data.data, atol, self.data.nnz): + self.data.eliminate_zeros() return self else: return self @@ -1407,7 +1287,7 @@ def transform(self, inpt, inverse=False, sparse=True): # transform data if inverse: if self.isket: - data = (S.conj().T) * self.data + data = (S.conj().T) * self.data elif self.isbra: data = self.data.dot(S) else: @@ -1430,76 +1310,12 @@ def transform(self, inpt, inverse=False, sparse=True): out._isherm = self._isherm out.superrep = self.superrep - if settings.auto_tidyup: + if auto_tidyup: return out.tidyup() else: return out - - def trunc_neg(self, method="clip"): - """Truncates negative eigenvalues and renormalizes. - - Returns a new Qobj by removing the negative eigenvalues - of this instance, then renormalizing to obtain a valid density - operator. - - - Parameters - ---------- - method : str - Algorithm to use to remove negative eigenvalues. "clip" - simply discards negative eigenvalues, then renormalizes. - "sgs" uses the SGS algorithm (doi:10/bb76) to find the - positive operator that is nearest in the Shatten 2-norm. - - Returns - ------- - oper : :class:`qutip.Qobj` - A valid density operator. - - """ - if not self.isherm: - raise ValueError("Must be a Hermitian operator to remove negative " - "eigenvalues.") - - if method not in ('clip', 'sgs'): - raise ValueError("Method {} not recognized.".format(method)) - - eigvals, eigstates = self.eigenstates() - if all([eigval >= 0 for eigval in eigvals]): - # All positive, so just renormalize. - return self.unit() - idx_nonzero = eigvals != 0 - eigvals = eigvals[idx_nonzero] - eigstates = eigstates[idx_nonzero] - - if method == 'clip': - eigvals[eigvals < 0] = 0 - elif method == 'sgs': - eigvals = eigvals[::-1] - eigstates = eigstates[::-1] - - acc = 0.0 - dim = self.shape[0] - n_eigs = len(eigvals) - - for idx in reversed(range(n_eigs)): - if eigvals[idx] + acc / (idx + 1) >= 0: - break - else: - acc += eigvals[idx] - eigvals[idx] = 0.0 - - eigvals[:idx+1] += acc / (idx + 1) - - return sum([ - val * qutip.states.ket2dm(state) - for val, state in zip(eigvals, eigstates) - ], Qobj(np.zeros(self.shape), dims=self.dims) - ).unit() - - def matrix_element(self, bra, ket): """Calculates a matrix element. @@ -1578,7 +1394,7 @@ def overlap(self, other): #is not common, and not optimized. return zcsr_inner(self.data, other.dag().data, 1) elif other.isoper: - return (qutip.states.ket2dm(self).dag() * other).tr() + return (states.ket2dm(self).dag() * other).tr() else: raise TypeError("Can only calculate overlap for state vector Qobjs") @@ -1588,13 +1404,13 @@ def overlap(self, other): elif other.isket: return zcsr_inner(self.data, other.data, 0) elif other.isoper: - return (qutip.states.ket2dm(self).dag() * other).tr() + return (states.ket2dm(self).dag() * other).tr() else: raise TypeError("Can only calculate overlap for state vector Qobjs") elif self.isoper: if other.isket or other.isbra: - return (self.dag() * qutip.states.ket2dm(other)).tr() + return (self.dag() * states.ket2dm(other)).tr() elif other.isoper: return (self.dag() * other).tr() else: @@ -1749,174 +1565,6 @@ def trans(self): out.dims = [self.dims[1], self.dims[0]] return out - def extract_states(self, states_inds, normalize=False): - """Qobj with states in state_inds only. - - Parameters - ---------- - states_inds : list of integer - The states that should be kept. - - normalize : True / False - Weather or not the new Qobj instance should be normalized (default - is False). For Qobjs that represents density matrices or state - vectors normalized should probably be set to True, but for Qobjs - that represents operators in for example an Hamiltonian, normalize - should be False. - - Returns - ------- - q : :class:`qutip.Qobj` - A new instance of :class:`qutip.Qobj` that contains only the states - corresponding to the indices in `state_inds`. - - Notes - ----- - Experimental. - - """ - if self.isoper: - q = Qobj(self.data[states_inds, :][:, states_inds]) - elif self.isket: - q = Qobj(self.data[states_inds, :]) - elif self.isbra: - q = Qobj(self.data[:, states_inds]) - else: - raise TypeError("Can only eliminate states from operators or " + - "state vectors") - - return q.unit() if normalize else q - - def eliminate_states(self, states_inds, normalize=False): - """Creates a new quantum object with states in state_inds eliminated. - - Parameters - ---------- - states_inds : list of integer - The states that should be removed. - - normalize : True / False - Weather or not the new Qobj instance should be normalized (default - is False). For Qobjs that represents density matrices or state - vectors normalized should probably be set to True, but for Qobjs - that represents operators in for example an Hamiltonian, normalize - should be False. - - Returns - ------- - q : :class:`qutip.Qobj` - A new instance of :class:`qutip.Qobj` that contains only the states - corresponding to indices that are **not** in `state_inds`. - - Notes - ----- - Experimental. - - """ - keep_indices = np.array([s not in states_inds - for s in range(self.shape[0])]).nonzero()[0] - - return self.extract_states(keep_indices, normalize=normalize) - - def dnorm(self, B=None): - """Calculates the diamond norm, or the diamond distance to another - operator. - - Parameters - ---------- - B : :class:`qutip.Qobj` or None - If B is not None, the diamond distance d(A, B) = dnorm(A - B) between - this operator and B is returned instead of the diamond norm. - - Returns - ------- - d : float - Either the diamond norm of this operator, or the diamond distance - from this operator to B. - - """ - return mts.dnorm(self, B) - - - @property - def ishp(self): - # FIXME: this needs to be cached in the same ways as isherm. - if self.type in ["super", "oper"]: - try: - J = sr.to_choi(self) - return J.isherm - except: - return False - else: - return False - - @property - def iscp(self): - # FIXME: this needs to be cached in the same ways as isherm. - if self.type in ["super", "oper"]: - try: - J = ( - self - # We can test with either Choi or chi, since the basis - # transformation between them is unitary and hence - # preserves the CP and TP conditions. - if self.superrep in ('choi', 'chi') - else sr.to_choi(self) - ) - # If J isn't hermitian, then that could indicate either - # that J is not normal, or is normal, but has complex eigenvalues. - # In either case, it makes no sense to then demand that the - # eigenvalues be non-negative. - if not J.isherm: - return False - eigs = J.eigenenergies() - return all(eigs >= -settings.atol) - except: - return False - else: - return False - - @property - def istp(self): - import qutip.superop_reps as sr - if self.type in ["super", "oper"]: - try: - # Normalize to a super of type choi or chi. - # We can test with either Choi or chi, since the basis - # transformation between them is unitary and hence - # preserves the CP and TP conditions. - if self.type == "super" and self.superrep in ('choi', 'chi'): - qobj = self - else: - qobj = sr.to_choi(self) - - # Possibly collapse dims. - if any([len(index) > 1 for super_index in qobj.dims - for index in super_index]): - qobj = Qobj(qobj, dims=collapse_dims_super(qobj.dims)) - else: - qobj = qobj - - # We use the condition from John Watrous' lecture notes, - # Tr_1(J(Phi)) = identity_2. - tr_oper = qobj.ptrace([0]) - ident = ops.identity(tr_oper.shape[0]) - return isequal(tr_oper, ident) - except: - return False - else: - return False - - @property - def iscptp(self): - from qutip.superop_reps import to_choi - if self.type == "super" or self.type == "oper": - reps = ('choi', 'chi') - q_oper = to_choi(self) if self.superrep not in reps else self - return q_oper.iscp and q_oper.istp - else: - return False - @property def isherm(self): @@ -1940,11 +1588,11 @@ def check_isunitary(self): eye_data = fast_identity(self.shape[0]) return not (np.any(np.abs((self.data*self.dag().data - eye_data).data) - > settings.atol) + > atol) or np.any(np.abs((self.dag().data*self.data - eye_data).data) > - settings.atol) + atol) ) else: @@ -1966,6 +1614,8 @@ def isunitary(self, isunitary): @property def type(self): + """Type of Qobj + """ if not self._type: self._type = type_from_dims(self.dims) @@ -1973,6 +1623,8 @@ def type(self): @property def shape(self): + """Shape of Qobj + """ if self.data.shape == (1, 1): return tuple([np.prod(self.dims[0]), np.prod(self.dims[1])]) else: @@ -1980,26 +1632,32 @@ def shape(self): @property def isbra(self): + """Is bra vector""" return self.type == 'bra' @property def isket(self): + """Is ket vector""" return self.type == 'ket' @property def isoperbra(self): + """Is operator-bra""" return self.type == 'operator-bra' @property def isoperket(self): + """Is operator-ket""" return self.type == 'operator-ket' @property def isoper(self): + """Is operator""" return self.type == 'oper' @property def issuper(self): + """Is super operator""" return self.type == 'super' @staticmethod @@ -2065,359 +1723,4 @@ def evaluate(qobj_list, t, args): return q_sum - -# ----------------------------------------------------------------------------- -# This functions evaluates a time-dependent quantum object on the list-string -# and list-function formats that are used by the time-dependent solvers. -# Although not used directly in by those solvers, it can for test purposes be -# conventient to be able to evaluate the expressions passed to the solver for -# arbitrary value of time. This function provides this functionality. -# -def qobj_list_evaluate(qobj_list, t, args): - """ - Depracated: See Qobj.evaluate - """ - warnings.warn("Deprecated: Use Qobj.evaluate", DeprecationWarning) - return Qobj.evaluate(qobj_list, t, args) - - -# ----------------------------------------------------------------------------- -# -# A collection of tests used to determine the type of quantum objects, and some -# functions for increased compatibility with quantum optics toolbox. -# - -def dag(A): - """Adjont operator (dagger) of a quantum object. - - Parameters - ---------- - A : :class:`qutip.Qobj` - Input quantum object. - - Returns - ------- - oper : :class:`qutip.Qobj` - Adjoint of input operator - - Notes - ----- - This function is for legacy compatibility only. It is recommended to use - the ``dag()`` Qobj method. - - """ - if not isinstance(A, Qobj): - raise TypeError("Input is not a quantum object") - - return A.dag() - - -def ptrace(Q, sel): - """Partial trace of the Qobj with selected components remaining. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Composite quantum object. - sel : int/list - An ``int`` or ``list`` of components to keep after partial trace. - - Returns - ------- - oper : :class:`qutip.Qobj` - Quantum object representing partial trace with selected components - remaining. - - Notes - ----- - This function is for legacy compatibility only. It is recommended to use - the ``ptrace()`` Qobj method. - - """ - if not isinstance(Q, Qobj): - raise TypeError("Input is not a quantum object") - - return Q.ptrace(sel) - - -def dims(inpt): - """Returns the dims attribute of a quantum object. - - Parameters - ---------- - inpt : :class:`qutip.Qobj` - Input quantum object. - - Returns - ------- - dims : list - A ``list`` of the quantum objects dimensions. - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.dims` - attribute is recommended. - - """ - if isinstance(inpt, Qobj): - return inpt.dims - else: - raise TypeError("Input is not a quantum object") - - -def shape(inpt): - """Returns the shape attribute of a quantum object. - - Parameters - ---------- - inpt : :class:`qutip.Qobj` - Input quantum object. - - Returns - ------- - shape : list - A ``list`` of the quantum objects shape. - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.shape` - attribute is recommended. - - """ - if isinstance(inpt, Qobj): - return Qobj.shape - else: - return np.shape(inpt) - - -def isket(Q): - """ - Determines if given quantum object is a ket-vector. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isket : bool - True if qobj is ket-vector, False otherwise. - - Examples - -------- - >>> psi = basis(5,2) - >>> isket(psi) - True - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isket` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isket else False - - -def isbra(Q): - """Determines if given quantum object is a bra-vector. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isbra : bool - True if Qobj is bra-vector, False otherwise. - - Examples - -------- - >>> psi = basis(5,2) - >>> isket(psi) - False - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isbra` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isbra else False - - -def isoperket(Q): - """Determines if given quantum object is an operator in column vector form - (operator-ket). - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isoperket : bool - True if Qobj is operator-ket, False otherwise. - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isoperket` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isoperket else False - - -def isoperbra(Q): - """Determines if given quantum object is an operator in row vector form - (operator-bra). - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isoperbra : bool - True if Qobj is operator-bra, False otherwise. - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isoperbra` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isoperbra else False - - -def isoper(Q): - """Determines if given quantum object is a operator. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isoper : bool - True if Qobj is operator, False otherwise. - - Examples - -------- - >>> a = destroy(5) - >>> isoper(a) - True - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isoper` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isoper else False - - -def issuper(Q): - """Determines if given quantum object is a super-operator. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - issuper : bool - True if Qobj is superoperator, False otherwise. - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.issuper` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.issuper else False - - -def isequal(A, B, tol=None): - """Determines if two qobj objects are equal to within given tolerance. - - Parameters - ---------- - A : :class:`qutip.Qobj` - Qobj one - B : :class:`qutip.Qobj` - Qobj two - tol : float - Tolerence for equality to be valid - - Returns - ------- - isequal : bool - True if qobjs are equal, False otherwise. - - Notes - ----- - This function is for legacy compatibility only. Instead, it is recommended - to use the equality operator of Qobj instances instead: A == B. - - """ - if tol is None: - tol = settings.atol - - if not isinstance(A, Qobj) or not isinstance(B, Qobj): - return False - - if A.dims != B.dims: - return False - - Adat = A.data - Bdat = B.data - elems = (Adat - Bdat).data - if np.any(np.abs(elems) > tol): - return False - - return True - - -def isherm(Q): - """Determines if given operator is Hermitian. - - Parameters - ---------- - Q : :class:`qutip.Qobj` - Quantum object - - Returns - ------- - isherm : bool - True if operator is Hermitian, False otherwise. - - Examples - -------- - >>> a = destroy(4) - >>> isherm(a) - False - - Notes - ----- - This function is for legacy compatibility only. Using the `Qobj.isherm` - attribute is recommended. - - """ - return True if isinstance(Q, Qobj) and Q.isherm else False - - -# TRAILING IMPORTS -# We do a few imports here to avoid circular dependencies. -from qutip.eseries import eseries -import qutip.superop_reps as sr -import qutip.tensor as tensor -import qutip.operators as ops -import qutip.metrics as mts -import qutip.states -import qutip.superoperator +from ..qutip_lite import states \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qutip_lite/settings.py b/qiskit/providers/aer/openpulse/qutip_lite/settings.py index ab17dd3c89..cee33c3bcc 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/settings.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/settings.py @@ -44,11 +44,11 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name + """ -This module contains settings for the QuTiP graphics, multiprocessing, and -tidyup functionality, etc. +This module contains settings for qutip_lite functionality """ -from __future__ import absolute_import # use auto tidyup auto_tidyup = True # use auto tidyup dims on multiplication @@ -59,42 +59,3 @@ atol = 1e-12 # use auto tidyup absolute tolerance auto_tidyup_atol = 1e-12 -# number of cpus (set at qutip import) -num_cpus = 0 -# flag indicating if fortran module is installed -fortran = False -# path to the MKL library -mkl_lib = None -# Flag if mkl_lib is found -has_mkl = False -# Has OPENMP -has_openmp = False -# debug mode for development -debug = False -# are we in IPython? Note that this cannot be -# set by the RC file. -ipython = False -# define whether log handler should be -# - default: switch based on IPython detection -# - stream: set up non-propagating StreamHandler -# - basic: call basicConfig -# - null: leave logging to the user -log_handler = 'default' -# Allow for a colorblind mode that uses different colormaps -# and plotting options by default. -colorblind_safe = False -# Sets the threshold for matrix NNZ where OPENMP -# turns on. This is automatically calculated and -# put in the qutiprc file. This value is here in case -# that failts -openmp_thresh = 10000 -# Note that since logging depends on settings, -# if we want to do any logging here, it must be manually -# configured, rather than through _logging.get_logger(). -try: - import logging - _logger = logging.getLogger(__name__) - _logger.addHandler(logging.NullHandler()) - del logging # Don't leak names! -except: - _logger = None diff --git a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py index 42321085ac..7c422172bc 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py @@ -44,6 +44,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name + """ This module contains a collection of routines for operating on sparse matrices on the scipy.sparse formats, for use internally by other modules @@ -59,20 +61,14 @@ import numpy as np import scipy.linalg as la from scipy.linalg.blas import get_blas_funcs -_dznrm2 = get_blas_funcs("znrm2") -from qutip.cy.sparse_utils import (_sparse_profile, _sparse_permute, +from .cy.sparse_utils import (_sparse_profile, _sparse_permute, _sparse_reverse_permute, _sparse_bandwidth, _isdiag, zcsr_one_norm, zcsr_inf_norm) -from qutip.fastsparse import fast_csr_matrix -from qutip.cy.spconvert import (arr_coo2fast, zcsr_reshape) -from qutip.settings import debug +from .fastsparse import fast_csr_matrix +from .cy.spconvert import (arr_coo2fast, zcsr_reshape) -import qutip.logging_utils -logger = qutip.logging_utils.get_logger() - -if debug: - import inspect +_dznrm2 = get_blas_funcs("znrm2") def sp_fro_norm(data): """ diff --git a/qiskit/providers/aer/openpulse/qutip_lite/states.py b/qiskit/providers/aer/openpulse/qutip_lite/states.py index e01572dd0b..7808a9a1eb 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/states.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/states.py @@ -45,24 +45,13 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -__all__ = ['basis', 'qutrit_basis', 'coherent', 'coherent_dm', 'fock_dm', - 'fock', 'thermal_dm', 'maximally_mixed_dm', 'ket2dm', 'projection', - 'qstate', 'ket', 'bra', 'state_number_enumerate', - 'state_number_index', 'state_index_number', 'state_number_qobj', - 'phase_basis', 'zero_ket', 'spin_state', 'spin_coherent', - 'bell_state', 'singlet_state', 'triplet_states', 'w_state', - 'ghz_state', 'enr_state_dictionaries', 'enr_fock', - 'enr_thermal_dm'] - import numpy as np -from scipy import arange, conj, prod +from scipy import arange, conj import scipy.sparse as sp -from qutip.qobj import Qobj -from qutip.operators import destroy, jmat -from qutip.tensor import tensor - -from qutip.fastsparse import fast_csr_matrix +from .qobj import Qobj +from .operators import destroy +from .fastsparse import fast_csr_matrix def basis(N, n=0, offset=0): @@ -500,524 +489,6 @@ def projection(N, n, m, offset=0): return ket1 * ket2.dag() -# -# composite qubit states -# -def qstate(string): - """Creates a tensor product for a set of qubits in either - the 'up' :math:`|0>` or 'down' :math:`|1>` state. - - Parameters - ---------- - string : str - String containing 'u' or 'd' for each qubit (ex. 'ududd') - - Returns - ------- - qstate : qobj - Qobj for tensor product corresponding to input string. - - Notes - ----- - Look at ket and bra for more general functions - creating multiparticle states. - - Examples - -------- - >>> qstate('udu') - Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = [8, 1], type = ket - Qobj data = - [[ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 1.] - [ 0.] - [ 0.]] - - """ - n = len(string) - if n != (string.count('u') + string.count('d')): - raise TypeError('String input to QSTATE must consist ' + - 'of "u" and "d" elements only') - else: - up = basis(2, 1) - dn = basis(2, 0) - lst = [] - for k in range(n): - if string[k] == 'u': - lst.append(up) - else: - lst.append(dn) - return tensor(lst) - - -# -# different qubit notation dictionary -# -_qubit_dict = {'g': 0, # ground state - 'e': 1, # excited state - 'u': 0, # spin up - 'd': 1, # spin down - 'H': 0, # horizontal polarization - 'V': 1} # vertical polarization - - -def _character_to_qudit(x): - """ - Converts a character representing a one-particle state into int. - """ - if x in _qubit_dict: - return _qubit_dict[x] - else: - return int(x) - - -def ket(seq, dim=2): - """ - Produces a multiparticle ket state for a list or string, - where each element stands for state of the respective particle. - - Parameters - ---------- - seq : str / list of ints or characters - Each element defines state of the respective particle. - (e.g. [1,1,0,1] or a string "1101"). - For qubits it is also possible to use the following conventions: - - 'g'/'e' (ground and excited state) - - 'u'/'d' (spin up and down) - - 'H'/'V' (horizontal and vertical polarization) - Note: for dimension > 9 you need to use a list. - - - dim : int (default: 2) / list of ints - Space dimension for each particle: - int if there are the same, list if they are different. - - Returns - ------- - ket : qobj - - Examples - -------- - >>> ket("10") - Quantum object: dims = [[2, 2], [1, 1]], shape = [4, 1], type = ket - Qobj data = - [[ 0.] - [ 0.] - [ 1.] - [ 0.]] - - >>> ket("Hue") - Quantum object: dims = [[2, 2, 2], [1, 1, 1]], shape = [8, 1], type = ket - Qobj data = - [[ 0.] - [ 1.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.]] - - >>> ket("12", 3) - Quantum object: dims = [[3, 3], [1, 1]], shape = [9, 1], type = ket - Qobj data = - [[ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 1.] - [ 0.] - [ 0.] - [ 0.]] - - >>> ket("31", [5, 2]) - Quantum object: dims = [[5, 2], [1, 1]], shape = [10, 1], type = ket - Qobj data = - [[ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 1.] - [ 0.] - [ 0.]] - """ - if isinstance(dim, int): - dim = [dim] * len(seq) - return tensor([basis(dim[i], _character_to_qudit(x)) - for i, x in enumerate(seq)]) - - -def bra(seq, dim=2): - """ - Produces a multiparticle bra state for a list or string, - where each element stands for state of the respective particle. - - Parameters - ---------- - seq : str / list of ints or characters - Each element defines state of the respective particle. - (e.g. [1,1,0,1] or a string "1101"). - For qubits it is also possible to use the following conventions: - - 'g'/'e' (ground and excited state) - - 'u'/'d' (spin up and down) - - 'H'/'V' (horizontal and vertical polarization) - Note: for dimension > 9 you need to use a list. - - - dim : int (default: 2) / list of ints - Space dimension for each particle: - int if there are the same, list if they are different. - - Returns - ------- - bra : qobj - - Examples - -------- - >>> bra("10") - Quantum object: dims = [[1, 1], [2, 2]], shape = [1, 4], type = bra - Qobj data = - [[ 0. 0. 1. 0.]] - - >>> bra("Hue") - Quantum object: dims = [[1, 1, 1], [2, 2, 2]], shape = [1, 8], type = bra - Qobj data = - [[ 0. 1. 0. 0. 0. 0. 0. 0.]] - - >>> bra("12", 3) - Quantum object: dims = [[1, 1], [3, 3]], shape = [1, 9], type = bra - Qobj data = - [[ 0. 0. 0. 0. 0. 1. 0. 0. 0.]] - - - >>> bra("31", [5, 2]) - Quantum object: dims = [[1, 1], [5, 2]], shape = [1, 10], type = bra - Qobj data = - [[ 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]] - """ - return ket(seq, dim=dim).dag() - - -# -# quantum state number helper functions -# -def state_number_enumerate(dims, excitations=None, state=None, idx=0): - """ - An iterator that enumerate all the state number arrays (quantum numbers on - the form [n1, n2, n3, ...]) for a system with dimensions given by dims. - - Example: - - >>> for state in state_number_enumerate([2,2]): - >>> print(state) - [ 0 0 ] - [ 0 1 ] - [ 1 0 ] - [ 1 1 ] - - Parameters - ---------- - dims : list or array - The quantum state dimensions array, as it would appear in a Qobj. - - state : list - Current state in the iteration. Used internally. - - excitations : integer (None) - Restrict state space to states with excitation numbers below or - equal to this value. - - idx : integer - Current index in the iteration. Used internally. - - Returns - ------- - state_number : list - Successive state number arrays that can be used in loops and other - iterations, using standard state enumeration *by definition*. - - """ - - if state is None: - state = np.zeros(len(dims), dtype=int) - - if excitations and sum(state[0:idx]) > excitations: - pass - elif idx == len(dims): - if excitations is None: - yield np.array(state) - else: - yield tuple(state) - else: - for n in range(dims[idx]): - state[idx] = n - for s in state_number_enumerate(dims, excitations, state, idx + 1): - yield s - - -def state_number_index(dims, state): - """ - Return the index of a quantum state corresponding to state, - given a system with dimensions given by dims. - - Example: - - >>> state_number_index([2, 2, 2], [1, 1, 0]) - 6 - - Parameters - ---------- - dims : list or array - The quantum state dimensions array, as it would appear in a Qobj. - - state : list - State number array. - - Returns - ------- - idx : int - The index of the state given by `state` in standard enumeration - ordering. - - """ - return int( - sum([state[i] * prod(dims[i + 1:]) for i, d in enumerate(dims)])) - - -def state_index_number(dims, index): - """ - Return a quantum number representation given a state index, for a system - of composite structure defined by dims. - - Example: - - >>> state_index_number([2, 2, 2], 6) - [1, 1, 0] - - Parameters - ---------- - dims : list or array - The quantum state dimensions array, as it would appear in a Qobj. - - index : integer - The index of the state in standard enumeration ordering. - - Returns - ------- - state : list - The state number array corresponding to index `index` in standard - enumeration ordering. - - """ - state = np.empty_like(dims) - - D = np.concatenate([np.flipud(np.cumprod(np.flipud(dims[1:]))), [1]]) - - for n in range(len(dims)): - state[n] = index / D[n] - index -= state[n] * D[n] - - return list(state) - - -def state_number_qobj(dims, state): - """ - Return a Qobj representation of a quantum state specified by the state - array `state`. - - Example: - - >>> state_number_qobj([2, 2, 2], [1, 0, 1]) - Quantum object: dims = [[2, 2, 2], [1, 1, 1]], \ -shape = [8, 1], type = ket - Qobj data = - [[ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 0.] - [ 1.] - [ 0.] - [ 0.]] - - Parameters - ---------- - dims : list or array - The quantum state dimensions array, as it would appear in a Qobj. - - state : list - State number array. - - Returns - ------- - state : :class:`qutip.Qobj.qobj` - The state as a :class:`qutip.Qobj.qobj` instance. - - - """ - return tensor([fock(dims[i], s) for i, s in enumerate(state)]) - - -# -# Excitation-number restricted (enr) states -# -def enr_state_dictionaries(dims, excitations): - """ - Return the number of states, and lookup-dictionaries for translating - a state tuple to a state index, and vice versa, for a system with a given - number of components and maximum number of excitations. - - Parameters - ---------- - dims: list - A list with the number of states in each sub-system. - - excitations : integer - The maximum numbers of dimension - - Returns - ------- - nstates, state2idx, idx2state: integer, dict, dict - The number of states `nstates`, a dictionary for looking up state - indices from a state tuple, and a dictionary for looking up state - state tuples from state indices. - """ - nstates = 0 - state2idx = {} - idx2state = {} - - for state in state_number_enumerate(dims, excitations): - state2idx[state] = nstates - idx2state[nstates] = state - nstates += 1 - - return nstates, state2idx, idx2state - - -def enr_fock(dims, excitations, state): - """ - Generate the Fock state representation in a excitation-number restricted - state space. The `dims` argument is a list of integers that define the - number of quantums states of each component of a composite quantum system, - and the `excitations` specifies the maximum number of excitations for - the basis states that are to be included in the state space. The `state` - argument is a tuple of integers that specifies the state (in the number - basis representation) for which to generate the Fock state representation. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - state : list of integers - The state in the number basis representation. - - Returns - ------- - ket : Qobj - A Qobj instance that represent a Fock state in the exication-number- - restricted state space defined by `dims` and `exciations`. - - """ - nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) - - data = sp.lil_matrix((nstates, 1), dtype=np.complex) - - try: - data[state2idx[tuple(state)], 0] = 1 - except: - raise ValueError("The state tuple %s is not in the restricted " - "state space" % str(tuple(state))) - - return Qobj(data, dims=[dims, [1]*len(dims)]) - - -def enr_thermal_dm(dims, excitations, n): - """ - Generate the density operator for a thermal state in the excitation-number- - restricted state space defined by the `dims` and `exciations` arguments. - See the documentation for enr_fock for a more detailed description of - these arguments. The temperature of each mode in dims is specified by - the average number of excitatons `n`. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - n : integer - The average number of exciations in the thermal state. `n` can be - a float (which then applies to each mode), or a list/array of the same - length as dims, in which each element corresponds specifies the - temperature of the corresponding mode. - - Returns - ------- - dm : Qobj - Thermal state density matrix. - """ - nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) - - if not isinstance(n, (list, np.ndarray)): - n = np.ones(len(dims)) * n - else: - n = np.asarray(n) - - diags = [np.prod((n / (n + 1)) ** np.array(state)) - for idx, state in idx2state.items()] - diags /= np.sum(diags) - data = sp.spdiags(diags, 0, nstates, nstates, format='csr') - - return Qobj(data, dims=[dims, dims]) - - -def phase_basis(N, m, phi0=0): - """ - Basis vector for the mth phase of the Pegg-Barnett phase operator. - - Parameters - ---------- - N : int - Number of basis vectors in Hilbert space. - m : int - Integer corresponding to the mth discrete phase phi_m=phi0+2*pi*m/N - phi0 : float (default=0) - Reference phase angle. - - Returns - ------- - state : qobj - Ket vector for mth Pegg-Barnett phase operator basis state. - - Notes - ----- - The Pegg-Barnett basis states form a complete set over the truncated - Hilbert space. - - """ - phim = phi0 + (2.0 * np.pi * m) / N - n = np.arange(N).reshape((N, 1)) - data = 1.0 / np.sqrt(N) * np.exp(1.0j * n * phim) - return Qobj(data) - - def zero_ket(N, dims=None): """ Creates the zero ket vector with shape Nx1 and @@ -1038,190 +509,3 @@ def zero_ket(N, dims=None): """ return Qobj(sp.csr_matrix((N, 1), dtype=complex), dims=dims) - - -def spin_state(j, m, type='ket'): - """Generates the spin state |j, m>, i.e. the eigenstate - of the spin-j Sz operator with eigenvalue m. - - Parameters - ---------- - j : float - The spin of the state (). - - m : int - Eigenvalue of the spin-j Sz operator. - - type : string {'ket', 'bra', 'dm'} - Type of state to generate. - - Returns - ------- - state : qobj - Qobj quantum object for spin state - - """ - J = 2 * j + 1 - - if type == 'ket': - return basis(int(J), int(j - m)) - elif type == 'bra': - return basis(int(J), int(j - m)).dag() - elif type == 'dm': - return fock_dm(int(J), int(j - m)) - else: - raise ValueError("invalid value keyword argument 'type'") - - -def spin_coherent(j, theta, phi, type='ket'): - """Generate the coherent spin state |theta, phi>. - - Parameters - ---------- - j : float - The spin of the state. - - theta : float - Angle from z axis. - - phi : float - Angle from x axis. - - type : string {'ket', 'bra', 'dm'} - Type of state to generate. - - Returns - ------- - state : qobj - Qobj quantum object for spin coherent state - - """ - Sp = jmat(j, '+') - Sm = jmat(j, '-') - psi = (0.5 * theta * np.exp(1j * phi) * Sm - - 0.5 * theta * np.exp(-1j * phi) * Sp).expm() * spin_state(j, j) - - if type == 'ket': - return psi - elif type == 'bra': - return psi.dag() - elif type == 'dm': - return ket2dm(psi) - else: - raise ValueError("invalid value keyword argument 'type'") - - -def bell_state(state='00'): - """ - Returns the Bell state: - - |B00> = 1 / sqrt(2)*[|0>|0>+|1>|1>] - |B01> = 1 / sqrt(2)*[|0>|0>-|1>|1>] - |B10> = 1 / sqrt(2)*[|0>|1>+|1>|0>] - |B11> = 1 / sqrt(2)*[|0>|1>-|1>|0>] - - Returns - ------- - Bell_state : qobj - Bell state - - """ - if state == '00': - Bell_state = tensor( - basis(2), basis(2))+tensor(basis(2, 1), basis(2, 1)) - elif state == '01': - Bell_state = tensor( - basis(2), basis(2))-tensor(basis(2, 1), basis(2, 1)) - elif state == '10': - Bell_state = tensor( - basis(2), basis(2, 1))+tensor(basis(2, 1), basis(2)) - elif state == '11': - Bell_state = tensor( - basis(2), basis(2, 1))-tensor(basis(2, 1), basis(2)) - - return Bell_state.unit() - - -def singlet_state(): - """ - Returns the two particle singlet-state: - - |S>=1/sqrt(2)*[|0>|1>-|1>|0>] - - that is identical to the fourth bell state. - - Returns - ------- - Bell_state : qobj - |B11> Bell state - - """ - return bell_state('11') - - -def triplet_states(): - """ - Returns the two particle triplet-states: - - |T>= |1>|1> - = 1 / sqrt(2)*[|0>|1>-|1>|0>] - = |0>|0> - that is identical to the fourth bell state. - - Returns - ------- - trip_states : list - 2 particle triplet states - - """ - trip_states = [] - trip_states.append(tensor(basis(2, 1), basis(2, 1))) - trip_states.append( - (tensor(basis(2), basis(2, 1)) + tensor(basis(2, 1), basis(2))).unit() - ) - trip_states.append(tensor(basis(2), basis(2))) - return trip_states - - -def w_state(N=3): - """ - Returns the N-qubit W-state. - - Parameters - ---------- - N : int (default=3) - Number of qubits in state - - Returns - ------- - W : qobj - N-qubit W-state - - """ - inds = np.zeros(N, dtype=int) - inds[0] = 1 - state = tensor([basis(2, x) for x in inds]) - for kk in range(1, N): - perm_inds = np.roll(inds, kk) - state += tensor([basis(2, x) for x in perm_inds]) - return state.unit() - - -def ghz_state(N=3): - """ - Returns the N-qubit GHZ-state. - - Parameters - ---------- - N : int (default=3) - Number of qubits in state - - Returns - ------- - G : qobj - N-qubit GHZ-state - - """ - state = (tensor([basis(2) for k in range(N)]) + - tensor([basis(2, 1) for k in range(N)])) - return state/np.sqrt(2) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py b/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py new file mode 100755 index 0000000000..f7ab8ca771 --- /dev/null +++ b/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py @@ -0,0 +1,575 @@ +# -*- coding: utf-8 -*- +# This file is part of QuTiP: Quantum Toolbox in Python. +# +# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names +# of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +############################################################################### +# +# This module was initially contributed by Ben Criger. +# +""" +This module implements transformations between superoperator representations, +including supermatrix, Kraus, Choi and Chi (process) matrix formalisms. +""" + +__all__ = ['super_to_choi', 'choi_to_super', 'choi_to_kraus', 'kraus_to_choi', + 'kraus_to_super', 'choi_to_chi', 'chi_to_choi', 'to_choi', + 'to_chi', 'to_super', 'to_kraus', 'to_stinespring' + ] + +# Python Standard Library +from itertools import starmap, product + +# NumPy/SciPy +from numpy.core.multiarray import array, zeros +from numpy.core.shape_base import hstack +from numpy.matrixlib.defmatrix import matrix +from numpy import sqrt, floor, log2 +from numpy import dot +from scipy.linalg import eig, svd +# Needed to avoid conflict with itertools.product. +import numpy as np + +# Other QuTiP functions and classes +from .superoperator import vec2mat, spre, spost, operator_to_vector +from .operators import identity, sigmax, sigmay, sigmaz +from .tensor import tensor, flatten +from .qobj import Qobj +from .states import basis + + +# SPECIFIC SUPEROPERATORS ----------------------------------------------------- + +def _dep_super(pe): + """ + Returns the superoperator corresponding to qubit depolarization for a + given parameter pe. + + TODO: if this is going into production (hopefully it isn't) then check + CPTP, expand to arbitrary dimensional systems, etc. + """ + return Qobj(dims=[[[2], [2]], [[2], [2]]], + inpt=array([[1. - pe / 2., 0., 0., pe / 2.], + [0., 1. - pe, 0., 0.], + [0., 0., 1. - pe, 0.], + [pe / 2., 0., 0., 1. - pe / 2.]])) + + +def _dep_choi(pe): + """ + Returns the choi matrix corresponding to qubit depolarization for a + given parameter pe. + + TODO: if this is going into production (hopefully it isn't) then check + CPTP, expand to arbitrary dimensional systems, etc. + """ + return Qobj(dims=[[[2], [2]], [[2], [2]]], + inpt=array([[1. - pe / 2., 0., 0., 1. - pe], + [0., pe / 2., 0., 0.], + [0., 0., pe / 2., 0.], + [1. - pe, 0., 0., 1. - pe / 2.]]), + superrep='choi') + + +# CHANGE OF BASIS FUNCTIONS --------------------------------------------------- +# These functions find change of basis matrices, and are useful in converting +# between (for instance) Choi and chi matrices. At some point, these should +# probably be moved out to another module. + +_SINGLE_QUBIT_PAULI_BASIS = (identity(2), sigmax(), sigmay(), sigmaz()) + + +def _pauli_basis(nq=1): + # NOTE: This is slow as can be. + # TODO: Make this sparse. CSR format was causing problems for the [idx, :] + # slicing below. + B = zeros((4 ** nq, 4 ** nq), dtype=complex) + dims = [[[2] * nq] * 2] * 2 + + for idx, op in enumerate(starmap(tensor, + product(_SINGLE_QUBIT_PAULI_BASIS, + repeat=nq))): + B[:, idx] = operator_to_vector(op).dag().data.todense() + + return Qobj(B, dims=dims) + + +# PRIVATE CONVERSION FUNCTIONS ------------------------------------------------ +# These functions handle the main work of converting between representations, +# and are exposed below by other functions that add postconditions about types. +# +# TODO: handle type='kraus' as a three-index Qobj, rather than as a list? + +def _super_tofrom_choi(q_oper): + """ + We exploit that the basis transformation between Choi and supermatrix + representations squares to the identity, so that if we munge Qobj.type, + we can use the same function. + + Since this function doesn't respect :attr:`Qobj.type`, we mark it as + private; only those functions which wrap this in a way so as to preserve + type should be called externally. + """ + data = q_oper.data.toarray() + sqrt_shape = int(sqrt(data.shape[0])) + return Qobj(dims=q_oper.dims, + inpt=data.reshape([sqrt_shape] * 4). + transpose(3, 1, 2, 0).reshape(q_oper.data.shape)) + +def _isqubitdims(dims): + """Checks whether all entries in a dims list are integer powers of 2. + + Parameters + ---------- + dims : nested list of ints + Dimensions to be checked. + + Returns + ------- + isqubitdims : bool + True if and only if every member of the flattened dims + list is an integer power of 2. + """ + return all([ + 2**floor(log2(dim)) == dim + for dim in flatten(dims) + ]) + + +def _super_to_superpauli(q_oper): + """ + Converts a superoperator in the column-stacking basis to + the Pauli basis (assuming qubit dimensions). + + This is an internal function, as QuTiP does not currently have + a way to mark that superoperators are represented in the Pauli + basis as opposed to the column-stacking basis; a Pauli-basis + ``type='super'`` would thus break other conversion functions. + """ + # Ensure we start with a column-stacking-basis superoperator. + sqobj = to_super(q_oper) + if not _isqubitdims(sqobj.dims): + raise ValueError("Pauli basis is only defined for qubits.") + nq = int(log2(sqobj.shape[0]) / 2) + B = _pauli_basis(nq) / sqrt(2**nq) + # To do this, we have to hack a bit and force the dims to match, + # since the _pauli_basis function makes different assumptions + # about indices than we need here. + B.dims = sqobj.dims + return (B.dag() * sqobj * B) + + +def super_to_choi(q_oper): + # TODO: deprecate and make private in favor of to_choi, + # which looks at Qobj.type to determine the right conversion function. + """ + Takes a superoperator to a Choi matrix + TODO: Sanitize input, incorporate as method on Qobj if type=='super' + """ + q_oper = _super_tofrom_choi(q_oper) + q_oper.superrep = 'choi' + return q_oper + + +def choi_to_super(q_oper): + # TODO: deprecate and make private in favor of to_super, + # which looks at Qobj.type to determine the right conversion function. + """ + Takes a Choi matrix to a superoperator + TODO: Sanitize input, Abstract-ify application of channels to states + """ + q_oper = super_to_choi(q_oper) + q_oper.superrep = 'super' + return q_oper + + +def choi_to_kraus(q_oper): + """ + Takes a Choi matrix and returns a list of Kraus operators. + TODO: Create a new class structure for quantum channels, perhaps as a + strict sub-class of Qobj. + """ + vals, vecs = eig(q_oper.data.todense()) + vecs = [array(_) for _ in zip(*vecs)] + return [Qobj(inpt=sqrt(val)*vec2mat(vec)) for val, vec in zip(vals, vecs)] + + +def kraus_to_choi(kraus_list): + """ + Takes a list of Kraus operators and returns the Choi matrix for the channel + represented by the Kraus operators in `kraus_list` + """ + kraus_mat_list = list(map(lambda x: matrix(x.data.todense()), kraus_list)) + op_len = len(kraus_mat_list[0]) + op_rng = range(op_len) + choi_blocks = array([[sum([op[:, c_ix] * array([op.H[r_ix, :]]) + for op in kraus_mat_list]) + for r_ix in op_rng] + for c_ix in op_rng]) + return Qobj(inpt=hstack(hstack(choi_blocks)), + dims=[kraus_list[0].dims, kraus_list[0].dims], type='super', + superrep='choi') + + +def kraus_to_super(kraus_list): + """ + Converts a list of Kraus operators and returns a super operator. + """ + return choi_to_super(kraus_to_choi(kraus_list)) + + +def _nq(dims): + dim = np.product(dims[0][0]) + nq = int(log2(dim)) + if 2 ** nq != dim: + raise ValueError("{} is not an integer power of 2.".format(dim)) + return nq + + +def choi_to_chi(q_oper): + """ + Converts a Choi matrix to a Chi matrix in the Pauli basis. + + NOTE: this is only supported for qubits right now. Need to extend to + Heisenberg-Weyl for other subsystem dimensions. + """ + nq = _nq(q_oper.dims) + B = _pauli_basis(nq) + # Force the basis change to match the dimensions of + # the input. + B.dims = q_oper.dims + B.superrep = 'choi' + + return Qobj(B.dag() * q_oper * B, superrep='chi') + + +def chi_to_choi(q_oper): + """ + Converts a Choi matrix to a Chi matrix in the Pauli basis. + + NOTE: this is only supported for qubits right now. Need to extend to + Heisenberg-Weyl for other subsystem dimensions. + """ + nq = _nq(q_oper.dims) + B = _pauli_basis(nq) + # Force the basis change to match the dimensions of + # the input. + B.dims = q_oper.dims + + # We normally should not multiply objects of different + # superreps, so Qobj warns about that. Here, however, we're actively + # converting between, so the superrep of B is irrelevant. + # To suppress warnings, we pretend that B is also a chi. + B.superrep = 'chi' + + # The Chi matrix has tr(chi) == d², so we need to divide out + # by that to get back to the Choi form. + return Qobj((B * q_oper * B.dag()) / q_oper.shape[0], superrep='choi') + +def _svd_u_to_kraus(U, S, d, dK, indims, outdims): + """ + Given a partial isometry U and a vector of square-roots of singular values S + obtained from an SVD, produces the Kraus operators represented by U. + + Returns + ------- + Ks : list of Qobj + Quantum objects represnting each of the Kraus operators. + """ + # We use U * S since S is 1-index, such that this is equivalent to + # U . diag(S), but easier to write down. + Ks = list(map(Qobj, array(U * S).reshape((d, d, dK), order='F').transpose((2, 0, 1)))) + for K in Ks: + K.dims = [outdims, indims] + return Ks + + +def _generalized_kraus(q_oper, thresh=1e-10): + # TODO: document! + # TODO: use this to generalize to_kraus to the case where U != V. + # This is critical for non-CP maps, as appear in (for example) + # diamond norm differences between two CP maps. + if q_oper.type != "super" or q_oper.superrep != "choi": + raise ValueError("Expected a Choi matrix, got a {} (superrep {}).".format(q_oper.type, q_oper.superrep)) + + # Remember the shape of the underlying space, + # as we'll need this to make Kraus operators later. + dL, dR = map(int, map(sqrt, q_oper.shape)) + # Also remember the dims breakout. + out_dims, in_dims = q_oper.dims + out_left, out_right = out_dims + in_left, in_right = in_dims + + # Find the SVD. + U, S, V = svd(q_oper.data.todense()) + + # Truncate away the zero singular values, up to a threshold. + nonzero_idxs = S > thresh + dK = nonzero_idxs.sum() + U = array(U)[:, nonzero_idxs] + # We also want S to be a single index array, which np.matrix + # doesn't allow for. This is stripped by calling array() on it. + S = sqrt(array(S)[nonzero_idxs]) + # Since NumPy returns V and not V+, we need to take the dagger + # to get back to quantum info notation for Stinespring pairs. + V = array(V.conj().T)[:, nonzero_idxs] + + # Next, we convert each of U and V into Kraus operators. + # Finally, we want the Kraus index to be left-most so that we + # can map over it when making Qobjs. + # FIXME: does not preserve dims! + kU = _svd_u_to_kraus(U, S, dL, dK, out_right, out_left) + kV = _svd_u_to_kraus(V, S, dL, dK, in_right, in_left) + + return kU, kV + + +def choi_to_stinespring(q_oper, thresh=1e-10): + # TODO: document! + kU, kV = _generalized_kraus(q_oper, thresh=thresh) + + assert(len(kU) == len(kV)) + dK = len(kU) + dL = kU[0].shape[0] + dR = kV[0].shape[1] + # Also remember the dims breakout. + out_dims, in_dims = q_oper.dims + out_left, out_right = out_dims + in_left, in_right = in_dims + + A = Qobj(zeros((dK * dL, dL)), dims=[out_left + [dK], out_right + [1]]) + B = Qobj(zeros((dK * dR, dR)), dims=[in_left + [dK], in_right + [1]]) + + for idx_kraus, (KL, KR) in enumerate(zip(kU, kV)): + A += tensor(KL, basis(dK, idx_kraus)) + B += tensor(KR, basis(dK, idx_kraus)) + + # There is no input (right) Kraus index, so strip that off. + del A.dims[1][-1] + del B.dims[1][-1] + + return A, B + +# PUBLIC CONVERSION FUNCTIONS ------------------------------------------------- +# These functions handle superoperator conversions in a way that preserves the +# correctness of Qobj.type, and in a way that automatically branches based on +# the input Qobj.type. + +def to_choi(q_oper): + """ + Converts a Qobj representing a quantum map to the Choi representation, + such that the trace of the returned operator is equal to the dimension + of the system. + + Parameters + ---------- + q_oper : Qobj + Superoperator to be converted to Choi representation. If + ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, + such that ``to_choi(A) == to_choi(sprepost(A, A.dag()))``. + + Returns + ------- + choi : Qobj + A quantum object representing the same map as ``q_oper``, such that + ``choi.superrep == "choi"``. + + Raises + ------ + TypeError: if the given quantum object is not a map, or cannot be converted + to Choi representation. + """ + if q_oper.type == 'super': + if q_oper.superrep == 'choi': + return q_oper + if q_oper.superrep == 'super': + return super_to_choi(q_oper) + if q_oper.superrep == 'chi': + return chi_to_choi(q_oper) + else: + raise TypeError(q_oper.superrep) + elif q_oper.type == 'oper': + return super_to_choi(spre(q_oper) * spost(q_oper.dag())) + else: + raise TypeError( + "Conversion of Qobj with type = {0.type} " + "and superrep = {0.choi} to Choi not supported.".format(q_oper) + ) + + +def to_chi(q_oper): + """ + Converts a Qobj representing a quantum map to a representation as a chi + (process) matrix in the Pauli basis, such that the trace of the returned + operator is equal to the dimension of the system. + + Parameters + ---------- + q_oper : Qobj + Superoperator to be converted to Chi representation. If + ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, + such that ``to_chi(A) == to_chi(sprepost(A, A.dag()))``. + + Returns + ------- + chi : Qobj + A quantum object representing the same map as ``q_oper``, such that + ``chi.superrep == "chi"``. + + Raises + ------ + TypeError: if the given quantum object is not a map, or cannot be converted + to Chi representation. + """ + if q_oper.type == 'super': + # Case 1: Already done. + if q_oper.superrep == 'chi': + return q_oper + # Case 2: Can directly convert. + elif q_oper.superrep == 'choi': + return choi_to_chi(q_oper) + # Case 3: Need to go through Choi. + elif q_oper.superrep == 'super': + return to_chi(to_choi(q_oper)) + else: + raise TypeError(q_oper.superrep) + elif q_oper.type == 'oper': + return to_chi(spre(q_oper) * spost(q_oper.dag())) + else: + raise TypeError( + "Conversion of Qobj with type = {0.type} " + "and superrep = {0.choi} to Choi not supported.".format(q_oper) + ) + + +def to_super(q_oper): + """ + Converts a Qobj representing a quantum map to the supermatrix (Liouville) + representation. + + Parameters + ---------- + q_oper : Qobj + Superoperator to be converted to supermatrix representation. If + ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, + such that ``to_super(A) == sprepost(A, A.dag())``. + + Returns + ------- + superop : Qobj + A quantum object representing the same map as ``q_oper``, such that + ``superop.superrep == "super"``. + + Raises + ------ + TypeError + If the given quantum object is not a map, or cannot be converted + to supermatrix representation. + """ + if q_oper.type == 'super': + # Case 1: Already done. + if q_oper.superrep == "super": + return q_oper + # Case 2: Can directly convert. + elif q_oper.superrep == 'choi': + return choi_to_super(q_oper) + # Case 3: Need to go through Choi. + elif q_oper.superrep == 'chi': + return to_super(to_choi(q_oper)) + # Case 4: Something went wrong. + else: + raise ValueError( + "Unrecognized superrep '{}'.".format(q_oper.superrep)) + elif q_oper.type == 'oper': # Assume unitary + return spre(q_oper) * spost(q_oper.dag()) + else: + raise TypeError( + "Conversion of Qobj with type = {0.type} " + "and superrep = {0.superrep} to supermatrix not " + "supported.".format(q_oper) + ) + + +def to_kraus(q_oper): + """ + Converts a Qobj representing a quantum map to a list of quantum objects, + each representing an operator in the Kraus decomposition of the given map. + + Parameters + ---------- + q_oper : Qobj + Superoperator to be converted to Kraus representation. If + ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, + such that ``to_kraus(A) == to_kraus(sprepost(A, A.dag())) == [A]``. + + Returns + ------- + kraus_ops : list of Qobj + A list of quantum objects, each representing a Kraus operator in the + decomposition of ``q_oper``. + + Raises + ------ + TypeError: if the given quantum object is not a map, or cannot be + decomposed into Kraus operators. + """ + if q_oper.type == 'super': + if q_oper.superrep in ("super", "chi"): + return to_kraus(to_choi(q_oper)) + elif q_oper.superrep == 'choi': + return choi_to_kraus(q_oper) + elif q_oper.type == 'oper': # Assume unitary + return [q_oper] + else: + raise TypeError( + "Conversion of Qobj with type = {0.type} " + "and superrep = {0.superrep} to Kraus decomposition not " + "supported.".format(q_oper) + ) + +def to_stinespring(q_oper): + r""" + Converts a Qobj representing a quantum map $\Lambda$ to a pair of partial isometries + $A$ and $B$ such that $\Lambda(X) = \Tr_2(A X B^\dagger)$ for all inputs $X$, where + the partial trace is taken over a a new index on the output dimensions of $A$ and $B$. + + For completely positive inputs, $A$ will always equal $B$ up to precision errors. + + Parameters + ---------- + q_oper : Qobj + Superoperator to be converted to a Stinespring pair. + + Returns + ------- + A, B : Qobj + Quantum objects representing each of the Stinespring matrices for the input Qobj. + """ + return choi_to_stinespring(to_choi(q_oper)) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py index 12f4a707ac..78c5735551 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -51,11 +51,11 @@ import scipy.sparse as sp import numpy as np -from qutip.qobj import Qobj -from qutip.fastsparse import fast_csr_matrix, fast_identity -from qutip.sparse import sp_reshape -from qutip.cy.spmath import zcsr_kron from functools import partial +from .qobj import Qobj +from .fastsparse import fast_csr_matrix, fast_identity +from .sparse import sp_reshape +from .cy.spmath import zcsr_kron def liouvillian(H, c_ops=[], data_only=False, chi=None): @@ -78,17 +78,14 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): Liouvillian superoperator. """ - if isinstance(c_ops, (Qobj, QobjEvo)): + if isinstance(c_ops, (Qobj)): c_ops = [c_ops] if chi and len(chi) != len(c_ops): raise ValueError('chi must be a list with same length as c_ops') h = None if H is not None: - if isinstance(H, QobjEvo): - h = H.cte - else: - h = H + h = H if h.isoper: op_dims = h.dims op_shape = h.shape @@ -100,10 +97,7 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): else: # no hamiltonian given, pick system size from a collapse operator if isinstance(c_ops, list) and len(c_ops) > 0: - if isinstance(c_ops[0], QobjEvo): - c = c_ops[0].cte - else: - c = c_ops[0] + c = c_ops[0] if c.isoper: op_dims = c.dims op_shape = c.shape @@ -122,18 +116,7 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): td = False L = None - if isinstance(H, QobjEvo): - td = True - - def H2L(H): - if H.isoper: - return -1.0j * (spre(H) - spost(H)) - else: - return H - - L = H.apply(H2L) - data = L.cte.data - elif isinstance(H, Qobj): + if isinstance(H, Qobj): if H.isoper: Ht = H.data.T data = -1j * zcsr_kron(spI, H.data) @@ -145,18 +128,7 @@ def H2L(H): td_c_ops = [] for idx, c_op in enumerate(c_ops): - if isinstance(c_op, QobjEvo): - td = True - if c_op.const: - c_ = c_op.cte - elif chi: - td_c_ops.append(lindblad_dissipator(c_op, chi=chi[idx])) - continue - else: - td_c_ops.append(lindblad_dissipator(c_op)) - continue - else: - c_ = c_op + c_ = c_op if c_.issuper: data = data + c_.data @@ -173,26 +145,13 @@ def H2L(H): data = data - 0.5 * zcsr_kron(spI, cdc) data = data - 0.5 * zcsr_kron(cdct, spI) - if not td: - if data_only: - return data - else: - L = Qobj() - L.dims = sop_dims - L.data = data - L.superrep = 'super' - return L + if data_only: + return data else: - if not L: - l = Qobj() - l.dims = sop_dims - l.data = data - l.superrep = 'super' - L = QobjEvo(l) - else: - L.cte.data = data - for c_op in td_c_ops: - L += c_op + L = Qobj() + L.dims = sop_dims + L.data = data + L.superrep = 'super' return L @@ -258,10 +217,8 @@ def lindblad_dissipator(a, b=None, data_only=False, chi=None): else: D = spre(a) * spost(b.dag()) - 0.5 * spre(ad_b) - 0.5 * spost(ad_b) - if isinstance(a, QobjEvo) or isinstance(b, QobjEvo): - return D - else: - return D.data if data_only else D + + return D.data if data_only else D def operator_to_vector(op): @@ -269,9 +226,6 @@ def operator_to_vector(op): Create a vector representation of a quantum operator given the matrix representation. """ - if isinstance(op, QobjEvo): - return op.apply(operator_to_vector) - q = Qobj() q.dims = [op.dims, [1]] q.data = sp_reshape(op.data.T, (np.prod(op.shape), 1)) @@ -283,9 +237,6 @@ def vector_to_operator(op): Create a matrix representation given a quantum operator in vector form. """ - if isinstance(op, QobjEvo): - return op.apply(vector_to_operator) - q = Qobj() q.dims = op.dims[0] n = int(np.sqrt(op.shape[0])) @@ -339,9 +290,6 @@ def spost(A): super : Qobj or QobjEvo Superoperator formed from input qauntum object. """ - if isinstance(A, QobjEvo): - return A.apply(spost) - if not isinstance(A, Qobj): raise TypeError('Input is not a quantum object') @@ -368,9 +316,6 @@ def spre(A): super :Qobj or QobjEvo Superoperator formed from input quantum object. """ - if isinstance(A, QobjEvo): - return A.apply(spre) - if not isinstance(A, Qobj): raise TypeError('Input is not a quantum object') @@ -408,15 +353,10 @@ def sprepost(A, B): super : Qobj or QobjEvo Superoperator formed from input quantum objects. """ - if isinstance(A, QobjEvo) or isinstance(B, QobjEvo): - return spre(A) * spost(B) + dims = [[_drop_projected_dims(A.dims[0]), + _drop_projected_dims(B.dims[1])], + [_drop_projected_dims(A.dims[1]), + _drop_projected_dims(B.dims[0])]] + data = zcsr_kron(B.data.T, A.data) + return Qobj(data, dims=dims, superrep='super') - else: - dims = [[_drop_projected_dims(A.dims[0]), - _drop_projected_dims(B.dims[1])], - [_drop_projected_dims(A.dims[1]), - _drop_projected_dims(B.dims[0])]] - data = zcsr_kron(B.data.T, A.data) - return Qobj(data, dims=dims, superrep='super') - -from qutip.qobjevo import QobjEvo diff --git a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py index 81a18e4ce6..2622c829ce 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py @@ -53,17 +53,13 @@ ] import numpy as np -from qutip.cy.spmath import zcsr_kron -from qutip.qobj import Qobj -from qutip.permute import reshuffle -from qutip.superoperator import operator_to_vector -from qutip.dimensions import ( +from .cy.spmath import zcsr_kron +from .qobj import Qobj +from .superoperator import operator_to_vector +from .dimensions import ( flatten, enumerate_flat, unflatten, deep_remove, - dims_to_tensor_shape, dims_idxs_to_tensor_idxs -) - -import qutip.settings -import qutip.superop_reps # Avoid circular dependency here. + dims_to_tensor_shape, dims_idxs_to_tensor_idxs) +from ..qutip_lite.settings import auto_tidyup def tensor(*args): @@ -135,267 +131,6 @@ def tensor(*args): if not out.isherm: out._isherm = None - return out.tidyup() if qutip.settings.auto_tidyup else out - - -def super_tensor(*args): - """Calculates the tensor product of input superoperators, by tensoring - together the underlying Hilbert spaces on which each vectorized operator - acts. - - Parameters - ---------- - args : array_like - ``list`` or ``array`` of quantum objects with ``type="super"``. - - Returns - ------- - obj : qobj - A composite quantum object. - - """ - if isinstance(args[0], list): - args = args[0] - - # Check if we're tensoring vectors or superoperators. - if all(arg.issuper for arg in args): - if not all(arg.superrep == "super" for arg in args): - raise TypeError( - "super_tensor on type='super' is only implemented for " - "superrep='super'." - ) - - # Reshuffle the superoperators. - shuffled_ops = list(map(reshuffle, args)) - - # Tensor the result. - shuffled_tensor = tensor(shuffled_ops) - - # Unshuffle and return. - out = reshuffle(shuffled_tensor) - out.superrep = args[0].superrep - return out - - elif all(arg.isoperket for arg in args): - - # Reshuffle the superoperators. - shuffled_ops = list(map(reshuffle, args)) - - # Tensor the result. - shuffled_tensor = tensor(shuffled_ops) - - # Unshuffle and return. - out = reshuffle(shuffled_tensor) - return out - - elif all(arg.isoperbra for arg in args): - return super_tensor(*(arg.dag() for arg in args)).dag() - - else: - raise TypeError( - "All arguments must be the same type, " - "either super, operator-ket or operator-bra." - ) - - -def _isoperlike(q): - return q.isoper or q.issuper - - -def _isketlike(q): - return q.isket or q.isoperket - - -def _isbralike(q): - return q.isbra or q.isoperbra - - -def composite(*args): - """ - Given two or more operators, kets or bras, returns the Qobj - corresponding to a composite system over each argument. - For ordinary operators and vectors, this is the tensor product, - while for superoperators and vectorized operators, this is - the column-reshuffled tensor product. - - If a mix of Qobjs supported on Hilbert and Liouville spaces - are passed in, the former are promoted. Ordinary operators - are assumed to be unitaries, and are promoted using ``to_super``, - while kets and bras are promoted by taking their projectors and - using ``operator_to_vector(ket2dm(arg))``. - """ - # First step will be to ensure everything is a Qobj at all. - if not all(isinstance(arg, Qobj) for arg in args): - raise TypeError("All arguments must be Qobjs.") - - # Next, figure out if we have something oper-like (isoper or issuper), - # or something ket-like (isket or isoperket). Bra-like we'll deal with - # by turning things into ket-likes and back. - if all(map(_isoperlike, args)): - # OK, we have oper/supers. - if any(arg.issuper for arg in args): - # Note that to_super does nothing to things - # that are already type=super, while it will - # promote unitaries to superunitaries. - return super_tensor(*map(qutip.superop_reps.to_super, args)) - - else: - # Everything's just an oper, so ordinary tensor products work. - return tensor(*args) - - elif all(map(_isketlike, args)): - # Ket-likes. - if any(arg.isoperket for arg in args): - # We have a vectorized operator, we we may need to promote - # something. - return super_tensor(*( - arg if arg.isoperket - else operator_to_vector(qutip.states.ket2dm(arg)) - for arg in args - )) - - else: - # Everything's ordinary, so we can use the tensor product here. - return tensor(*args) - - elif all(map(_isbralike, args)): - # Turn into ket-likes and recurse. - return composite(*(arg.dag() for arg in args)).dag() - - else: - raise TypeError("Unsupported Qobj types [{}].".format( - ", ".join(arg.type for arg in args) - )) - - -def _tensor_contract_single(arr, i, j): - """ - Contracts a dense tensor along a single index pair. - """ - if arr.shape[i] != arr.shape[j]: - raise ValueError("Cannot contract over indices of different length.") - idxs = np.arange(arr.shape[i]) - sltuple = tuple(slice(None, None, None) - if idx not in (i, j) else idxs for idx in range(arr.ndim)) - contract_at = i if j == i + 1 else 0 - return np.sum(arr[sltuple], axis=contract_at) - - -def _tensor_contract_dense(arr, *pairs): - """ - Contracts a dense tensor along one or more index pairs, - keeping track of how the indices are relabeled by the removal - of other indices. - """ - axis_idxs = list(range(arr.ndim)) - for pair in pairs: - # axis_idxs.index effectively evaluates the mapping from - # original index labels to the labels after contraction. - arr = _tensor_contract_single(arr, *map(axis_idxs.index, pair)) - list(map(axis_idxs.remove, pair)) - return arr - - -def tensor_swap(q_oper, *pairs): - """Transposes one or more pairs of indices of a Qobj. - Note that this uses dense representations and thus - should *not* be used for very large Qobjs. - - Parameters - ---------- - - pairs : tuple - One or more tuples ``(i, j)`` indicating that the - ``i`` and ``j`` dimensions of the original qobj - should be swapped. - - Returns - ------- - - sqobj : Qobj - The original Qobj with all named index pairs swapped with each other - """ - dims = q_oper.dims - tensor_pairs = dims_idxs_to_tensor_idxs(dims, pairs) - - data = q_oper.data.toarray() - - # Reshape into tensor indices - data = data.reshape(dims_to_tensor_shape(dims)) - - # Now permute the dims list so we know how to get back. - flat_dims = flatten(dims) - perm = list(range(len(flat_dims))) - for i, j in pairs: - flat_dims[i], flat_dims[j] = flat_dims[j], flat_dims[i] - for i, j in tensor_pairs: - perm[i], perm[j] = perm[j], perm[i] - dims = unflatten(flat_dims, enumerate_flat(dims)) - - # Next, permute the actual indices of the dense tensor. - data = data.transpose(perm) - - # Reshape back, using the left and right of dims. - data = data.reshape(list(map(np.prod, dims))) - - return Qobj(inpt=data, dims=dims, superrep=q_oper.superrep) - - -def tensor_contract(qobj, *pairs): - """Contracts a qobj along one or more index pairs. - Note that this uses dense representations and thus - should *not* be used for very large Qobjs. - - Parameters - ---------- - - pairs : tuple - One or more tuples ``(i, j)`` indicating that the - ``i`` and ``j`` dimensions of the original qobj - should be contracted. - - Returns - ------- - - cqobj : Qobj - The original Qobj with all named index pairs contracted - away. - - """ - # Record and label the original dims. - dims = qobj.dims - dims_idxs = enumerate_flat(dims) - tensor_dims = dims_to_tensor_shape(dims) - - # Convert to dense first, since sparse won't support the reshaping we need. - qtens = qobj.data.toarray() - - # Reshape by the flattened dims. - qtens = qtens.reshape(tensor_dims) - - # Contract out the indices from the flattened object. - # Note that we need to feed pairs through dims_idxs_to_tensor_idxs - # to ensure that we are contracting the right indices. - qtens = _tensor_contract_dense(qtens, *dims_idxs_to_tensor_idxs(dims, pairs)) - - # Remove the contracted indexes from dims so we know how to - # reshape back. - # This concerns dims, and not the tensor indices, so we need - # to make sure to use the original dims indices and not the ones - # generated by dims_to_* functions. - contracted_idxs = deep_remove(dims_idxs, *flatten(list(map(list, pairs)))) - contracted_dims = unflatten(flatten(dims), contracted_idxs) - - # We don't need to check for tensor idxs versus dims idxs here, - # as column- versus row-stacking will never move an index for the - # vectorized operator spaces all the way from the left to the right. - l_mtx_dims, r_mtx_dims = map(np.product, map(flatten, contracted_dims)) - - # Reshape back into a 2D matrix. - qmtx = qtens.reshape((l_mtx_dims, r_mtx_dims)) + return out.tidyup() if auto_tidyup else out - # Return back as a qobj. - return Qobj(qmtx, dims=contracted_dims, superrep=qobj.superrep) -# pylint: disable=wrong-import-position -import states diff --git a/qiskit/providers/aer/openpulse/setup.py b/qiskit/providers/aer/openpulse/setup.py deleted file mode 100644 index c8dfca7caf..0000000000 --- a/qiskit/providers/aer/openpulse/setup.py +++ /dev/null @@ -1,74 +0,0 @@ - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -import distutils.sysconfig -import os -import sys -import numpy as np -from setuptools import setup, Extension -from Cython.Build import cythonize -from Cython.Distutils import build_ext - -INCLUDE_DIRS = [np.get_include()] -# Add Cython OP extensions here -cython_op_exts = ['channel_value', 'measure', 'memory', 'utils'] - -# Add Cython QuTiP extensions here -cython_qutip_exts = ['graph_utils', 'math', 'ptrace', 'sparse_utils', - 'spconvert', 'spmatfuncs', 'spmath'] - -# Extra link args -_link_flags = [] -# If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) -if (sys.platform == 'win32' and int(str(sys.version_info[0])+str(sys.version_info[1])) >= 35 - and os.environ.get('MSYSTEM') is None): - _compiler_flags = [] -# Everything else -else: - _compiler_flags = ['-O2', '-funroll-loops'] - if sys.platform == 'darwin': - # These are needed for compiling on OSX 10.14+ - _compiler_flags.append('-mmacosx-version-min=10.9') - _link_flags.append('-mmacosx-version-min=10.9') - -# Remove -Wstrict-prototypes from cflags -cfg_vars = distutils.sysconfig.get_config_vars() -if "CFLAGS" in cfg_vars: - cfg_vars["CFLAGS"] = cfg_vars["CFLAGS"].replace("-Wstrict-prototypes", "") - - -EXT_MODULES = [] -# Add Cython files from cy -for ext in cython_op_exts: - _mod = Extension("cy."+ext, - sources=['cy/'+ext+'.pyx'], - include_dirs=[np.get_include()], - extra_compile_args=_compiler_flags, - extra_link_args=_link_flags, - language='c++') - EXT_MODULES.append(_mod) - -# Add Cython files from qutip_lite/cy -for ext in cython_qutip_exts: - _mod = Extension("qutip_lite.cy."+ext, - sources=['qutip_lite/cy/'+ext+'.pyx'], - include_dirs=[np.get_include()], - extra_compile_args=_compiler_flags, - extra_link_args=_link_flags, - language='c++') - EXT_MODULES.append(_mod) - - -setup(name='OpenPulse', - ext_modules=cythonize(EXT_MODULES) - ) diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index a2d632a91e..7984b9b8fe 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -19,10 +19,10 @@ import os import sys -import qutip as qt +import qiskit.providers.aer.openpulse.qutip_lite.cy as cy import qiskit.providers.aer.openpulse.solver.settings as op_set -_cython_path = os.path.abspath(qt.cy.__file__).replace('__init__.py', '') +_cython_path = os.path.abspath(cy.__file__).replace('__init__.py', '') _cython_path = _cython_path.replace("\\", "/") _include_string = "'"+_cython_path+"complex_math.pxi'" @@ -224,8 +224,8 @@ def cython_preamble(): void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) -from qutip.cy.spmatfuncs cimport spmvpy -from qutip.cy.math cimport erf +from qiskit.providers.aer.openpulse.qutip_lite.cy.spmatfuncs cimport spmvpy +from qiskit.providers.aer.openpulse.qutip_lite.cy.math cimport erf from libc.math cimport pi from qiskit.providers.aer.openpulse.cy.channel_value cimport chan_value diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index f30a384bec..36078dd918 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -22,7 +22,7 @@ import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs -from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr +from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from qiskit.providers.aer.openpulse.solver.zvode import qiskit_zvode from qiskit.providers.aer.openpulse.cy.memory import write_memory from qiskit.providers.aer.openpulse.cy.measure import (occ_probabilities, diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index 8004ddde9d..2653888467 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -20,8 +20,8 @@ from numpy.random import RandomState, randint from scipy.linalg.blas import get_blas_funcs from collections import OrderedDict -from qutip.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr -from qutip.cy.utilities import _cython_build_cleanup +from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr +from ..qutip_lite.cy.utilities import _cython_build_cleanup from ..qobj.operators import apply_projector from .codegen import OPCodegen from .rhs_utils import _op_generate_rhs, _op_func_load diff --git a/setup.py b/setup.py index 447ec236f4..8013abb911 100644 --- a/setup.py +++ b/setup.py @@ -52,12 +52,12 @@ def find_qiskit_aer_packages(): from setuptools import Extension from Cython.Build import cythonize from Cython.Distutils import build_ext # pylint: disable=unused-import + INCLUDE_DIRS = [np.get_include()] # Add Cython OP extensions here OP_EXTS = ['channel_value', 'measure', 'memory', 'utils'] - - Q_EXTS = ['spmatfuncs', 'sparse_utils', 'graph_utils', - 'spmath', 'math', 'spconvert', 'ptrace'] + # Add qutip_lite extensions here + Q_EXTS = ['spmatfuncs', 'sparse_utils', 'spmath', 'math', 'spconvert'] # Extra link args link_flags = [] From d0c044c7057dcca83635f3c3bbf67bcfe9a1e0fb Mon Sep 17 00:00:00 2001 From: David McKay Date: Wed, 7 Aug 2019 14:24:03 -0400 Subject: [PATCH 23/31] Bring simulator to be usable with terra and finalize syntax (#3) * Add readme and example * Add qubit whitelist, update example * Added a frequency to the channels * Adding frame rotation, doesn't quite work yet * Fixed bug, now works for general oscillation frequencies * Added dressed state measurement * Notebook updated with Rabi and CR * Update readme.md * Reorder estates after dressing to original ordering. Uses a very basic routine so won't work at avoided crossings * Generalize qubit operators, fix transformation error * Updated notebook with the correct sims * Fixed some path issues, replaced absolute with relative paths * Moved example, fixed error for unitary solver in opsolve * Updated to work with the pulse_simulator backend * Put header into results so that get_counts will work * Updated measurement to fix an error in the eigenstate mapper, add measurement level 1 and clean up example * Fixed hamiltonian issues with noise, fixed measurement, finalized notebook * Fixed issue with default config in pulse sim backend * Fix reference to qiskiterror * Fixed some import errors after merging with paul --- example/pulse_sim.ipynb | 870 ++++++++++++++++++ .../providers/aer/backends/pulse_simulator.py | 19 + .../aer/openpulse/cy/channel_value.pxd | 3 +- .../aer/openpulse/cy/channel_value.pyx | 20 +- qiskit/providers/aer/openpulse/cy/measure.pyx | 23 +- qiskit/providers/aer/openpulse/qobj/digest.py | 305 ++++-- .../providers/aer/openpulse/qobj/op_qobj.py | 13 +- .../providers/aer/openpulse/qobj/op_system.py | 8 +- .../providers/aer/openpulse/qobj/operators.py | 66 +- .../providers/aer/openpulse/qobj/opparse.py | 16 +- qiskit/providers/aer/openpulse/readme.md | 59 ++ .../providers/aer/openpulse/solver/codegen.py | 106 ++- .../aer/openpulse/solver/data_config.py | 27 +- .../aer/openpulse/solver/monte_carlo.py | 13 +- .../providers/aer/openpulse/solver/opsolve.py | 127 ++- .../aer/openpulse/solver/rhs_utils.py | 4 +- .../providers/aer/openpulse/solver/unitary.py | 18 +- 17 files changed, 1499 insertions(+), 198 deletions(-) create mode 100644 example/pulse_sim.ipynb create mode 100644 qiskit/providers/aer/openpulse/readme.md diff --git a/example/pulse_sim.ipynb b/example/pulse_sim.ipynb new file mode 100644 index 0000000000..bcab81db2d --- /dev/null +++ b/example/pulse_sim.ipynb @@ -0,0 +1,870 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Open Pulse Simulator - Rabi Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If running the openpulse branch from source, may need to add a symbolic link in site-packages. Also run `python setup.py build_ext --inplace` in openpulse first." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "#Import general libraries (needed for functions)\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from scipy.optimize import curve_fit\n", + "\n", + "#Import Qiskit classes classes\n", + "import qiskit\n", + "#from qiskit.providers.aer.noise import NoiseModel\n", + "#from qiskit.providers.aer.noise.errors.standard_errors import depolarizing_error, thermal_relaxation_error\n", + "\n", + "import qiskit.pulse as pulse\n", + "import qiskit.pulse.pulse_lib as pulse_lib\n", + "from qiskit.compiler import assemble\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qiskit.IBMQ.load_account()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "#Get a pulse configuration from pok\n", + "provider = qiskit.IBMQ.get_provider(hub='ibm-q-dev',group='qiskit', project='ignis')\n", + "backend_real = provider.get_backend('ibmq_poughkeepsie')\n", + "back_config = backend_real.configuration().to_dict()\n", + "system = pulse.PulseChannelSpec.from_backend(backend_real)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "#Get pulse simulator backend\n", + "backend_sim = qiskit.Aer.get_backend('pulse_simulator')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simulations\n", + "\n", + "### These need to be run in sequential order\n", + "\n", + "[Rabi Oscillation](#rabi)\n", + "Apply a pulse to Q0 and measure the population evoluation versus pulse amplitude\n", + "\n", + "[Measurement Level 1 for the Readout Test](#readout_test)\n", + "
Prepare the |0> and |1> states and look at the measurement level 1 output \n", + "\n", + "[CR Oscillation](#cr)\n", + "
Look at the Q1 State when we drive Q0 at the frequency of Q1\n", + "\n", + "[T1](#t1)\n", + "
Demonstrating noise with pulse" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Simulate a Rabi Oscillation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we are going to simulate a rabi oscillation, i.e., meausure the qubit state population versus the amplitude of a drive pulse. This is the same example that is in the tutorials." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Build Pulse Schedule " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Build on qubit 0" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "#qubit to use for exeperiment\n", + "qubit = 0\n", + "# exp configuration\n", + "exps = 41\n", + "shots = 512\n", + "\n", + "# Rabi pulse\n", + "drive_amps = np.linspace(0, 0.9, exps)\n", + "drive_samples = 128\n", + "drive_sigma = 16\n", + "\n", + "# Measurement pulse\n", + "meas_amp = 0.025\n", + "meas_samples = 1200\n", + "meas_sigma = 4\n", + "meas_risefall = 25\n", + "\n", + "# Measurement pulse (common for all experiment)\n", + "meas_pulse = pulse_lib.gaussian_square(duration=meas_samples, amp=meas_amp,\n", + " sigma=meas_sigma, risefall=meas_risefall, \n", + " name='meas_pulse')\n", + "acq_cmd=pulse.Acquire(duration=meas_samples)\n", + "\n", + "# create measurement schedule\n", + "measure_and_acquire = meas_pulse(system.qubits[qubit].measure) | acq_cmd(system.acquires, system.memoryslots)\n", + "\n", + "# Create schedule\n", + "schedules = []\n", + "for ii, drive_amp in enumerate(drive_amps):\n", + " # drive pulse\n", + " rabi_pulse = pulse_lib.gaussian(duration=drive_samples, \n", + " amp=drive_amp, \n", + " sigma=drive_sigma, name='rabi_pulse_%d' % ii)\n", + " \n", + " # add commands to schedule\n", + " schedule = pulse.Schedule(name='rabi_exp_amp_%s' % drive_amp)\n", + " \n", + " schedule += rabi_pulse(system.qubits[qubit].drive)\n", + " schedule += measure_and_acquire << schedule.duration\n", + " \n", + " schedules.append(schedule)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Design the Hamiltonian" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's build a transmon Hamiltonian with anharmonicity to test the Rabi oscillation and CR" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "hamiltonian = {}\n", + "hamiltonian['h_str'] = []\n", + "#Q0 terms\n", + "hamiltonian['h_str'].append('pi*(2*v0-alpha0)*O0')\n", + "hamiltonian['h_str'].append('pi*alpha0*O0*O0')\n", + "hamiltonian['h_str'].append('2*pi*r*X0||D0')\n", + "hamiltonian['h_str'].append('2*pi*r*X0||U0')\n", + "\n", + "#Q1 terms\n", + "hamiltonian['h_str'].append('pi*(2*v1-alpha1)*O1')\n", + "hamiltonian['h_str'].append('pi*alpha1*O1*O1')\n", + "hamiltonian['h_str'].append('X1||r*D1')\n", + "\n", + "#Exchange coupling betwene Q0 and Q1\n", + "hamiltonian['h_str'].append('2*pi*j*(Sp0*Sm1+Sm0*Sp1)')\n", + "hamiltonian['vars'] = {'v0': 5.00, 'v1': 5.1, 'j': 0.01, \n", + " 'r': 0.02, 'alpha0': -0.33, 'alpha1': -0.33}\n", + "\n", + "#set the qubit dimensions to 3\n", + "hamiltonian['qub'] = {'0': 3, '1': 3}\n", + "\n", + "#update the back_end\n", + "back_config['hamiltonian'] = hamiltonian\n", + "back_config['noise'] = {}\n", + "back_config['dt'] = 1.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Add Solver Settings" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Any solver settings also does into the back_config" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "back_config['ode_options'] = {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Restrict the Qubits Used in the Simulation " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use a qubit whitelist (`qubit_list`) to restrict the set of qubits used in the solution. The pulse simulator will appropriately alter the Hamiltonian. To start let's assume the list contains the first 2 qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "back_config['qubit_list'] = [0,1]\n", + "#back_config['qubit_list'] = None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Assemble the qobj with the backend config file and the qubit_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have to do this step twice to get the dressed frequencies for setting the LO's. Note here that we set `meas_level=1` and `meas_return=avg` which will return the average probability for the qubit to be in the |1> state." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "rabi_qobj = assemble(schedules, backend_real, \n", + " meas_level=1, meas_return='avg', \n", + " memory_slots=2,\n", + " shots=shots, sim_config = back_config)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "evals = backend_sim.get_dressed_energies(rabi_qobj)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0. , 4.9990098 , 9.66953431, 5.1009902 , 10.10132826,\n", + " 14.76614835, 9.86913744, 14.87385165, 19.54 ])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "evals/2/np.pi" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "rabi_qobj = assemble(schedules, backend_real, \n", + " meas_level=1, meas_return='avg', \n", + " memory_slots=2, qubit_lo_freq = [evals[1]/2/np.pi,\n", + " evals[3]/2/np.pi],\n", + " shots=shots, sim_config = back_config)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simulate" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "#Note: this is how to run bypassing the backend\n", + "#opsys = qiskit.providers.aer.openpulse.qobj.digest.digest_pulse_obj(rabi_qobj.to_dict())\n", + "#simdata = qiskit.providers.aer.openpulse.solver.opsolve.opsolve(opsys)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "sim_result = backend_sim.run(rabi_qobj).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract the qubit populations " + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "amp_data_Q0 = []\n", + "amp_data_Q1 = []\n", + "\n", + "for exp_idx in range(len(drive_amps)):\n", + " exp_mem = sim_result.get_memory(exp_idx)\n", + " amp_data_Q0.append(np.abs(exp_mem[0]))\n", + " amp_data_Q1.append(np.abs(exp_mem[1]))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pi Amplitude 0.314328\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "#Fit the data\n", + "fit_func = lambda x,A,B,T,phi: (A*np.cos(2*np.pi*x/T+phi)+B)\n", + "fitparams, conv = curve_fit(fit_func, drive_amps, amp_data_Q0, [0.5,0.5,0.6,1.5])\n", + "\n", + "#get the pi amplitude\n", + "pi_amp = (fitparams[3])*fitparams[2]/2/np.pi\n", + "\n", + "plt.plot(drive_amps, amp_data_Q0, label='Q0')\n", + "plt.plot(drive_amps, amp_data_Q1, label='Q1')\n", + "plt.plot(drive_amps, fit_func(drive_amps, *fitparams), color='black', linestyle='dashed', label='Fit')\n", + "plt.axvline(pi_amp, color='black', linestyle='dashed')\n", + "plt.legend()\n", + "plt.xlabel('Pulse amplitude, a.u.', fontsize=20)\n", + "plt.ylabel('Signal, a.u.', fontsize=20)\n", + "plt.title('Rabi on Q0', fontsize=20)\n", + "plt.grid(True)\n", + "\n", + "print('Pi Amplitude %f'%(pi_amp))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Look at the Shots Distribution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the pulse amplitude calibrated above, do an experiment with no pulse and an experiment with a pi pulse and look at the measurement outcomes. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "# Create schedule\n", + "pi_pulse = pulse_lib.gaussian(duration=drive_samples,\n", + " amp=pi_amp, \n", + " sigma=drive_sigma,\n", + " name='pi_pulse')\n", + "\n", + "pi_2_pulse = pulse_lib.gaussian(duration=drive_samples,\n", + " amp=pi_amp/2, \n", + " sigma=drive_sigma,\n", + " name='pi_2_pulse')\n", + " \n", + "# excited\n", + "excited_exp = pulse.Schedule(name='pi_exp')\n", + "excited_exp += pi_pulse(system.qubits[qubit].drive)\n", + "measure_time = excited_exp.duration\n", + "excited_exp |= measure_and_acquire << measure_time\n", + "\n", + "# superposition\n", + "sup_exp = pulse.Schedule(name='pi_2_exp')\n", + "sup_exp += pi_2_pulse(system.qubits[qubit].drive)\n", + "measure_time = sup_exp.duration\n", + "sup_exp |= measure_and_acquire << measure_time\n", + "\n", + "# ground\n", + "ground_exp = pulse.Schedule(name='no_pi_exp')\n", + "ground_exp |= pulse.Schedule(name='ground_exp')\n", + "ground_exp |= measure_and_acquire << measure_time \n", + "\n", + "excited_exp_schedules = [ground_exp, sup_exp, excited_exp]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Change the `meas_return=single` which will return each individual measurement" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "readout_qobj = assemble(excited_exp_schedules, backend_real, \n", + " meas_level=1, meas_return='single', \n", + " memory_slots=2, qubit_lo_freq = [evals[1]/2/np.pi,\n", + " evals[3]/2/np.pi],\n", + " shots=shots, sim_config = back_config)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "sim_result = backend_sim.run(readout_qobj).result()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot the data, there is no measurement error in the simulator data so the histographs will be all centered at the average point." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "ground_data = sim_result.get_memory(0)[:, qubit]\n", + "excited_data = sim_result.get_memory(2)[:, qubit]\n", + "sup_data = sim_result.get_memory(1)[:, qubit]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add some random noise to the data to better approximate the experiment" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "for idx in range(len(ground_data)):\n", + " ground_data[idx] += random.gauss(0,0.1)+1j*random.gauss(0,0.1)\n", + " excited_data[idx] += random.gauss(0,0.1)+1j*random.gauss(0,0.1)\n", + " sup_data[idx] += random.gauss(0,0.1)+1j*random.gauss(0,0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0, 0.5, 'Q (a.u.)')" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "plt.scatter(np.real(ground_data), np.imag(ground_data), s=5, cmap='viridis',c='blue',alpha=0.5, label='|0>')\n", + "plt.scatter(np.real(excited_data), np.imag(excited_data), s=5, cmap='viridis',c='green',alpha=0.5, label='|1>')\n", + "plt.scatter(np.real(sup_data), np.imag(sup_data), s=5, cmap='viridis',c='red',alpha=0.5, label='|0>+|1>')\n", + "ground_center = np.mean(ground_data)\n", + "excited_center = np.mean(excited_data)\n", + "sup_center = np.mean(sup_data)\n", + "\n", + "plt.scatter(np.real(ground_center), np.imag(ground_center), c='blue', s=200, alpha=1.0)\n", + "plt.scatter(np.real(excited_center), np.imag(excited_center), c='green', s=200, alpha=1.0)\n", + "plt.scatter(np.real(sup_center), np.imag(sup_center), c='red', s=200, alpha=1.0)\n", + "\n", + "plt.title('Kernel integrated I-Q scatter plot', fontsize=20)\n", + "plt.legend(loc='upper right')\n", + "plt.xlabel('I (a.u.)', fontsize=16)\n", + "plt.ylabel('Q (a.u.)', fontsize=16)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Cross-Resonance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simulate cross-resonance by driving on U0. Note you need to run Rabi first to setup the hamiltonian." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "#qubit to use for exeperiment\n", + "qubit = 0\n", + "# exp configuration\n", + "exps = 41\n", + "shots = 512\n", + "\n", + "# Rabi pulse\n", + "cr_drive_amps = np.linspace(0, 0.9, exps)\n", + "cr_drive_samples = 128*3\n", + "cr_drive_sigma = 4\n", + "\n", + "\n", + "# Create schedule\n", + "schedules = []\n", + "for ii, cr_drive_amp in enumerate(cr_drive_amps):\n", + " # drive pulse\n", + " cr_rabi_pulse = pulse_lib.gaussian_square(duration=cr_drive_samples, \n", + " amp=cr_drive_amp, \n", + " risefall=cr_drive_sigma*4,\n", + " sigma=cr_drive_sigma, name='rabi_pulse_%d' % ii)\n", + " \n", + " # add commands to schedule\n", + " schedule = pulse.Schedule(name='cr_rabi_exp_amp_%s' % cr_drive_amp)\n", + " \n", + " schedule += cr_rabi_pulse(system.controls[0])\n", + " schedule += measure_and_acquire << schedule.duration\n", + " \n", + " schedules.append(schedule)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "cr_rabi_qobj = assemble(schedules, backend_real, \n", + " meas_level=1, meas_return='avg', \n", + " memory_slots=2, qubit_lo_freq = [evals[1]/2/np.pi,\n", + " evals[3]/2/np.pi],\n", + " shots=shots, sim_config = back_config)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "sim_result = backend_sim.run(cr_rabi_qobj).result()" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "amp_data_Q0 = []\n", + "amp_data_Q1 = []\n", + "\n", + "for exp_idx in range(len(cr_drive_amps)):\n", + " exp_mem = sim_result.get_memory(exp_idx)\n", + " amp_data_Q0.append(np.abs(exp_mem[0]))\n", + " amp_data_Q1.append(np.abs(exp_mem[1]))" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(drive_amps, amp_data_Q0, label='Q0')\n", + "plt.plot(drive_amps, amp_data_Q1, label='Q1')\n", + "plt.legend()\n", + "plt.xlabel('Pulse amplitude, a.u.', fontsize=20)\n", + "plt.ylabel('Signal, a.u.', fontsize=20)\n", + "plt.title('CR (Target Q1, driving on Q0)', fontsize=20)\n", + "plt.grid(True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# T1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the calibrated Pi pulse add a T1 decay channel and simulate a t1 experiment. This can take a while to run. The noise operators in pulse are still a work in progress." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "t1_times = np.linspace(0,500,10)\n", + "t1_times = np.array(t1_times, dtype=int)\n", + "\n", + "T1_exps = []\n", + "for kk in range(len(t1_times)):\n", + " schedule = pulse.Schedule(name='T1_{}'.format(kk))\n", + " schedule |= pi_pulse(system.qubits[qubit].drive) << schedule.duration\n", + " schedule |= measure_and_acquire << int(t1_times[kk]) + schedule.duration\n", + " T1_exps.append(schedule)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [], + "source": [ + "# Add noise to the Hamiltonian on qubit '0'\n", + "back_config['noise'] = {\"qubit\": \n", + " {\"0\": \n", + " {\"Sm\": 0.006\n", + " }}}\n", + "#back_config['noise'] = {}" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [], + "source": [ + "t1_qobj = assemble(T1_exps, backend_real, \n", + " meas_level=1, meas_return='avg', \n", + " memory_slots=2, qubit_lo_freq = [evals[1]/2/np.pi,\n", + " evals[3]/2/np.pi],\n", + " shots=100, sim_config = back_config)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "sim_result_t1 = backend_sim.run(t1_qobj).result()" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "t1_data_Q0 = []\n", + "t1_data_Q1 = []\n", + "\n", + "\n", + "for exp_idx in range(len(t1_times)):\n", + " exp_mem = sim_result_t1.get_memory(exp_idx)\n", + " t1_data_Q0.append(np.abs(exp_mem[0]))\n", + " t1_data_Q1.append(np.abs(exp_mem[1]))\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "#Fit the data\n", + "fit_func_t1 = lambda x,A,B,T: (A*np.exp(-x/T)+B)\n", + "fitparams, conv = curve_fit(fit_func_t1, t1_times, t1_data_Q0, [0.5,0.5,100])\n", + "\n", + "\n", + "plt.plot(t1_times, t1_data_Q0, label='Q0')\n", + "plt.plot(t1_times, t1_data_Q1, label='Q1')\n", + "plt.plot(t1_times, fit_func_t1(t1_times, *fitparams), color='black', linestyle='dashed', label='Fit')\n", + "\n", + "plt.legend()\n", + "plt.xlabel('Wait Time (dt)', fontsize=20)\n", + "plt.ylabel('Signal, a.u.', fontsize=20)\n", + "plt.ylim([0,1.05])\n", + "plt.title('T1 on Q0', fontsize=20)\n", + "plt.grid(True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/qiskit/providers/aer/backends/pulse_simulator.py b/qiskit/providers/aer/backends/pulse_simulator.py index 63cc9f6eeb..4ddf647d9d 100644 --- a/qiskit/providers/aer/backends/pulse_simulator.py +++ b/qiskit/providers/aer/backends/pulse_simulator.py @@ -23,6 +23,7 @@ from qiskit.providers.models import BackendConfiguration from .aerbackend import AerBackend from ..aerjob import AerJob +from ..aererror import AerError from ..version import __version__ from ..openpulse.qobj.digest import digest_pulse_obj from ..openpulse.solver.opsolve import opsolve @@ -44,6 +45,7 @@ class PulseSimulator(AerBackend): 'open_pulse': True, 'memory': False, 'max_shots': 50000, + 'coupling_map': None, 'description': 'A pulse-based Hamiltonian simulator', 'gates': [], 'basis_gates': [] @@ -86,3 +88,20 @@ def _format_results(self, job_id, results, time_taken, qobj_id): output["backend_version"] = self.configuration().backend_version output["time_taken"] = time_taken return Result.from_dict(output) + + def get_dressed_energies(self, qobj): + """Digest the pulse qobj and return the eigenenergies + of the Hamiltonian""" + openpulse_system = digest_pulse_obj(qobj.to_dict()) + return openpulse_system.evals + + def _validate(self, qobj, backend_options, noise_model): + """Validate the pulse object. Make sure a + config has been attached in the proper location""" + + # Check to make sure a sim_config has been added + if not hasattr(qobj.config, 'sim_config'): + raise AerError('The pulse simulator qobj must have a sim_config ' + 'entry to configure the simulator') + + super()._validate(qobj, backend_options, noise_model) diff --git a/qiskit/providers/aer/openpulse/cy/channel_value.pxd b/qiskit/providers/aer/openpulse/cy/channel_value.pxd index d6a5d0d71e..cba3f3cdad 100644 --- a/qiskit/providers/aer/openpulse/cy/channel_value.pxd +++ b/qiskit/providers/aer/openpulse/cy/channel_value.pxd @@ -17,8 +17,9 @@ cdef complex chan_value(double t, unsigned int chan_num, + double freq_ch, double[::1] chan_pulse_times, complex[::1] pulse_array, unsigned int[::1] pulse_ints, double[::1] fc_array, - unsigned char[::1] register) \ No newline at end of file + unsigned char[::1] register) diff --git a/qiskit/providers/aer/openpulse/cy/channel_value.pyx b/qiskit/providers/aer/openpulse/cy/channel_value.pyx index a00b10fd43..54c268ec56 100644 --- a/qiskit/providers/aer/openpulse/cy/channel_value.pyx +++ b/qiskit/providers/aer/openpulse/cy/channel_value.pyx @@ -18,21 +18,22 @@ cimport cython cimport cython -from libc.math cimport floor +from libc.math cimport floor, M_PI cdef extern from "" namespace "std" nogil: double complex exp(double complex x) + @cython.cdivision(True) cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): """Computes the array index value for sampling a pulse in pulse_array. - + Args: t (double): The current simulation time. start (double): Start time of pulse in question. stop (double): Stop time of pulse. len_arr (int): Length of the pulse sample array. - + Returns: int: The array index value. """ @@ -41,6 +42,7 @@ cdef inline int get_arr_idx(double t, double start, double stop, int len_arr): @cython.boundscheck(False) cdef complex chan_value(double t, unsigned int chan_num, + double freq_ch, double[::1] chan_pulse_times, complex[::1] pulse_array, unsigned int[::1] pulse_ints, @@ -51,7 +53,7 @@ cdef complex chan_value(double t, Args: t (double): Current time. chan_num (int): The int that labels the channel. - chan_pulse_times (int array): Array containing + chan_pulse_times (int array): Array containing start_time, stop_time, pulse_int, conditional for each pulse on the channel. pulse_array (complex array): The array containing all the @@ -59,7 +61,8 @@ cdef complex chan_value(double t, pulse_ints (int array): Array that tells you where to start indexing pulse_array for a given pulse labeled by chan_pulse_times[4*kk+2]. - current_pulse_idx (int array): + current_pulse_idx (int array), + freq_ch (doule) channel frequency: """ cdef size_t kk cdef double start_time, stop_time, phase=0 @@ -68,7 +71,7 @@ cdef complex chan_value(double t, # This is because each entry has four values: # start_time, stop_time, pulse_int, conditional cdef unsigned int num_times = chan_pulse_times.shape[0] // 4 - + for kk in range(num_times): # the time is overlapped with the kkth pulse start_time = chan_pulse_times[4*kk] @@ -93,11 +96,12 @@ cdef complex chan_value(double t, # If condition not satisfied no do FC if not register[fc_array[3*kk+2]]: do_fc = 0 - if do_fc: + if do_fc: # Update the frame change value phase += fc_array[3*kk+1] else: break - if phase != 0: + if phase != 0: out *= exp(1j*phase) + out *= exp(-1j*2*M_PI*freq_ch*t) return out \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/cy/measure.pyx b/qiskit/providers/aer/openpulse/cy/measure.pyx index dd5f64c7a6..033df8096f 100644 --- a/qiskit/providers/aer/openpulse/cy/measure.pyx +++ b/qiskit/providers/aer/openpulse/cy/measure.pyx @@ -24,26 +24,26 @@ from qiskit.providers.aer.openpulse.qutip_lite.cy.spmatfuncs import cy_expect_ps @cython.boundscheck(False) def occ_probabilities(unsigned int[::1] qubits, complex[::1] state, list meas_ops): - """Computes the occupation probabilities of the specifed qubits for + """Computes the occupation probabilities of the specifed qubits for the given state. Args: qubits (int array): Ints labelling which qubits are to be measured. """ - + cdef unsigned int num_qubits = qubits.shape[0] cdef np.ndarray[double, ndim=1, mode="c"] probs = np.zeros(qubits.shape[0], dtype=float) - + cdef size_t kk cdef object oper - + for kk in range(num_qubits): oper = meas_ops[qubits[kk]] probs[kk] = cy_expect_psi_csr(oper.data.data, oper.data.indices, oper.data.indptr, state, 1) - + return probs @cython.boundscheck(False) @@ -51,10 +51,19 @@ def write_shots_memory(unsigned char[:, ::1] mem, unsigned int[::1] mem_slots, double[::1] probs, double[::1] rand_vals): - + + """Converts probabilities back into shots + + Args: + mem + mem_slots + probs: expectation value + rand_vals: random values used to convert back into shots + """ + cdef unsigned int nrows = mem.shape[0] cdef unsigned int nprobs = probs.shape[0] - + cdef size_t ii, jj cdef unsigned char temp for ii in range(nrows): diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index eba21ca962..5717bd535d 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -14,22 +14,25 @@ from collections import OrderedDict import numpy as np +import numpy.linalg as la from qiskit.exceptions import QiskitError from .op_system import OPSystem from .opparse import HamiltonianParser, NoiseParser -from .operators import init_fock_state, qubit_occ_oper +from .operators import qubit_occ_oper_dressed from ..solver.options import OPoptions # pylint: disable=no-name-in-module,import-error from ..cy.utils import oplist_to_array +from . import op_qobj as op """A module of routines for digesting a PULSE qobj into something we can actually use. """ + def digest_pulse_obj(qobj): """Takes an input PULSE obj an disgests it into things we can actually make use of. - + Args: qobj (Qobj): Qobj of PULSE type. @@ -39,39 +42,64 @@ def digest_pulse_obj(qobj): # Output data object out = OPSystem() - config_keys = qobj['config'].keys() + #get the config settings from the qobj + config_dict_sim = qobj['config']['sim_config'] + config_dict = qobj['config'] + + qubit_list = config_dict_sim.get('qubit_list', None) + if qubit_list is None: + qubit_list = list(range(config_dict_sim['n_qubits'])) + else: + config_dict_sim['n_qubits'] = len(qubit_list) + + config_keys_sim = config_dict_sim.keys() + config_keys = config_dict.keys() # Look for config keys out.global_data['shots'] = 1024 if 'shots' in config_keys: - out.global_data['shots'] = int(qobj['config']['shots']) + out.global_data['shots'] = int(config_dict['shots']) + + out.global_data['meas_level'] = 1 + if 'meas_level' in config_keys: + out.global_data['meas_level'] = int(config_dict['meas_level']) + + out.global_data['meas_return'] = 'avg' + if 'meas_return' in config_keys: + out.global_data['meas_return'] = config_dict['meas_return'] out.global_data['seed'] = None if 'seed' in config_keys: - out.global_data['seed'] = int(qobj['config']['seed']) + out.global_data['seed'] = int(config_dict['seed']) if 'memory_slots' in config_keys: - out.global_data['memory_slots'] = qobj['config']['memory_slots'] + out.global_data['memory_slots'] = config_dict['memory_slots'] else: err_str = 'Number of memory_slots must be specific in Qobj config' raise QiskitError(err_str) - + if 'memory' in config_keys: - out.global_data['memory'] = qobj['config']['memory'] + out.global_data['memory'] = config_dict['memory'] else: out.global_data['memory'] = False out.global_data['n_registers'] = 0 if 'n_registers' in config_keys: - out.global_data['n_registers'] = qobj['config']['n_registers'] - + out.global_data['n_registers'] = config_dict['n_registers'] + + + #which level to measure + out.global_data['q_level_meas'] = 1 + if 'q_level_meas' in config_keys_sim: + out.global_data['q_level_meas'] = int(config_dict_sim['q_level_meas']) + # Attach the ODE options - allowed_ode_options = ['atol', 'rtol', 'nsteps', 'max_step', + allowed_ode_options = ['atol', 'rtol', 'nsteps', 'max_step', 'num_cpus', 'norm_tol', 'norm_steps', 'rhs_reuse', 'rhs_filename'] user_set_ode_options = {} - if 'ode_options' in config_keys: - for key, val in qobj['config']['ode_options'].items(): + if 'ode_options' in config_keys_sim: + for key, val in config_dict_sim['ode_options'].items(): if key not in allowed_ode_options: raise Exception('Invalid ode_option: {}'.format(key)) else: @@ -80,14 +108,14 @@ def digest_pulse_obj(qobj): # Step #1: Parse hamiltonian representation - if 'hamiltonian' not in config_keys: + if 'hamiltonian' not in config_keys_sim: raise QiskitError('Qobj must have hamiltonian in config to simulate.') else: - ham = qobj['config']['hamiltonian'] - + ham = config_dict_sim['hamiltonian'] + out.vars = OrderedDict(ham['vars']) out.global_data['vars'] = list(out.vars.values()) - + # Get qubit subspace dimensions if 'qub' in ham.keys(): dim_qub = ham['qub'] @@ -97,8 +125,8 @@ def digest_pulse_obj(qobj): _dim_qub[int(key)] = val dim_qub = _dim_qub else: - dim_qub = {}.fromkeys(range(qobj['config']['n_qubits']), 2) - + dim_qub = {}.fromkeys(range(config_dict_sim['n_qubits']), 2) + # Get oscillator subspace dimensions if 'osc' in ham.keys(): dim_osc = ham['osc'] @@ -106,22 +134,22 @@ def digest_pulse_obj(qobj): # Convert str keys to int keys for key, val in dim_osc.items(): _dim_osc[int(key)] = val - dim_osc = _dim_osc + dim_osc = _dim_osc else: dim_osc = {} - + # Parse the Hamiltonian system = HamiltonianParser(h_str=ham['h_str'], dim_osc=dim_osc, dim_qub=dim_qub) - system.parse() + system.parse(qubit_list) out.system = system.compiled - - if 'noise' in qobj['config'].keys(): - noise = NoiseParser(noise_dict=qobj['config']['noise'], + + if 'noise' in config_dict_sim.keys(): + noise = NoiseParser(noise_dict=config_dict_sim['noise'], dim_osc=dim_osc, dim_qub=dim_qub) noise.parse() - + out.noise = noise.compiled if any(out.noise): out.can_sample = False @@ -129,26 +157,60 @@ def digest_pulse_obj(qobj): else: out.noise = None - # Set initial state - out.initial_state = init_fock_state(dim_osc, dim_qub) - + # Step #2: Get Hamiltonian channels - out.channels = get_hamiltonian_channels(ham['h_str']) - + out.channels = get_hamiltonian_channels(out.system, qubit_list) + + h_diag, evals, estates = get_diag_hamiltonian(out.system, + out.vars, out.channels) + + #convert estates into a qobj + estates_qobj = [] + for kk in range(len(estates[:,])): + estates_qobj.append(op.state(estates[:,kk])) + + out.h_diag = np.ascontiguousarray(h_diag.real) + out.evals = evals + + + # Set initial state + out.initial_state = 0*op.basis(len(evals), 1) + for idx, estate_coef in enumerate(estates[:,0]): + out.initial_state += estate_coef*op.basis(len(evals), idx) + #init_fock_state(dim_osc, dim_qub) + + #Setup freqs for the channels + out.freqs = OrderedDict() + for key in out.channels.keys(): + chidx = int(key[1:]) + if key[0]=='D': + out.freqs[key] = config_dict['qubit_lo_freq'][chidx] + elif key[0]=='U': + out.freqs[key] = 0 + for u_lo_idx in config_dict_sim['u_channel_lo'][chidx]: + if u_lo_idx['q'] < len(config_dict['qubit_lo_freq']): + qfreq = config_dict['qubit_lo_freq'][u_lo_idx['q']] + qscale = u_lo_idx['scale'][0] + out.freqs[key] += qfreq*qscale + else: + raise ValueError("Channel is not D or U") + + out.global_data['freqs'] = list(out.freqs.values()) + # Step #3: Build pulse arrays pulses, pulses_idx, pulse_dict = build_pulse_arrays(qobj) out.global_data['pulse_array'] = pulses out.global_data['pulse_indices'] = pulses_idx out.pulse_to_int = pulse_dict - + # Step #4: Get dt - if 'dt' not in qobj['config'].keys(): + if 'dt' not in config_dict_sim.keys(): raise QiskitError('Qobj must have a dt value to simulate.') else: - out.dt = qobj['config']['dt'] + out.dt = config_dict_sim['dt'] + - # Set the ODE solver max step to be the half the width of the smallest pulse min_width = np.iinfo(np.int32).max for key, val in out.pulse_to_int.items(): @@ -157,49 +219,116 @@ def digest_pulse_obj(qobj): start = out.global_data['pulse_indices'][val] min_width = min(min_width, stop-start) out.ode_options.max_step = min_width/2 * out.dt - + # Step #6: Convert experiments to data structures. - out.global_data['measurement_ops'] = [None]*qobj['config']['n_qubits'] + out.global_data['measurement_ops'] = [None]*config_dict_sim['n_qubits'] for exp in qobj['experiments']: - exp_struct = experiment_to_structs(exp, + exp_struct = experiment_to_structs(exp, out.channels, out.global_data['pulse_indices'], out.pulse_to_int, - out.dt) + out.dt, qubit_list) + + # Add in measurement operators + # Not sure if this will work for multiple measurements if len(exp_struct['acquire']) > 0: for acq in exp_struct['acquire']: for jj in acq[1]: + if jj > qubit_list[-1]: + continue if not out.global_data['measurement_ops'][jj]: out.global_data['measurement_ops'][jj] = \ - qubit_occ_oper(jj, + qubit_occ_oper_dressed(jj, + estates_qobj, h_osc=dim_osc, h_qub=dim_qub, - level=1 + level=out.global_data['q_level_meas'] ) - + + out.experiments.append(exp_struct) if not exp_struct['can_sample']: out.can_sample = False return out -def get_hamiltonian_channels(hamstring): +def get_diag_hamiltonian(parsed_ham, ham_vars, channels): + + """ Get the diagonal elements of the hamiltonian and get the + dressed frequencies and eigenstates + + Parameters: + parsed_ham (list): A list holding ops and strings from the Hamiltonian + of a specific quantum system. + + ham_vars: dictionary of variables + + channels: drive channels (set to 0) + + Returns: + h_diag: diagonal elements of the hamiltonian + h_evals: eigenvalues of the hamiltonian with no time-dep terms + h_estates: eigenstates of the hamiltonian with no time-dep terms + + Raises: + Exception: Missing index on channel. + """ + + pi = np.pi + + #Get the diagonal elements of the hamiltonian with all the + #drive terms set to zero + for chan in channels: + exec('%s=0'%chan) + + #might be a better solution to replace the 'var' in the hamiltonian + #string with 'op_system.vars[var]' + for var in ham_vars: + exec('%s=%f'%(var,ham_vars[var])) + + H_full = np.zeros(np.shape(parsed_ham[0][0].full()), dtype=complex) + + for hpart in parsed_ham: + H_full += hpart[0].full()*eval(hpart[1]) + + h_diag = np.diag(H_full) + + evals, estates = la.eigh(H_full) + + eval_mapping = [] + for ii in range(len(evals)): + eval_mapping.append(np.argmax(np.abs(estates[:,ii]))) + + evals2 = evals.copy() + estates2 = estates.copy() + + for ii in range(len(eval_mapping)): + evals2[eval_mapping[ii]] = evals[ii] + estates2[:,eval_mapping[ii]] = estates[:,ii] + + return h_diag, evals2, estates2 + + + +def get_hamiltonian_channels(parsed_ham, qubit_list=None): """ Get all the qubit channels D_i and U_i in the string representation of a system Hamiltonian. - + Parameters: - hamstring (list): A list holding strings giving the Hamiltonian + parsed_ham (list): A list holding ops and strings from the Hamiltonian of a specific quantum system. - + + qubit_list: whitelist of qubits + Returns: list: A list of all channels in Hamiltonian string. - + Raises: Exception: Missing index on channel. """ out_channels = [] - for ham_str in hamstring: + for _, ham_str in parsed_ham: chan_idx = [i for i, letter in enumerate(ham_str) if letter in ['D', 'U']] for ch in chan_idx: if (ch+1) == len(ham_str) or not ham_str[ch+1].isdigit(): @@ -219,11 +348,11 @@ def get_hamiltonian_channels(hamstring): if temp_chan not in out_channels: out_channels.append(temp_chan) out_channels.sort(key=lambda x: (int(x[1:]), x[0])) - + out_dict = OrderedDict() for idx, val in enumerate(out_channels): out_dict[val] = idx - + return out_dict @@ -231,10 +360,10 @@ def build_pulse_arrays(qobj): """ Build pulses and pulse_idx arrays, and a pulse_dict used in simulations and mapping of experimental pulse sequencies to pulse_idx sequencies and timings. - + Parameters: qobj (Qobj): A pulse-qobj instance. - + Returns: ndarray, ndarray, dict: Returns all pulses in one array, an array of start indices for pulses, and dict that @@ -246,7 +375,7 @@ def build_pulse_arrays(qobj): for kk, pulse in enumerate(qobj_pulses): pulse_dict[pulse['name']] = kk total_pulse_length += len(pulse['samples']) - + idx = kk+1 #now go through experiments looking for PV gates pv_pulses = [] @@ -257,12 +386,12 @@ def build_pulse_arrays(qobj): pv_pulses.append((pulse['val'], idx)) idx += 1 total_pulse_length += 1 - + pulse_dict['pv'] = pv_pulses - + pulses = np.empty(total_pulse_length, dtype=complex) pulses_idx = np.zeros(idx+1, dtype=np.uint32) - + stop = 0 ind = 1 for kk, pulse in enumerate(qobj_pulses): @@ -270,19 +399,21 @@ def build_pulse_arrays(qobj): pulses_idx[ind] = stop oplist_to_array(pulse['samples'], pulses, pulses_idx[ind-1]) ind += 1 - + for pv in pv_pulses: stop = pulses_idx[ind-1] + 1 pulses_idx[ind] = stop oplist_to_array([pv[0]], pulses, pulses_idx[ind-1]) ind += 1 - + return pulses, pulses_idx, pulse_dict -def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): +def experiment_to_structs(experiment, ham_chans, pulse_inds, + pulse_to_int, dt, qubit_list=None): + #TO DO: Error check that operations are restricted to qubit list max_time = 0 structs = {} - structs['name'] = experiment['header']['name'] + structs['header'] = experiment['header'] structs['channels'] = OrderedDict() for chan_name in ham_chans: structs['channels'][chan_name] = [[], []] @@ -291,10 +422,10 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): structs['snapshot'] = [] structs['tlist'] = [] structs['can_sample'] = True - # This is a list that tells us whether the last PV pulse on a channel needs to + # This is a list that tells us whether the last PV pulse on a channel needs to # be assigned a final time based on the next pulse on that channel pv_needs_tf = [0]*len(ham_chans) - + # The instructions are time-ordered so just loop through them. for inst in experiment['instructions']: # Do D and U channels @@ -302,13 +433,13 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): chan_name = inst['ch'].upper() if chan_name not in ham_chans.keys(): raise ValueError('Channel {} is not in Hamiltonian model'.format(inst['ch'])) - + # If last pulse on channel was a PV then need to set # its final time to be start time of current pulse if pv_needs_tf[ham_chans[chan_name]]: structs['channels'][chan_name][0][-3] = inst['t0'] pv_needs_tf[ham_chans[chan_name]] = 0 - + # Get condtional info if 'conditional' in inst.keys(): cond = inst['conditional'] @@ -323,11 +454,11 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): break structs['channels'][chan_name][0].extend([inst['t0'], None, index, cond]) pv_needs_tf[ham_chans[chan_name]] = 1 - + # Frame changes elif inst['name'] == 'fc': structs['channels'][chan_name][1].extend([inst['t0'],inst['phase'], cond]) - + # A standard pulse else: start = inst['t0'] @@ -335,30 +466,46 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): pulse_width = (pulse_inds[pulse_int+1]-pulse_inds[pulse_int])*dt stop = start + pulse_width structs['channels'][chan_name][0].extend([start, stop, pulse_int, cond]) - + max_time = max(max_time, stop) - + # Take care of acquires and snapshots (bfuncs added ) else: # measurements if inst['name'] == 'acquire': + + #Better way?? + qlist2 = [] + mlist2 = [] + if qubit_list is None: + qlist2 = inst['qubits'] + mlist2 = inst['memory_slot'] + else: + for qind, qb in enumerate(inst['qubits']): + if qb in qubit_list: + qlist2.append(qb) + mlist2.append(inst['memory_slot'][qind]) + + + + acq_vals = [inst['t0'], - np.asarray(inst['qubits'], dtype=np.uint32), - np.asarray(inst['memory_slot'], dtype=np.uint32) + np.asarray(qlist2, dtype=np.uint32), + np.asarray(mlist2, dtype=np.uint32) ] if 'register_slot' in inst.keys(): acq_vals.append(np.asarray(inst['register_slot'], dtype=np.uint32)) else: acq_vals.append(None) structs['acquire'].append(acq_vals) - + #update max_time max_time = max(max_time, inst['t0']+dt*inst['duration']) - + # Add time to tlist if inst['t0'] not in structs['tlist']: structs['tlist'].append(inst['t0']) - + # conditionals elif inst['name'] == 'bfunc': bfun_vals = [inst['t0'], inst['mask'], inst['relation'], @@ -367,26 +514,26 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt): bfun_vals.append(inst['memory']) else: bfun_vals.append(None) - + structs['cond'].append(acq_vals) - + #update max_time max_time = max(max_time, inst['t0']) - + # Add time to tlist if inst['t0'] not in structs['tlist']: structs['tlist'].append(inst['t0']) - + # snapshots elif inst['name'] == 'snapshot': if inst['type'] != 'state': raise QiskitError("Snapshots must be of type 'state'") structs['snapshot'].append([inst['t0'], inst['label']]) - + # Add time to tlist if inst['t0'] not in structs['tlist']: structs['tlist'].append(inst['t0']) - + #update max_time max_time = max(max_time, inst['t0']) diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py index ae77d5f1a9..bc6b083d55 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_qobj.py +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -14,9 +14,9 @@ # pylint: disable=invalid-name import numpy as np -import qiskit.providers.aer.openpulse.qutip_lite.operators as ops -import qiskit.providers.aer.openpulse.qutip_lite.states as st -import qiskit.providers.aer.openpulse.qutip_lite.tensor as ten +from ..qutip_lite import operators as ops +from ..qutip_lite import states as st +from ..qutip_lite import tensor as ten from ..qutip_lite.qobj import Qobj from ..qutip_lite.cy.spmatfuncs import (spmv_csr, cy_expect_psi_csr) @@ -161,6 +161,11 @@ def basis(level, pos): """ return st.basis(level, pos) +def state(state_vec): + """ Qiskit wrapper of qobj + """ + return Qobj(state_vec) + def fock_dm(level, eigv): """ Qiskit wrapper of fock_dm @@ -199,7 +204,7 @@ def get_func(name, qobj): __operdict = {'X': sigmax, 'Y': sigmay, 'Z': sigmaz, - 'Sp': sigmap, 'Sm': sigmam, 'I': qeye, + 'Sp': create, 'Sm': destroy, 'I': qeye, 'O': num, 'P': project, 'A': destroy, 'C': create, 'N': num} diff --git a/qiskit/providers/aer/openpulse/qobj/op_system.py b/qiskit/providers/aer/openpulse/qobj/op_system.py index 9c596de4bf..2099143480 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_system.py +++ b/qiskit/providers/aer/openpulse/qobj/op_system.py @@ -45,4 +45,10 @@ def __init__(self): # Can experiments be simulated once then sampled self.can_sample = True # holds global data - self.global_data = {} \ No newline at end of file + self.global_data = {} + # holds frequencies for the channels + self.freqs = {} + # diagonal elements of the hamiltonian + self.h_diag = None + # eigenvalues of the time-independent hamiltonian + self.evals = None \ No newline at end of file diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index e7b0d9c1ca..13030509cf 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -32,10 +32,21 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): out_oper (qutip.Qobj): quantum operator for target qubit. """ + opr_tmp = None + # get number of levels in Hilbert space if opname in ['X', 'Y', 'Z', 'Sp', 'Sm', 'I', 'O', 'P']: is_qubit = True dim = h_qub.get(index, 2) + + if opname in ['X', 'Y', 'Z'] and dim>2: + if opname=='X': + opr_tmp = op.get_oper('A', dim)+op.get_oper('C', dim) + elif opname=='Y': + opr_tmp = -1j*op.get_oper('A', dim)+1j*op.get_oper('C', dim) + else: + opr_tmp = op.get_oper('I', dim) - op.get_oper('N', dim) + else: is_qubit = False dim = h_osc.get(index, 5) @@ -43,7 +54,8 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): if opname == 'P': opr_tmp = op.get_oper(opname, dim, states) else: - opr_tmp = op.get_oper(opname, dim) + if opr_tmp is None: + opr_tmp = op.get_oper(opname, dim) # reverse sort by index rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] @@ -69,7 +81,7 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): """Builds the occupation number operator for a target qubit in a qubit oscillator system, where the oscillator are the first subsystems, and the qubit last. - + Parameters ---------- target_qubit (int): Qubit for which operator is built. @@ -97,17 +109,59 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): return op.tensor(opers) +def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): + """Builds the occupation number operator for a target qubit + in a qubit oscillator system, where the oscillator are the first + subsystems, and the qubit last. This does it for a dressed systems + assuming estates has the same ordering + + Parameters + ---------- + target_qubit (int): Qubit for which operator is built. + estates: eigenstates in the dressed frame + h_osc (dict): Dict of number of levels in each oscillator. + h_qub (dict): Dict of number of levels in each qubit system. + level (int): Level of qubit system to be measured. + + Returns + ------- + out_oper (qutip.Qobj): Occupation number operator for target qubit. + """ + # reverse sort by index + rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] + rev_h_qub = sorted(h_qub.items(), key=lambda x: x[0])[::-1] + + # osc_n * … * osc_0 * qubit_n * … * qubit_0 + states = [] + proj_op = 0*op.fock_dm(len(estates), 0) + for ii,dd in rev_h_osc: + states.append(op.basis(dd,0)) + for ii,dd in rev_h_qub: + if ii == target_qubit: + states.append(op.basis(dd, level)) + else: + states.append(op.state(np.ones(dd))) + + + state = op.tensor(states) + + for ii in range(len(estates)): + if state[ii]==1: + proj_op += estates[ii] * estates[ii].dag() + + return proj_op + def measure_outcomes(measured_qubits, state_vector, measure_ops, seed=None): """Generate measurement outcomes for a given set of qubits and state vector. - + Parameters: measured_qubits (array_like): Qubits to be measured. state_vector(ndarray): State vector. measure_ops (list): List of measurement operator seed (int): Optional seed to RandomState for reproducibility. - + Returns: outcomes (str): String of binaries representing measured qubit values. """ @@ -122,7 +176,7 @@ def measure_outcomes(measured_qubits, state_vector, measure_ops, if excited_prob >= rnds[kk]: outcomes += '1' else: - outcomes += '0' + outcomes += '0' else: # We need a string for all the qubits up to the last # one being measured for the mask operation to work @@ -134,7 +188,7 @@ def measure_outcomes(measured_qubits, state_vector, measure_ops, def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): """Builds and applies the projection operator associated with a given qubit measurement result onto a state vector. - + Parameters ---------- measured_qubits (list): measured qubit indices. diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index 1e18dfa858..74977632ca 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -62,7 +62,7 @@ def compiled(self): """ return self.__tc_hams + self.__td_hams - def parse(self): + def parse(self, qubit_list=None): """ Parse and generate quantum class object """ self.__td_hams = [] @@ -79,7 +79,9 @@ def parse(self): # find time-dependent term if p_td: - coef, token = self._tokenizer(p_td.group('opr')) + coef, token = self._tokenizer(p_td.group('opr'), qubit_list) + if token is None: + continue # combine coefficient to time-dependent term if coef: td = '*'.join([coef, p_td.group('ch')]) @@ -90,7 +92,9 @@ def parse(self): self.__td_hams.append(_td) else: - coef, token = self._tokenizer(ham) + coef, token = self._tokenizer(ham, qubit_list) + if token is None: + continue token = self._shunting_yard(token) _tc = self._token2qobj(token), coef @@ -148,9 +152,11 @@ def _expand_sum(self): return ham_out - def _tokenizer(self, op_str): + def _tokenizer(self, op_str, qubit_list=None): """ Convert string to token and coefficient + Check if the index is in qubit_list """ + # generate token _op_str = copy.copy(op_str) token_list = [] @@ -165,6 +171,8 @@ def _tokenizer(self, op_str): _name = p.group() if p.group() not in self.__str2qopr.keys(): idx = int(p.group('idx')) + if qubit_list is not None and idx not in qubit_list: + return 0, None name = p.group('opr') opr = gen_oper(name, idx, self.dim_osc, self.dim_qub) self.__str2qopr[p.group()] = opr diff --git a/qiskit/providers/aer/openpulse/readme.md b/qiskit/providers/aer/openpulse/readme.md new file mode 100644 index 0000000000..87c74c5ed3 --- /dev/null +++ b/qiskit/providers/aer/openpulse/readme.md @@ -0,0 +1,59 @@ +# Openpulse + +This simulates job using the open pulse format (see the spec). + +## Example + +XXX + +## Hamiltonian + +One of the main components of the open pulse simulator is the specification +of the Hamiltonian. The Hamiltonian dictates how the simulation will evolve +the dynamics based on the pulse schedule. + +There are two allowed components to a simulation, *qubits* and *oscillators*. +*Qubits* are the objects that will be measured at the end of the simulation. +The operators for qubits are defined as Pauli's but if the number of levels +is defined by be greater than 2 these will be internally converted to +creation and annhilation operators. + +The Hamiltonian is a dictionary comparised of +- `h_str`: Definition of the Hamiltonian in terms of operators, drives and +coefficients +- `vars`: Numeric values for the variables in the Hamiltonian +- `qubs`: Dictionary indicating the number of levels for each qubit to +use in the simulation +- `osc`: Dictionary indicating the number of levels for each oscillator to +use in the simulation + +There must be qubits, but there does not need to be oscillators. Measurements +are given by the qubit state; the measurement process is not simulated. + +### `h_str` Syntax + +The `h_str` is a list of strings which indicate various terms in the +Hamiltonian. These can have the form + +`||` or `` + +where `` is a string form of an operator with coefficients and `` +indicates one of the time-dependent drive channels (e.g. `D2` or `U10`). +Additionally, there is a sum form +`_SUM[i,n1,n2,F[{i}]]` where {i} is replaced in each loop by the value. + +## Solving + +The solver takes the Hamiltonian (`H(t=0)`) and sets all drive/control channels to zero. +Consider `H_d` to be the diagonal elements of `H(t=0)`, then the transformation applied is +`U=e^{-i H_d t/hbar}`. For all drive/control channels, the LO is applied so +`d(t) -> D(t)e^{-i w t}`. If the drive is associated with some operator *B* then +the upper triangular part of *B* is multiplied by `d(t)` and the lower triangular part +by `d*(t)`. This ensures the Hamiltonian is Hermitian and also that in the transformed +frame that a resonant pulse is close to DC. + +### Measurement + +The measurement operators are the projections onto the 1 excitation subsystem for qubit `l` +where qubit `l` is defined by diagonalizing `H(t=0)` (i.e. the dressed basis). + diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index 7984b9b8fe..4ba0ec6ab5 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -19,8 +19,8 @@ import os import sys -import qiskit.providers.aer.openpulse.qutip_lite.cy as cy -import qiskit.providers.aer.openpulse.solver.settings as op_set +from ..qutip_lite import cy as cy +from . import settings _cython_path = os.path.abspath(cy.__file__).replace('__init__.py', '') _cython_path = _cython_path.replace("\\", "/") @@ -40,7 +40,7 @@ def __init__(self, op_system): self.dt = op_system.dt self.num_ham_terms = self.op_system.global_data['num_h_terms'] - + # Code generator properties self._file = None self.code = [] # strings to be written to file @@ -79,7 +79,7 @@ def generate(self, filename="rhs.pyx"): self.file(filename) self._file.writelines(self.code) self._file.close() - op_set.CGEN_NUM += 1 + settings.CGEN_NUM += 1 def indent(self): """increase indention level by one""" @@ -97,12 +97,16 @@ def ODE_func_header(self): # strings for time and vector variables input_vars = ("\n double t" + ",\n complex[::1] vec") + + # add diagonal hamiltonian terms + input_vars += (",\n double[::1] energ") + for k in range(self.num_ham_terms): input_vars += (",\n " + "complex[::1] data%d, " % k + "int[::1] idx%d, " % k + "int[::1] ptr%d" % k) - + #Add global vaiables input_vars += (",\n " + "complex[::1] pulse_array") input_vars += (",\n " + "unsigned int[::1] pulse_indices") @@ -115,14 +119,18 @@ def ODE_func_header(self): # add Hamiltonian variables for key in self.op_system.vars.keys(): input_vars += (",\n " + "complex %s" % key) - + + # add Freq variables + for key in self.op_system.freqs.keys(): + input_vars += (",\n " + "double %s_freq" % key) + # register input_vars += (",\n " + "unsigned char[::1] register") - + func_end = "):" return [func_name + input_vars + func_end] - + def channels(self): """Write out the channels """ @@ -130,42 +138,57 @@ def channels(self): channel_lines.append("# Compute complex channel values at time `t`") for chan, idx in self.op_system.channels.items(): - chan_str = "%s = chan_value(t, %s, " %(chan, idx) + \ + chan_str = "%s = chan_value(t, %s, %s_freq, " %(chan, idx, chan) + \ "%s_pulses, pulse_array, pulse_indices, " % chan + \ - "%s_fc, register)" % chan + "%s_fc, register)" % (chan) channel_lines.append(chan_str) channel_lines.append('') return channel_lines - - + + def func_vars(self): """Writes the variables and spmv parts""" func_vars = [] + sp1 = " " + sp2 = sp1+sp1 func_vars.append("# Eval the time-dependent terms and do SPMV.") - for idx, term in enumerate(self.op_system.system): + for idx in range(len(self.op_system.system)+1): + + if (idx==len(self.op_system.system) and + (len(self.op_system.system) < self.num_ham_terms)): + #this is the noise term + term = [1.0, 1.0] + elif idx < len(self.op_system.system): + term = self.op_system.system[idx] + else: + continue + if isinstance(term, list) or term[1]: func_vars.append("td%s = %s" % (idx, term[1])) - func_vars.append("if abs(td%s) > 1e-15:" % idx) - - spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ - "&vec[0], td{i}, &out[0], num_rows)" - func_vars.append(" "+spmv_str.format(i=idx)) - else: - spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ - "&vec[0], 1.0, &out[0], num_rows)" - func_vars.append(spmv_str.format(i=idx)) - - # There is a noise term - if len(self.op_system.system) < self.num_ham_terms: - spmv_str = "spmvpy(&data{i}[0], &idx{i}[0], &ptr{i}[0], "+ \ - "&vec[0], 1.0, &out[0], num_rows)" - func_vars.append("") - func_vars.append("# Noise term") - func_vars.append(spmv_str.format(i=idx+1)) - - + func_vars.append("td%s = 1.0" % (idx)) + + func_vars.append("if abs(td%s) > 1e-15:" % idx) + + func_vars.append(sp1 + "for row in range(num_rows):") + func_vars.append(sp2 + "dot = 0;") + func_vars.append(sp2 + "row_start = ptr%d[row];"%idx) + func_vars.append(sp2 + "row_end = ptr%d[row+1];"%idx) + func_vars.append(sp2 + "for jj in range(row_start,row_end):") + func_vars.append(sp1 + + sp2 + + "osc_term = exp(1j*(energ[row]-energ[idx%d[jj]])*t)"%idx) + func_vars.append(sp1 + sp2 + "if rowPyDataMem_NEW_ZEROED(num_rows,sizeof(complex))' ] @@ -191,9 +216,8 @@ def func_header(op_system): for val in op_system.channels: func_vars.append("cdef double complex %s" % val) - for kk, item in enumerate(op_system.system): - if item[1]: - func_vars.append("cdef double complex td%s" % kk) + for kk in range(len(op_system.system)+1): + func_vars.append("cdef double complex td%s" % kk) return func_vars @@ -224,6 +248,12 @@ def cython_preamble(): void PyDataMem_NEW_ZEROED(size_t size, size_t elsize) void PyArray_ENABLEFLAGS(np.ndarray arr, int flags) +cdef extern from "" namespace "std" nogil: + double complex exp(double complex x) + +cdef extern from "" namespace "std" nogil: + double complex conj(double complex x) + from qiskit.providers.aer.openpulse.qutip_lite.cy.spmatfuncs cimport spmvpy from qiskit.providers.aer.openpulse.qutip_lite.cy.math cimport erf from libc.math cimport pi diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py index d526fa8fb7..d86f761c10 100644 --- a/qiskit/providers/aer/openpulse/solver/data_config.py +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -13,6 +13,7 @@ # that they have been altered from the originals. import numpy as np +pi = np.pi def op_data_config(op_system): """ Preps the data for the opsolver. @@ -40,6 +41,8 @@ def op_data_config(op_system): op_system.global_data['n_ops_ind'] = [] op_system.global_data['n_ops_ptr'] = [] + op_system.global_data['h_diag_elems'] = op_system.h_diag + # if there are any collapse operators H_noise = 0 for kk in range(op_system.global_data['c_num']): @@ -53,10 +56,10 @@ def op_data_config(op_system): op_system.global_data['n_ops_data'].append(n_op.data.data) op_system.global_data['n_ops_ind'].append(n_op.data.indices) op_system.global_data['n_ops_ptr'].append(n_op.data.indptr) - # Norm ops added to time-independent part of + # Norm ops added to time-independent part of # Hamiltonian to decrease norm H_noise -= 0.5j * n_op - + if H_noise: H = H + [H_noise] @@ -67,13 +70,17 @@ def op_data_config(op_system): # setup ode args string ode_var_str = "" + + #diagonal elements + ode_var_str += "global_data['h_diag_elems'], " + # Hamiltonian data for kk in range(op_system.global_data['num_h_terms']): h_str = "global_data['h_ops_data'][%s], " % kk h_str += "global_data['h_ops_ind'][%s], " % kk h_str += "global_data['h_ops_ptr'][%s], " % kk ode_var_str += h_str - + # Add pulse array and pulse indices ode_var_str += "global_data['pulse_array'], " ode_var_str += "global_data['pulse_indices'], " @@ -81,6 +88,9 @@ def op_data_config(op_system): var_list = list(op_system.vars.keys()) final_var = var_list[-1] + freq_list = list(op_system.freqs.keys()) + final_freq = freq_list[-1] + # Now add channel variables chan_list = list(op_system.channels.keys()) final_chan = chan_list[-1] @@ -89,12 +99,19 @@ def op_data_config(op_system): ode_var_str += "exp['channels']['%s'][1]" % chan if chan != final_chan or var_list: ode_var_str+= ', ' - + #now do the variables for idx, var in enumerate(var_list): ode_var_str += "global_data['vars'][%s]" % idx - if var != final_var: + if var != final_var or freq_list: ode_var_str+= ', ' + + #now do the freq + for idx, freq in enumerate(freq_list): + ode_var_str += "global_data['freqs'][%s]" % idx + if freq != final_freq: + ode_var_str+= ', ' + # Add register ode_var_str += ", register" op_system.global_data['string'] = ode_var_str diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index 36078dd918..d3d747742f 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -22,6 +22,7 @@ import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs + from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from qiskit.providers.aer.openpulse.solver.zvode import qiskit_zvode from qiskit.providers.aer.openpulse.cy.memory import write_memory @@ -47,9 +48,9 @@ def monte_carlo(seed, exp, global_data, ode_options): # Get number of acquire, snapshots, and conditionals num_acq = len(exp['acquire']) - acq_idx = 0 + acq_idx = 0 num_snap = len(exp['snapshot']) - snap_idx = 0 + snap_idx = 0 num_cond = len(exp['cond']) cond_idx = 0 @@ -80,8 +81,8 @@ def monte_carlo(seed, exp, global_data, ode_options): ODE.t = 0.0 ODE._y = np.array([0.0], complex) ODE._integrator.reset(len(ODE._y), ODE.jac is not None) - - ODE.set_initial_value(global_data['initial_state'], 0) + + ODE.set_initial_value(global_data['initial_state'], 0) # make array for collapse operator inds cinds = np.arange(global_data['c_num']) @@ -169,8 +170,6 @@ def monte_carlo(seed, exp, global_data, ode_options): probs = occ_probabilities(qubits, out_psi, global_data['measurement_ops']) rand_vals = rng.rand(memory_slots.shape[0]) write_shots_memory(memory, memory_slots, probs, rand_vals) - int_mem = memory.dot(np.power(2.0, - np.arange(memory.shape[1]-1,-1,-1))).astype(int) acq_idx += 1 - return int_mem \ No newline at end of file + return memory diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index 2653888467..d9fc5043f7 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -29,6 +29,8 @@ from .unitary import unitary_evolution from .monte_carlo import monte_carlo from qiskit.tools.parallel import parallel_map, CPU_COUNT +from ..cy.measure import write_shots_memory + dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -83,9 +85,9 @@ def __init__(self, op_system): if not op_system.can_sample: # preallocate ntraj arrays for state vectors, collapse times, and # which operator - self.collapse_times = [[] for kk in + self.collapse_times = [[] for kk in range(op_system.global_data['shots'])] - self.collapse_operators = [[] for kk in + self.collapse_operators = [[] for kk in range(op_system.global_data['shots'])] # setup seeds array if op_system.global_data['seed']: @@ -99,11 +101,22 @@ def __init__(self, op_system): def run(self): map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} - + + + # exp_results from the solvers return the values of the measurement + # operators + # If no collapse terms, and only measurements at end # can do a single shot. + + # exp_results is a list of '0' and '1' + # where '0' occurs with probability 1- + # and '1' occurs with probability + # M is the measurement operator, which is a projector + # into one of the qubit states (usually |1>) if self.op_system.can_sample: - results = parallel_map(unitary_evolution, + start = time.time() + exp_results = parallel_map(unitary_evolution, self.op_system.experiments, task_args=(self.op_system.global_data, self.op_system.ode_options @@ -111,10 +124,16 @@ def run(self): **map_kwargs ) + end = time.time() + exp_times = (np.ones(len(self.op_system.experiments))* + (end-start)/len(self.op_system.experiments)) + + # need to simulate each trajectory, so shots*len(experiments) times # Do a for-loop over experiments, and do shots in parallel_map else: - all_results = [] + exp_results = [] + exp_times = [] for exp in self.op_system.experiments: start = time.time() rng = np.random.RandomState(exp['seed']) @@ -127,24 +146,88 @@ def run(self): ), **map_kwargs ) - unique = np.unique(exp_res, return_counts=True) - hex_dict = {} - for idx in range(unique[0].shape[0]): - key = hex(unique[0][idx]) - hex_dict[key] = unique[1][idx] + + # exp_results is a list for each shot + # so transform back to an array of shots + exp_res2 = [] + for exp_shot in exp_res: + exp_res2.append(exp_shot[0].tolist()) + + end = time.time() - results = {'name': exp['name'], - 'seed_simulator': exp['seed'], - 'shots': self.op_system.global_data['shots'], - 'status': 'DONE', - 'success': True, - 'time_taken': (end - start), - 'header': {}} - - results['data'] = {'counts': hex_dict} - - all_results.append(results) - + exp_times.append(end-start) + exp_results.append(np.array(exp_res2)) + + + #format the data into the proper output + all_results = [] + for idx_exp, exp in enumerate(self.op_system.experiments): + + + m_lev = self.op_system.global_data['meas_level'] + m_ret = self.op_system.global_data['meas_return'] + + # populate the results dictionary + results = {'seed_simulator': exp['seed'], + 'shots': self.op_system.global_data['shots'], + 'status': 'DONE', + 'success': True, + 'time_taken': exp_times[idx_exp], + 'header': exp['header'], + 'meas_level': m_lev, + 'meas_return': m_ret, + 'data': {}} + + memory = exp_results[idx_exp] + + # meas_level 2 return the shots + if m_lev==2: + + # convert the memory **array** into a n + # integer + # e.g. [1,0] -> 2 + int_mem = memory.dot(np.power(2.0, + np.arange(memory.shape[1]-1, -1, -1))).astype(int) + + # if the memory flag is set return each shot + if self.op_system.global_data['memory']: + hex_mem = [hex(val) for val in int_mem] + results['data']['memory'] = hex_mem + + # Get hex counts dict + unique = np.unique(int_mem, return_counts=True) + hex_dict = {} + for kk in range(unique[0].shape[0]): + key = hex(unique[0][kk]) + hex_dict[key] = unique[1][kk] + + results['data']['counts'] = hex_dict + + # meas_level 1 returns the + elif m_lev==1: + + + + if m_ret=='avg': + + memory = [np.mean(memory,0)] + + # convert into the right [real, complex] pair form for json + # this should be cython? + results['data']['memory'] = [] + + for mem_shot in memory: + results['data']['memory'].append([]) + for mem_slot in mem_shot: + results['data']['memory'][-1].append( + [np.real(mem_slot), np.imag(mem_slot)]) + + if m_ret=='avg': + results['data']['memory'] = results['data']['memory'][0] + + + all_results.append(results) + _cython_build_cleanup(self.op_system.global_data['rhs_file_name']) return all_results diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index 36852a8c3a..3ccfe13b8c 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -13,8 +13,8 @@ # that they have been altered from the originals. import os -from qiskit.providers.aer.openpulse.solver.codegen import OPCodegen -import qiskit.providers.aer.openpulse.solver.settings as op_set +from .codegen import OPCodegen +from . import settings as op_set def _op_generate_rhs(op_system): diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index 703b9cd69a..242ad42ade 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -20,8 +20,7 @@ import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs -from qiskit.providers.aer.openpulse.cy.measure import (occ_probabilities, - write_shots_memory) +from ..cy.measure import occ_probabilities, write_shots_memory dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -88,20 +87,11 @@ def unitary_evolution(exp, global_data, ode_options): # set channel and frame change indexing arrays # Do final measurement at end + psi_rot = np.exp(-1j*global_data['h_diag_elems']*ODE.t) + psi *= psi_rot qubits = exp['acquire'][0][1] memory_slots = exp['acquire'][0][2] probs = occ_probabilities(qubits, psi, global_data['measurement_ops']) rand_vals = rng.rand(memory_slots.shape[0]*shots) write_shots_memory(memory, memory_slots, probs, rand_vals) - int_mem = memory.dot(np.power(2.0, - np.arange(memory.shape[1]-1, -1, -1))).astype(int) - if global_data['memory']: - hex_mem = [hex(val) for val in int_mem] - return hex_mem - # Get hex counts dict - unique = np.unique(int_mem, return_counts=True) - hex_dict = {} - for kk in range(unique[0].shape[0]): - key = hex(unique[0][kk]) - hex_dict[key] = unique[1][kk] - return hex_dict + return memory From e8e53708b101316a7572af6cbe8f21f23807d9ba Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Wed, 7 Aug 2019 14:26:40 -0400 Subject: [PATCH 24/31] updates --- .../openpulse/qutip_lite/cy/spmatfuncs.pyx | 2 - .../aer/openpulse/qutip_lite/qobj.py | 5 +- .../aer/openpulse/qutip_lite/superop_reps.py | 575 ------------------ .../aer/openpulse/qutip_lite/superoperator.py | 73 +-- .../aer/openpulse/qutip_lite/tensor.py | 43 +- 5 files changed, 27 insertions(+), 671 deletions(-) delete mode 100755 qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx index 1408140e73..2a0c994d3e 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/spmatfuncs.pyx @@ -520,8 +520,6 @@ def expect_csr_ket(object A, object B, int isherm): else: return expt - - @cython.boundscheck(False) @cython.wraparound(False) cpdef double complex zcsr_mat_elem(object A, object left, object right, bool bra_ket=1): diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index 06cf0c2d39..ede57bcd63 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -44,7 +44,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -# pylint: disable= invalid-name, redefined-outer-name +# pylint: disable= invalid-name, redefined-outer-name, no-name-in-module """The Quantum Object (Qobj) class, for representing quantum states and operators, and related functions. @@ -971,7 +971,6 @@ def proj(self): return Qobj(_out,dims=_dims) - def tr(self): """Trace of a quantum object. @@ -1176,8 +1175,6 @@ def sinm(self): else: raise TypeError('Invalid operand for matrix square root') - - def unit(self, inplace=False, norm=None, sparse=False, tol=0, maxiter=100000): diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py b/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py deleted file mode 100755 index f7ab8ca771..0000000000 --- a/qiskit/providers/aer/openpulse/qutip_lite/superop_reps.py +++ /dev/null @@ -1,575 +0,0 @@ -# -*- coding: utf-8 -*- -# This file is part of QuTiP: Quantum Toolbox in Python. -# -# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names -# of its contributors may be used to endorse or promote products derived -# from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -############################################################################### -# -# This module was initially contributed by Ben Criger. -# -""" -This module implements transformations between superoperator representations, -including supermatrix, Kraus, Choi and Chi (process) matrix formalisms. -""" - -__all__ = ['super_to_choi', 'choi_to_super', 'choi_to_kraus', 'kraus_to_choi', - 'kraus_to_super', 'choi_to_chi', 'chi_to_choi', 'to_choi', - 'to_chi', 'to_super', 'to_kraus', 'to_stinespring' - ] - -# Python Standard Library -from itertools import starmap, product - -# NumPy/SciPy -from numpy.core.multiarray import array, zeros -from numpy.core.shape_base import hstack -from numpy.matrixlib.defmatrix import matrix -from numpy import sqrt, floor, log2 -from numpy import dot -from scipy.linalg import eig, svd -# Needed to avoid conflict with itertools.product. -import numpy as np - -# Other QuTiP functions and classes -from .superoperator import vec2mat, spre, spost, operator_to_vector -from .operators import identity, sigmax, sigmay, sigmaz -from .tensor import tensor, flatten -from .qobj import Qobj -from .states import basis - - -# SPECIFIC SUPEROPERATORS ----------------------------------------------------- - -def _dep_super(pe): - """ - Returns the superoperator corresponding to qubit depolarization for a - given parameter pe. - - TODO: if this is going into production (hopefully it isn't) then check - CPTP, expand to arbitrary dimensional systems, etc. - """ - return Qobj(dims=[[[2], [2]], [[2], [2]]], - inpt=array([[1. - pe / 2., 0., 0., pe / 2.], - [0., 1. - pe, 0., 0.], - [0., 0., 1. - pe, 0.], - [pe / 2., 0., 0., 1. - pe / 2.]])) - - -def _dep_choi(pe): - """ - Returns the choi matrix corresponding to qubit depolarization for a - given parameter pe. - - TODO: if this is going into production (hopefully it isn't) then check - CPTP, expand to arbitrary dimensional systems, etc. - """ - return Qobj(dims=[[[2], [2]], [[2], [2]]], - inpt=array([[1. - pe / 2., 0., 0., 1. - pe], - [0., pe / 2., 0., 0.], - [0., 0., pe / 2., 0.], - [1. - pe, 0., 0., 1. - pe / 2.]]), - superrep='choi') - - -# CHANGE OF BASIS FUNCTIONS --------------------------------------------------- -# These functions find change of basis matrices, and are useful in converting -# between (for instance) Choi and chi matrices. At some point, these should -# probably be moved out to another module. - -_SINGLE_QUBIT_PAULI_BASIS = (identity(2), sigmax(), sigmay(), sigmaz()) - - -def _pauli_basis(nq=1): - # NOTE: This is slow as can be. - # TODO: Make this sparse. CSR format was causing problems for the [idx, :] - # slicing below. - B = zeros((4 ** nq, 4 ** nq), dtype=complex) - dims = [[[2] * nq] * 2] * 2 - - for idx, op in enumerate(starmap(tensor, - product(_SINGLE_QUBIT_PAULI_BASIS, - repeat=nq))): - B[:, idx] = operator_to_vector(op).dag().data.todense() - - return Qobj(B, dims=dims) - - -# PRIVATE CONVERSION FUNCTIONS ------------------------------------------------ -# These functions handle the main work of converting between representations, -# and are exposed below by other functions that add postconditions about types. -# -# TODO: handle type='kraus' as a three-index Qobj, rather than as a list? - -def _super_tofrom_choi(q_oper): - """ - We exploit that the basis transformation between Choi and supermatrix - representations squares to the identity, so that if we munge Qobj.type, - we can use the same function. - - Since this function doesn't respect :attr:`Qobj.type`, we mark it as - private; only those functions which wrap this in a way so as to preserve - type should be called externally. - """ - data = q_oper.data.toarray() - sqrt_shape = int(sqrt(data.shape[0])) - return Qobj(dims=q_oper.dims, - inpt=data.reshape([sqrt_shape] * 4). - transpose(3, 1, 2, 0).reshape(q_oper.data.shape)) - -def _isqubitdims(dims): - """Checks whether all entries in a dims list are integer powers of 2. - - Parameters - ---------- - dims : nested list of ints - Dimensions to be checked. - - Returns - ------- - isqubitdims : bool - True if and only if every member of the flattened dims - list is an integer power of 2. - """ - return all([ - 2**floor(log2(dim)) == dim - for dim in flatten(dims) - ]) - - -def _super_to_superpauli(q_oper): - """ - Converts a superoperator in the column-stacking basis to - the Pauli basis (assuming qubit dimensions). - - This is an internal function, as QuTiP does not currently have - a way to mark that superoperators are represented in the Pauli - basis as opposed to the column-stacking basis; a Pauli-basis - ``type='super'`` would thus break other conversion functions. - """ - # Ensure we start with a column-stacking-basis superoperator. - sqobj = to_super(q_oper) - if not _isqubitdims(sqobj.dims): - raise ValueError("Pauli basis is only defined for qubits.") - nq = int(log2(sqobj.shape[0]) / 2) - B = _pauli_basis(nq) / sqrt(2**nq) - # To do this, we have to hack a bit and force the dims to match, - # since the _pauli_basis function makes different assumptions - # about indices than we need here. - B.dims = sqobj.dims - return (B.dag() * sqobj * B) - - -def super_to_choi(q_oper): - # TODO: deprecate and make private in favor of to_choi, - # which looks at Qobj.type to determine the right conversion function. - """ - Takes a superoperator to a Choi matrix - TODO: Sanitize input, incorporate as method on Qobj if type=='super' - """ - q_oper = _super_tofrom_choi(q_oper) - q_oper.superrep = 'choi' - return q_oper - - -def choi_to_super(q_oper): - # TODO: deprecate and make private in favor of to_super, - # which looks at Qobj.type to determine the right conversion function. - """ - Takes a Choi matrix to a superoperator - TODO: Sanitize input, Abstract-ify application of channels to states - """ - q_oper = super_to_choi(q_oper) - q_oper.superrep = 'super' - return q_oper - - -def choi_to_kraus(q_oper): - """ - Takes a Choi matrix and returns a list of Kraus operators. - TODO: Create a new class structure for quantum channels, perhaps as a - strict sub-class of Qobj. - """ - vals, vecs = eig(q_oper.data.todense()) - vecs = [array(_) for _ in zip(*vecs)] - return [Qobj(inpt=sqrt(val)*vec2mat(vec)) for val, vec in zip(vals, vecs)] - - -def kraus_to_choi(kraus_list): - """ - Takes a list of Kraus operators and returns the Choi matrix for the channel - represented by the Kraus operators in `kraus_list` - """ - kraus_mat_list = list(map(lambda x: matrix(x.data.todense()), kraus_list)) - op_len = len(kraus_mat_list[0]) - op_rng = range(op_len) - choi_blocks = array([[sum([op[:, c_ix] * array([op.H[r_ix, :]]) - for op in kraus_mat_list]) - for r_ix in op_rng] - for c_ix in op_rng]) - return Qobj(inpt=hstack(hstack(choi_blocks)), - dims=[kraus_list[0].dims, kraus_list[0].dims], type='super', - superrep='choi') - - -def kraus_to_super(kraus_list): - """ - Converts a list of Kraus operators and returns a super operator. - """ - return choi_to_super(kraus_to_choi(kraus_list)) - - -def _nq(dims): - dim = np.product(dims[0][0]) - nq = int(log2(dim)) - if 2 ** nq != dim: - raise ValueError("{} is not an integer power of 2.".format(dim)) - return nq - - -def choi_to_chi(q_oper): - """ - Converts a Choi matrix to a Chi matrix in the Pauli basis. - - NOTE: this is only supported for qubits right now. Need to extend to - Heisenberg-Weyl for other subsystem dimensions. - """ - nq = _nq(q_oper.dims) - B = _pauli_basis(nq) - # Force the basis change to match the dimensions of - # the input. - B.dims = q_oper.dims - B.superrep = 'choi' - - return Qobj(B.dag() * q_oper * B, superrep='chi') - - -def chi_to_choi(q_oper): - """ - Converts a Choi matrix to a Chi matrix in the Pauli basis. - - NOTE: this is only supported for qubits right now. Need to extend to - Heisenberg-Weyl for other subsystem dimensions. - """ - nq = _nq(q_oper.dims) - B = _pauli_basis(nq) - # Force the basis change to match the dimensions of - # the input. - B.dims = q_oper.dims - - # We normally should not multiply objects of different - # superreps, so Qobj warns about that. Here, however, we're actively - # converting between, so the superrep of B is irrelevant. - # To suppress warnings, we pretend that B is also a chi. - B.superrep = 'chi' - - # The Chi matrix has tr(chi) == d², so we need to divide out - # by that to get back to the Choi form. - return Qobj((B * q_oper * B.dag()) / q_oper.shape[0], superrep='choi') - -def _svd_u_to_kraus(U, S, d, dK, indims, outdims): - """ - Given a partial isometry U and a vector of square-roots of singular values S - obtained from an SVD, produces the Kraus operators represented by U. - - Returns - ------- - Ks : list of Qobj - Quantum objects represnting each of the Kraus operators. - """ - # We use U * S since S is 1-index, such that this is equivalent to - # U . diag(S), but easier to write down. - Ks = list(map(Qobj, array(U * S).reshape((d, d, dK), order='F').transpose((2, 0, 1)))) - for K in Ks: - K.dims = [outdims, indims] - return Ks - - -def _generalized_kraus(q_oper, thresh=1e-10): - # TODO: document! - # TODO: use this to generalize to_kraus to the case where U != V. - # This is critical for non-CP maps, as appear in (for example) - # diamond norm differences between two CP maps. - if q_oper.type != "super" or q_oper.superrep != "choi": - raise ValueError("Expected a Choi matrix, got a {} (superrep {}).".format(q_oper.type, q_oper.superrep)) - - # Remember the shape of the underlying space, - # as we'll need this to make Kraus operators later. - dL, dR = map(int, map(sqrt, q_oper.shape)) - # Also remember the dims breakout. - out_dims, in_dims = q_oper.dims - out_left, out_right = out_dims - in_left, in_right = in_dims - - # Find the SVD. - U, S, V = svd(q_oper.data.todense()) - - # Truncate away the zero singular values, up to a threshold. - nonzero_idxs = S > thresh - dK = nonzero_idxs.sum() - U = array(U)[:, nonzero_idxs] - # We also want S to be a single index array, which np.matrix - # doesn't allow for. This is stripped by calling array() on it. - S = sqrt(array(S)[nonzero_idxs]) - # Since NumPy returns V and not V+, we need to take the dagger - # to get back to quantum info notation for Stinespring pairs. - V = array(V.conj().T)[:, nonzero_idxs] - - # Next, we convert each of U and V into Kraus operators. - # Finally, we want the Kraus index to be left-most so that we - # can map over it when making Qobjs. - # FIXME: does not preserve dims! - kU = _svd_u_to_kraus(U, S, dL, dK, out_right, out_left) - kV = _svd_u_to_kraus(V, S, dL, dK, in_right, in_left) - - return kU, kV - - -def choi_to_stinespring(q_oper, thresh=1e-10): - # TODO: document! - kU, kV = _generalized_kraus(q_oper, thresh=thresh) - - assert(len(kU) == len(kV)) - dK = len(kU) - dL = kU[0].shape[0] - dR = kV[0].shape[1] - # Also remember the dims breakout. - out_dims, in_dims = q_oper.dims - out_left, out_right = out_dims - in_left, in_right = in_dims - - A = Qobj(zeros((dK * dL, dL)), dims=[out_left + [dK], out_right + [1]]) - B = Qobj(zeros((dK * dR, dR)), dims=[in_left + [dK], in_right + [1]]) - - for idx_kraus, (KL, KR) in enumerate(zip(kU, kV)): - A += tensor(KL, basis(dK, idx_kraus)) - B += tensor(KR, basis(dK, idx_kraus)) - - # There is no input (right) Kraus index, so strip that off. - del A.dims[1][-1] - del B.dims[1][-1] - - return A, B - -# PUBLIC CONVERSION FUNCTIONS ------------------------------------------------- -# These functions handle superoperator conversions in a way that preserves the -# correctness of Qobj.type, and in a way that automatically branches based on -# the input Qobj.type. - -def to_choi(q_oper): - """ - Converts a Qobj representing a quantum map to the Choi representation, - such that the trace of the returned operator is equal to the dimension - of the system. - - Parameters - ---------- - q_oper : Qobj - Superoperator to be converted to Choi representation. If - ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, - such that ``to_choi(A) == to_choi(sprepost(A, A.dag()))``. - - Returns - ------- - choi : Qobj - A quantum object representing the same map as ``q_oper``, such that - ``choi.superrep == "choi"``. - - Raises - ------ - TypeError: if the given quantum object is not a map, or cannot be converted - to Choi representation. - """ - if q_oper.type == 'super': - if q_oper.superrep == 'choi': - return q_oper - if q_oper.superrep == 'super': - return super_to_choi(q_oper) - if q_oper.superrep == 'chi': - return chi_to_choi(q_oper) - else: - raise TypeError(q_oper.superrep) - elif q_oper.type == 'oper': - return super_to_choi(spre(q_oper) * spost(q_oper.dag())) - else: - raise TypeError( - "Conversion of Qobj with type = {0.type} " - "and superrep = {0.choi} to Choi not supported.".format(q_oper) - ) - - -def to_chi(q_oper): - """ - Converts a Qobj representing a quantum map to a representation as a chi - (process) matrix in the Pauli basis, such that the trace of the returned - operator is equal to the dimension of the system. - - Parameters - ---------- - q_oper : Qobj - Superoperator to be converted to Chi representation. If - ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, - such that ``to_chi(A) == to_chi(sprepost(A, A.dag()))``. - - Returns - ------- - chi : Qobj - A quantum object representing the same map as ``q_oper``, such that - ``chi.superrep == "chi"``. - - Raises - ------ - TypeError: if the given quantum object is not a map, or cannot be converted - to Chi representation. - """ - if q_oper.type == 'super': - # Case 1: Already done. - if q_oper.superrep == 'chi': - return q_oper - # Case 2: Can directly convert. - elif q_oper.superrep == 'choi': - return choi_to_chi(q_oper) - # Case 3: Need to go through Choi. - elif q_oper.superrep == 'super': - return to_chi(to_choi(q_oper)) - else: - raise TypeError(q_oper.superrep) - elif q_oper.type == 'oper': - return to_chi(spre(q_oper) * spost(q_oper.dag())) - else: - raise TypeError( - "Conversion of Qobj with type = {0.type} " - "and superrep = {0.choi} to Choi not supported.".format(q_oper) - ) - - -def to_super(q_oper): - """ - Converts a Qobj representing a quantum map to the supermatrix (Liouville) - representation. - - Parameters - ---------- - q_oper : Qobj - Superoperator to be converted to supermatrix representation. If - ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, - such that ``to_super(A) == sprepost(A, A.dag())``. - - Returns - ------- - superop : Qobj - A quantum object representing the same map as ``q_oper``, such that - ``superop.superrep == "super"``. - - Raises - ------ - TypeError - If the given quantum object is not a map, or cannot be converted - to supermatrix representation. - """ - if q_oper.type == 'super': - # Case 1: Already done. - if q_oper.superrep == "super": - return q_oper - # Case 2: Can directly convert. - elif q_oper.superrep == 'choi': - return choi_to_super(q_oper) - # Case 3: Need to go through Choi. - elif q_oper.superrep == 'chi': - return to_super(to_choi(q_oper)) - # Case 4: Something went wrong. - else: - raise ValueError( - "Unrecognized superrep '{}'.".format(q_oper.superrep)) - elif q_oper.type == 'oper': # Assume unitary - return spre(q_oper) * spost(q_oper.dag()) - else: - raise TypeError( - "Conversion of Qobj with type = {0.type} " - "and superrep = {0.superrep} to supermatrix not " - "supported.".format(q_oper) - ) - - -def to_kraus(q_oper): - """ - Converts a Qobj representing a quantum map to a list of quantum objects, - each representing an operator in the Kraus decomposition of the given map. - - Parameters - ---------- - q_oper : Qobj - Superoperator to be converted to Kraus representation. If - ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, - such that ``to_kraus(A) == to_kraus(sprepost(A, A.dag())) == [A]``. - - Returns - ------- - kraus_ops : list of Qobj - A list of quantum objects, each representing a Kraus operator in the - decomposition of ``q_oper``. - - Raises - ------ - TypeError: if the given quantum object is not a map, or cannot be - decomposed into Kraus operators. - """ - if q_oper.type == 'super': - if q_oper.superrep in ("super", "chi"): - return to_kraus(to_choi(q_oper)) - elif q_oper.superrep == 'choi': - return choi_to_kraus(q_oper) - elif q_oper.type == 'oper': # Assume unitary - return [q_oper] - else: - raise TypeError( - "Conversion of Qobj with type = {0.type} " - "and superrep = {0.superrep} to Kraus decomposition not " - "supported.".format(q_oper) - ) - -def to_stinespring(q_oper): - r""" - Converts a Qobj representing a quantum map $\Lambda$ to a pair of partial isometries - $A$ and $B$ such that $\Lambda(X) = \Tr_2(A X B^\dagger)$ for all inputs $X$, where - the partial trace is taken over a a new index on the output dimensions of $A$ and $B$. - - For completely positive inputs, $A$ will always equal $B$ up to precision errors. - - Parameters - ---------- - q_oper : Qobj - Superoperator to be converted to a Stinespring pair. - - Returns - ------- - A, B : Qobj - Quantum objects representing each of the Stinespring matrices for the input Qobj. - """ - return choi_to_stinespring(to_choi(q_oper)) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py index 78c5735551..6afa16d543 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -44,38 +44,33 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name -__all__ = ['liouvillian', 'liouvillian_ref', 'lindblad_dissipator', - 'operator_to_vector', 'vector_to_operator', 'mat2vec', 'vec2mat', - 'vec2mat_index', 'mat2vec_index', 'spost', 'spre', 'sprepost'] +""" +Module for super operators. +""" -import scipy.sparse as sp import numpy as np -from functools import partial from .qobj import Qobj from .fastsparse import fast_csr_matrix, fast_identity from .sparse import sp_reshape -from .cy.spmath import zcsr_kron +from .cy.spmath import zcsr_kron # pylint: disable=no-name-in-module def liouvillian(H, c_ops=[], data_only=False, chi=None): """Assembles the Liouvillian superoperator from a Hamiltonian - and a ``list`` of collapse operators. Like liouvillian, but with an - experimental implementation which avoids creating extra Qobj instances, - which can be advantageous for large systems. + and a ``list`` of collapse operators. - Parameters - ---------- - H : Qobj or QobjEvo - System Hamiltonian. + Args: + H (qobj.Qobj): System Hamiltonian. - c_ops : array_like of Qobj or QobjEvo - A ``list`` or ``array`` of collapse operators. + c_ops (qobj.Qobj or array_like): A single collapse operator or an array. - Returns - ------- - L : Qobj or QobjEvo - Liouvillian superoperator. + Returns: + qobj.Qobj: Liouvillian superoperator. + + Raises: + ValueError: Chi must be list of len(c_ops). """ if isinstance(c_ops, (Qobj)): @@ -96,7 +91,7 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): raise TypeError("Invalid type for Hamiltonian.") else: # no hamiltonian given, pick system size from a collapse operator - if isinstance(c_ops, list) and len(c_ops) > 0: + if isinstance(c_ops, list) and any(c_ops) > 0: c = c_ops[0] if c.isoper: op_dims = c.dims @@ -114,7 +109,6 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): spI = fast_identity(op_shape[0]) - td = False L = None if isinstance(H, Qobj): if H.isoper: @@ -126,7 +120,6 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): else: data = fast_csr_matrix(shape=(sop_shape[0], sop_shape[1])) - td_c_ops = [] for idx, c_op in enumerate(c_ops): c_ = c_op @@ -155,36 +148,6 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): return L -def liouvillian_ref(H, c_ops=[]): - """Assembles the Liouvillian superoperator from a Hamiltonian - and a ``list`` of collapse operators. - - Parameters - ---------- - H : qobj - System Hamiltonian. - - c_ops : array_like - A ``list`` or ``array`` of collapse operators. - - Returns - ------- - L : qobj - Liouvillian superoperator. - """ - - L = -1.0j * (spre(H) - spost(H)) if H else 0 - - for c in c_ops: - if c.issuper: - L += c - else: - cdc = c.dag() * c - L += spre(c) * spost(c.dag()) - 0.5 * spre(cdc) - 0.5 * spost(cdc) - - return L - - def lindblad_dissipator(a, b=None, data_only=False, chi=None): """ Lindblad dissipator (generalized) for a single pair of collapse operators @@ -217,7 +180,6 @@ def lindblad_dissipator(a, b=None, data_only=False, chi=None): else: D = spre(a) * spost(b.dag()) - 0.5 * spre(ad_b) - 0.5 * spost(ad_b) - return D.data if data_only else D @@ -354,9 +316,8 @@ def sprepost(A, B): Superoperator formed from input quantum objects. """ dims = [[_drop_projected_dims(A.dims[0]), - _drop_projected_dims(B.dims[1])], + _drop_projected_dims(B.dims[1])], [_drop_projected_dims(A.dims[1]), - _drop_projected_dims(B.dims[0])]] + _drop_projected_dims(B.dims[0])]] data = zcsr_kron(B.data.T, A.data) return Qobj(data, dims=dims, superrep='super') - diff --git a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py index 2622c829ce..636c84ba21 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/tensor.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/tensor.py @@ -48,45 +48,24 @@ Module for the creation of composite quantum objects via the tensor product. """ -__all__ = [ - 'tensor', 'super_tensor', 'composite', 'tensor_swap', 'tensor_contract' -] - import numpy as np -from .cy.spmath import zcsr_kron +from .cy.spmath import zcsr_kron # pylint: disable=no-name-in-module from .qobj import Qobj -from .superoperator import operator_to_vector -from .dimensions import ( - flatten, enumerate_flat, unflatten, deep_remove, - dims_to_tensor_shape, dims_idxs_to_tensor_idxs) from ..qutip_lite.settings import auto_tidyup def tensor(*args): """Calculates the tensor product of input operators. - Parameters - ---------- - args : array_like - ``list`` or ``array`` of quantum objects for tensor product. - - Returns - ------- - obj : qobj - A composite quantum object. - - Examples - -------- - >>> tensor([sigmax(), sigmax()]) - Quantum object: dims = [[2, 2], [2, 2]], \ -shape = [4, 4], type = oper, isHerm = True - Qobj data = - [[ 0.+0.j 0.+0.j 0.+0.j 1.+0.j] - [ 0.+0.j 0.+0.j 1.+0.j 0.+0.j] - [ 0.+0.j 1.+0.j 0.+0.j 0.+0.j] - [ 1.+0.j 0.+0.j 0.+0.j 0.+0.j]] - """ + Args: + args (array_like): List or array of quantum objects for tensor product. + Returns: + qobj.Qobj: A composite quantum object. + + Raises: + TypeError: Requires at least one input argument. + """ if not args: raise TypeError("Requires at least one input argument") @@ -109,7 +88,6 @@ def tensor(*args): raise TypeError("One of inputs is not a quantum object") out = Qobj() - if qlist[0].issuper: out.superrep = qlist[0].superrep if not all([q.superrep == out.superrep for q in qlist]): @@ -123,7 +101,6 @@ def tensor(*args): out.dims = q.dims else: out.data = zcsr_kron(out.data, q.data) - out.dims = [out.dims[0] + q.dims[0], out.dims[1] + q.dims[1]] out.isherm = out.isherm and q.isherm @@ -132,5 +109,3 @@ def tensor(*args): out._isherm = None return out.tidyup() if auto_tidyup else out - - From a63f2398eeed269b66e8966f8262cdd4f2b6a792 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Fri, 9 Aug 2019 15:45:40 -0400 Subject: [PATCH 25/31] linting --- .../aer/openpulse/qutip_lite/qobj.py | 348 ++++++++---------- .../aer/openpulse/qutip_lite/sparse.py | 231 ++++++------ .../aer/openpulse/qutip_lite/states.py | 331 +++++------------ .../aer/openpulse/qutip_lite/superoperator.py | 59 +-- 4 files changed, 390 insertions(+), 579 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index ede57bcd63..e44118096f 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -44,7 +44,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -# pylint: disable= invalid-name, redefined-outer-name, no-name-in-module +# pylint: disable=invalid-name, redefined-outer-name, no-name-in-module +# pylint: disable=import-error, unused-import """The Quantum Object (Qobj) class, for representing quantum states and operators, and related functions. @@ -56,27 +57,25 @@ import types import builtins - +from itertools import zip_longest # import math functions from numpy.math: required for td string evaluation +import numpy as np from numpy import (arccos, arccosh, arcsin, arcsinh, arctan, arctan2, arctanh, ceil, copysign, cos, cosh, degrees, e, exp, expm1, fabs, floor, fmod, frexp, hypot, isinf, isnan, ldexp, log, log10, log1p, modf, pi, radians, sin, sinh, sqrt, tan, tanh, trunc) - -import numpy as np +from qiskit.providers.aer.version import __version__ import scipy.sparse as sp import scipy.linalg as la from .settings import (auto_tidyup, auto_tidyup_dims, atol, auto_tidyup_atol) -from qiskit.providers.aer.version import __version__ from .fastsparse import fast_csr_matrix, fast_identity from .sparse import (sp_eigs, sp_expm, sp_fro_norm, sp_max_norm, - sp_one_norm, sp_L2_norm) + sp_one_norm, sp_L2_norm) from .dimensions import type_from_dims, enumerate_flat, collapse_dims_super from .cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_isherm, zcsr_trace, zcsr_proj, zcsr_inner) from .cy.spmatfuncs import zcsr_mat_elem from .cy.sparse_utils import cy_tidyup -from itertools import zip_longest class Qobj(object): @@ -202,6 +201,9 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], fast (str or bool): Fast object instantiation. superrep (str): Type of super representaiton. isunitary (bool): Is object unitary. + + Raises: + Exception: Something bad happened. """ self._isherm = isherm self._type = type @@ -227,7 +229,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], self._data = fast_csr_matrix((inpt.data.data, inpt.data.indices, inpt.data.indptr), - shape=inpt.shape, copy=copy) + shape=inpt.shape, copy=copy) if not np.any(dims): # Dimensions of quantum object used for keeping track of tensor @@ -284,7 +286,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], else: _tmp = inpt self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), - shape=_tmp.shape, copy=do_copy) + shape=_tmp.shape, copy=do_copy) if not np.any(dims): self.dims = [[int(inpt.shape[0])], [int(inpt.shape[1])]] @@ -296,7 +298,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], # if input is int, float, or complex then convert to array _tmp = sp.csr_matrix([[inpt]], dtype=complex) self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), - shape=_tmp.shape) + shape=_tmp.shape) if not np.any(dims): self.dims = [[1], [1]] else: @@ -308,12 +310,12 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], inpt = np.array([[0]]) _tmp = sp.csr_matrix(inpt, dtype=complex, copy=copy) self._data = fast_csr_matrix((_tmp.data, _tmp.indices, _tmp.indptr), - shape = _tmp.shape) + shape=_tmp.shape) self.dims = [[int(inpt.shape[0])], [int(inpt.shape[1])]] if type == 'super': # Type is not super, i.e. dims not explicitly passed, but oper shape - if dims== [[], []] and self.shape[0] == self.shape[1]: + if dims == [[], []] and self.shape[0] == self.shape[1]: sub_shape = np.sqrt(self.shape[0]) # check if root of shape is int if (sub_shape % 1) != 0: @@ -339,9 +341,11 @@ def copy(self): def get_data(self): """Gets underlying data.""" return self._data - + #Here we perfrom a check of the csr matrix type during setting of Q.data def set_data(self, data): + """Data setter + """ if not isinstance(data, fast_csr_matrix): raise TypeError('Qobj data must be in fast_csr format.') else: @@ -356,8 +360,8 @@ def __add__(self, other): if not isinstance(other, Qobj): if isinstance(other, (int, float, complex, np.integer, np.floating, - np.complexfloating, np.ndarray, list, tuple)) \ - or sp.issparse(other): + np.complexfloating, np.ndarray, list, tuple)) \ + or sp.issparse(other): other = Qobj(other) else: return NotImplemented @@ -379,7 +383,8 @@ def __add__(self, other): out.dims = self.dims - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if isinstance(dat, (int, float)): out._isherm = self._isherm @@ -387,7 +392,7 @@ def __add__(self, other): # We use _isherm here to prevent recalculating on self and # other, relying on that bool(None) == False. out._isherm = (True if self._isherm and other._isherm - else out.isherm) + else out.isherm) out.superrep = self.superrep @@ -407,7 +412,8 @@ def __add__(self, other): out.data.data = out.data.data + dat out.dims = other.dims - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if isinstance(dat, complex): out._isherm = out.isherm @@ -428,7 +434,8 @@ def __add__(self, other): out = Qobj() out.data = self.data + other.data out.dims = self.dims - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if self.type in ['ket', 'bra', 'operator-ket', 'operator-bra']: out._isherm = False @@ -479,7 +486,8 @@ def __mul__(self, other): out.data = self.data * other.data dims = [self.dims[0], other.dims[1]] out.dims = dims - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if (auto_tidyup_dims and not isinstance(dims[0][0], list) and not isinstance(dims[1][0], list)): @@ -497,10 +505,10 @@ def __mul__(self, other): # are traced out of that side. out.dims = [max([1], [dim for dim, m in zip(dims[0], mask) - if not m]), + if not m]), max([1], [dim for dim, m in zip(dims[1], mask) - if not m])] + if not m])] else: out.dims = dims @@ -533,7 +541,7 @@ def __mul__(self, other): raise TypeError("Incompatible Qobj shapes") elif isinstance(other, np.ndarray): - if other.dtype=='object': + if other.dtype == 'object': return np.array([self * item for item in other], dtype=object) else: @@ -551,7 +559,8 @@ def __mul__(self, other): out.data = self.data * other out.dims = self.dims out.superrep = self.superrep - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -567,9 +576,9 @@ def __rmul__(self, other): MULTIPLICATION with Qobj on RIGHT [ ex. 4*Qobj ] """ if isinstance(other, np.ndarray): - if other.dtype=='object': + if other.dtype == 'object': return np.array([item * self for item in other], - dtype=object) + dtype=object) else: return other * self.data @@ -579,12 +588,14 @@ def __rmul__(self, other): dtype=object) elif isinstance(other, (int, float, complex, - np.integer, np.floating, np.complexfloating)): + np.integer, np.floating, + np.complexfloating)): out = Qobj() out.data = other * self.data out.dims = self.dims out.superrep = self.superrep - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -611,7 +622,8 @@ def __div__(self, other): out = Qobj() out.data = self.data / other out.dims = self.dims - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() if isinstance(other, complex): out._isherm = out.isherm else: @@ -632,7 +644,8 @@ def __neg__(self): out.data = -self.data out.dims = self.dims out.superrep = self.superrep - if auto_tidyup: out.tidyup() + if auto_tidyup: + out.tidyup() out._isherm = self._isherm out._isunitary = self._isunitary return out @@ -662,7 +675,7 @@ def __ne__(self, other): """ INEQUALITY operator. """ - return not (self == other) + return not self == other def __pow__(self, n, m=None): # calculates powers of Qobj """ @@ -795,9 +808,9 @@ def _format_float(value): else: return "%.3f" % value - def _format_element(m, n, d): + def _format_element(_, n, d): s = " & " if n > 0 else "" - if type(d) == str: + if isinstance(d, str): return s + d else: if abs(np.imag(d)) < atol: @@ -808,9 +821,9 @@ def _format_element(m, n, d): s_re = _format_float(np.real(d)) s_im = _format_float(np.imag(d)) if np.imag(d) > 0.0: - return (s + "(" + s_re + "+" + s_im + "j)") + return s + "(" + s_re + "+" + s_im + "j)" else: - return (s + "(" + s_re + s_im + "j)") + return s + "(" + s_re + s_im + "j)" if M > 10 and N > 10: # truncated matrix output @@ -898,36 +911,26 @@ def norm(self, norm=None, sparse=False, tol=0, maxiter=100000): Other ket and operator norms may be specified using the `norm` and argument. - Parameters - ---------- - norm : str - Which norm to use for ket/bra vectors: L2 'l2', max norm 'max', - or for operators: trace 'tr', Frobius 'fro', one 'one', or max - 'max'. - - sparse : bool - Use sparse eigenvalue solver for trace norm. Other norms are not - affected by this parameter. - - tol : float - Tolerance for sparse solver (if used) for trace norm. The sparse - solver may not converge if the tolerance is set too low. + Args: + norm (str): Which norm to use for ket/bra vectors: L2 'l2', + max norm 'max', or for operators: trace 'tr', Frobius + 'fro', one 'one', or max 'max'. - maxiter : int - Maximum number of iterations performed by sparse solver (if used) - for trace norm. + sparse (bool): Use sparse eigenvalue solver for trace norm. + Other norms are not affected by this parameter. - Returns - ------- - norm : float - The requested norm of the operator or state quantum object. + tol (float): Tolerance for sparse solver (if used) for trace norm. + The sparse solver may not converge if the tolerance + is set too low. + maxiter (int): Maximum number of iterations performed by sparse + solver (if used) for trace norm. - Notes - ----- - The sparse eigensolver is much slower than the dense version. - Use sparse only if memory requirements demand it. + Returns: + float: The requested norm of the operator or state quantum object. + Raises: + ValueError: Invalid input. """ if self.type in ['oper', 'super']: if norm is None or norm == 'tr': @@ -961,15 +964,15 @@ def proj(self): TypeError: Project from only bra or ket. """ if self.isket: - _out = zcsr_proj(self.data,1) - _dims = [self.dims[0],self.dims[0]] + _out = zcsr_proj(self.data, 1) + _dims = [self.dims[0], self.dims[0]] elif self.isbra: - _out = zcsr_proj(self.data,0) - _dims = [self.dims[1],self.dims[1]] + _out = zcsr_proj(self.data, 0) + _dims = [self.dims[1], self.dims[1]] else: raise TypeError('Projector can only be formed from a bra or ket.') - return Qobj(_out,dims=_dims) + return Qobj(_out, dims=_dims) def tr(self): """Trace of a quantum object. @@ -1031,23 +1034,18 @@ def expm(self, method='dense'): Input operator must be square. - Parameters - ---------- - method : str {'dense', 'sparse'} - Use set method to use to calculate the matrix exponentiation. The - available choices includes 'dense' and 'sparse'. Since the - exponential of a matrix is nearly always dense, method='dense' - is set as default.s - - Returns - ------- - oper : :class:`qutip.Qobj` - Exponentiated quantum operator. + Args: + method (str): Use set method to use to calculate the matrix + exponentiation. The available choices includes + 'dense' and 'sparse'. Since the exponential of + a matrix is nearly always dense, method='dense' + is set as default. + Returns: + Qobj: Exponentiated quantum operator. - Raises - ------ - TypeError - Quantum operator is not square. + Raises: + TypeError: Quantum operator is not square. + ValueError: Invalid input. """ if self.dims[0][0] != self.dims[1][0]: @@ -1068,10 +1066,8 @@ def expm(self, method='dense'): def check_herm(self): """Check if the quantum object is hermitian. - Returns - ------- - isherm : bool - Returns the new value of isherm property. + Returns: + bool: Returns the new value of isherm property. """ self._isherm = None return self.isherm @@ -1081,30 +1077,20 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): Operator must be square. - Parameters - ---------- - sparse : bool - Use sparse eigenvalue/vector solver. - tol : float - Tolerance used by sparse solver (0 = machine precision). - maxiter : int - Maximum number of iterations used by sparse solver. + Args: + sparse (bool): Use sparse eigenvalue/vector solver. - Returns - ------- - oper : :class:`qutip.Qobj` - Matrix square root of operator. + tol (float): Tolerance used by sparse solver + (0 = machine precision). - Raises - ------ - TypeError - Quantum object is not square. + maxiter (int): Maximum number of iterations used by + sparse solver. - Notes - ----- - The sparse eigensolver is much slower than the dense version. - Use sparse only if memory requirements demand it. + Returns: + Qobj: Matrix square root of operator. + Raises: + TypeError: Quantum object is not square. """ if self.dims[0][0] == self.dims[1][0]: evals, evecs = sp_eigs(self.data, self.isherm, sparse=sparse, @@ -1171,13 +1157,13 @@ def sinm(self): """ if self.dims[0][0] == self.dims[1][0]: - return -0.5j * ((1j * self).expm() - (-1j * self).expm()) + return -0.5j * ((1j * self).expm() - (-1j * self).expm()) else: raise TypeError('Invalid operand for matrix square root') def unit(self, inplace=False, - norm=None, sparse=False, - tol=0, maxiter=100000): + norm=None, sparse=False, + tol=0, maxiter=100000): """Operator or state normalized to unity. Uses norm from Qobj.norm(). @@ -1197,7 +1183,7 @@ def unit(self, inplace=False, """ if inplace: nrm = self.norm(norm=norm, sparse=sparse, - tol=tol, maxiter=maxiter) + tol=tol, maxiter=maxiter) self.data /= nrm elif not inplace: @@ -1240,25 +1226,18 @@ def transform(self, inpt, inverse=False, sparse=True): Input array can be a ``matrix`` defining the transformation, or a ``list`` of kets that defines the new basis. + Args: + inpt (ndarray): A ``matrix`` or ``list`` of kets defining + the transformation. + inverse (bool): Whether to return inverse transformation. - Parameters - ---------- - inpt : array_like - A ``matrix`` or ``list`` of kets defining the transformation. - inverse : bool - Whether to return inverse transformation. - sparse : bool - Use sparse matrices when possible. Can be slower. - - Returns - ------- - oper : :class:`qutip.Qobj` - Operator in new basis. + sparse (bool): Use sparse matrices when possible. Can be slower. - Notes - ----- - This function is still in development. + Returns: + Qobj: Operator in new basis. + Raises: + TypeError: Invalid input. """ if isinstance(inpt, list) or (isinstance(inpt, np.ndarray) and @@ -1268,10 +1247,10 @@ def transform(self, inpt, inverse=False, sparse=True): 'Invalid size of ket list for basis transformation') if sparse: S = sp.hstack([psi.data for psi in inpt], - format='csr', dtype=complex).conj().T + format='csr', dtype=complex).conj().T else: S = np.hstack([psi.full() for psi in inpt], - dtype=complex).conj().T + dtype=complex).conj().T elif isinstance(inpt, Qobj) and inpt.isoper: S = inpt.data elif isinstance(inpt, np.ndarray): @@ -1319,23 +1298,16 @@ def matrix_element(self, bra, ket): Gives the matrix element for the quantum object sandwiched between a `bra` and `ket` vector. - Parameters - ----------- - bra : :class:`qutip.Qobj` - Quantum object of type 'bra' or 'ket' + Args: + bra (Qobj): Quantum object of type 'bra' or 'ket' - ket : :class:`qutip.Qobj` - Quantum object of type 'ket'. + ket (Qobj): Quantum object of type 'ket'. - Returns - ------- - elem : complex - Complex valued matrix element. + Returns: + complex: Complex valued matrix element. - Note - ---- - It is slightly more computationally efficient to use a ket - vector for the 'bra' input. + Raises: + TypeError: Invalid input. """ if not self.isoper: @@ -1343,10 +1315,10 @@ def matrix_element(self, bra, ket): else: if bra.isbra and ket.isket: - return zcsr_mat_elem(self.data,bra.data,ket.data,1) + return zcsr_mat_elem(self.data, bra.data, ket.data, 1) elif bra.isket and ket.isket: - return zcsr_mat_elem(self.data,bra.data,ket.data,0) + return zcsr_mat_elem(self.data, bra.data, ket.data, 0) else: raise TypeError("Can only calculate matrix elements for bra and ket vectors.") @@ -1507,29 +1479,20 @@ def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): Defined for quantum operators or superoperators only. - Parameters - ---------- - sparse : bool - Use sparse Eigensolver - tol : float - Tolerance used by sparse Eigensolver (0 = machine precision). - The sparse solver may not converge if the tolerance is set too low. - maxiter : int - Maximum number of iterations performed by sparse solver (if used). - safe : bool (default=True) - Check for degenerate ground state + Args: + sparse (bool): Use sparse Eigensolver - Returns - ------- - eigval : float - Eigenvalue for the ground state of quantum operator. - eigvec : :class:`qutip.Qobj` - Eigenket for the ground state of quantum operator. + tol (float): Tolerance used by sparse Eigensolver + (0 = machine precision). The sparse solver + may not converge if the tolerance is set too low. - Notes - ----- - The sparse eigensolver is much slower than the dense version. - Use sparse only if memory requirements demand it. + maxiter (int): Maximum number of iterations performed by + sparse solver (if used). + + safe (bool): Check for degenerate ground state + + Returns: + tuple: Eigenenergy and eigenstate of ground state. """ if safe: @@ -1539,10 +1502,11 @@ def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): grndval, grndvec = sp_eigs(self.data, self.isherm, sparse=sparse, eigvals=evals, tol=tol, maxiter=maxiter) if safe: - if tol == 0: tol = 1e-15 + if tol == 0: + tol = 1e-15 if (grndval[1]-grndval[0]) <= 10*tol: print("WARNING: Ground state may be degenerate. " - "Use Q.eigenstates()") + "Use Q.eigenstates()") new_dims = [self.dims[0], [1] * len(self.dims[0])] grndvec = Qobj(grndvec[0], dims=new_dims) grndvec = grndvec / grndvec.norm() @@ -1551,10 +1515,8 @@ def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): def trans(self): """Transposed operator. - Returns - ------- - oper : :class:`qutip.Qobj` - Transpose of input operator. + Returns: + Qobj: Transpose of input operator. """ out = Qobj() @@ -1564,6 +1526,11 @@ def trans(self): @property def isherm(self): + """Is operator Hermitian. + + Returns: + bool: Operator is Hermitian or not. + """ if self._isherm is not None: # used previously computed value @@ -1585,7 +1552,7 @@ def check_isunitary(self): eye_data = fast_identity(self.shape[0]) return not (np.any(np.abs((self.data*self.dag().data - eye_data).data) - > atol) + > atol) or np.any(np.abs((self.dag().data*self.data - eye_data).data) > @@ -1597,6 +1564,11 @@ def check_isunitary(self): @property def isunitary(self): + """Is operator unitary. + + Returns: + bool: Is operator unitary or not. + """ if self._isunitary is not None: # used previously computed value return self._isunitary @@ -1676,24 +1648,24 @@ def evaluate(qobj_list, t, args): Qobj(t) = H0 + H1 * sin(args['w'] * t) - Parameters - ---------- - qobj_list : list - A nested list of Qobj instances and corresponding time-dependent - coefficients. - t : float - The time for which to evaluate the time-dependent Qobj instance. - args : dictionary - A dictionary with parameter values required to evaluate the - time-dependent Qobj intance. + Args: + qobj_list (list): A nested list of Qobj instances and + corresponding time-dependent coefficients. - Returns - ------- - output : :class:`qutip.Qobj` - A Qobj instance that represents the value of qobj_list at time t. + t (float): The time for which to evaluate the time-dependent + Qobj instance. - """ + args (dict): A dictionary with parameter values required + to evaluate the time-dependent Qobj intance. + + Returns: + Qobj: A Qobj instance that represents the value of qobj_list + at time t. + + Raises: + TypeError: Invalid input. + """ q_sum = 0 if isinstance(qobj_list, Qobj): q_sum = qobj_list @@ -1707,6 +1679,7 @@ def evaluate(qobj_list, t, args): q_sum += q[0] * q[1](t, args) elif isinstance(q[1], str): args['t'] = t + # pylint: disable=eval-used q_sum += q[0] * float(eval(q[1], globals(), args)) else: raise TypeError('Unrecognized format for ' + @@ -1720,4 +1693,5 @@ def evaluate(qobj_list, t, args): return q_sum -from ..qutip_lite import states \ No newline at end of file +# pylint: disable=wrong-import-position +from ..qutip_lite import states diff --git a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py index 7c422172bc..4c44e6671d 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py @@ -44,7 +44,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -# pylint: disable=invalid-name +# pylint: disable=invalid-name, len-as-condition, no-name-in-module +# pylint: disable=import-error """ This module contains a collection of routines for operating on sparse @@ -56,16 +57,16 @@ 'sp_one_norm', 'sp_reshape', 'sp_eigs', 'sp_expm', 'sp_permute', 'sp_reverse_permute', 'sp_bandwidth', 'sp_profile'] -import scipy.sparse as sp -import scipy.sparse.linalg as spla import numpy as np import scipy.linalg as la +import scipy.sparse as sp +import scipy.sparse.linalg as spla from scipy.linalg.blas import get_blas_funcs from .cy.sparse_utils import (_sparse_profile, _sparse_permute, - _sparse_reverse_permute, _sparse_bandwidth, - _isdiag, zcsr_one_norm, zcsr_inf_norm) + _sparse_reverse_permute, _sparse_bandwidth, + _isdiag, zcsr_one_norm, zcsr_inf_norm) from .fastsparse import fast_csr_matrix -from .cy.spconvert import (arr_coo2fast, zcsr_reshape) +from .cy.spconvert import (zcsr_reshape) _dznrm2 = get_blas_funcs("znrm2") @@ -82,8 +83,9 @@ def sp_inf_norm(A): """ Infinity norm for sparse matrix """ - return zcsr_inf_norm(A.data, A.indices, - A.indptr, A.shape[0], A.shape[1]) + return zcsr_inf_norm(A.data, A.indices, + A.indptr, A.shape[0], + A.shape[1]) def sp_L2_norm(A): @@ -110,40 +112,36 @@ def sp_one_norm(A): """ One norm for sparse matrix """ - return zcsr_one_norm(A.data, A.indices, - A.indptr, A.shape[0], A.shape[1]) - + return zcsr_one_norm(A.data, A.indices, + A.indptr, A.shape[0], + A.shape[1]) +# pylint: disable=redefined-builtin def sp_reshape(A, shape, format='csr'): """ Reshapes a sparse matrix. - Parameters - ---------- - A : sparse_matrix - Input matrix in any format - shape : list/tuple - Desired shape of new matrix - format : string {'csr','coo','csc','lil'} - Optional string indicating desired output format + Args: + A (sparse): Input matrix in any format - Returns - ------- - B : csr_matrix - Reshaped sparse matrix + shape (list):Desired shape of new matrix - References - ---------- + format (str): Optional string indicating + desired output format - http://stackoverflow.com/questions/16511879/reshape-sparse-matrix-efficiently-python-scipy-0-12 + Returns: + csr_matrix: Reshaped sparse matrix + + Raises: + ValueError: Invalid input. """ if not hasattr(shape, '__len__') or len(shape) != 2: raise ValueError('Shape must be a list of two integers') - + if format == 'csr': return zcsr_reshape(A, shape[0], shape[1]) - + C = A.tocoo() nrows, ncols = C.shape size = nrows * ncols @@ -171,9 +169,6 @@ def _dense_eigs(data, isherm, vecs, N, eigvals, num_large, num_small): Internal functions for computing eigenvalues and eigenstates for a dense matrix. """ - if debug: - logger.debug(inspect.stack()[0][3] + ": vectors = " + str(vecs)) - evecs = None if vecs: @@ -229,8 +224,6 @@ def _sp_eigs(data, isherm, vecs, N, eigvals, num_large, num_small, tol, Internal functions for computing eigenvalues and eigenstates for a sparse matrix. """ - if debug: - print(inspect.stack()[0][3] + ": vectors = " + str(vecs)) big_vals = np.array([]) small_vals = np.array([]) @@ -318,34 +311,33 @@ def sp_eigs(data, isherm, vecs=True, sparse=False, sort='low', """Returns Eigenvalues and Eigenvectors for a sparse matrix. Uses dense eigen-solver unless user sets sparse=True. - Parameters - ---------- - data : csr_matrix - Input matrix - isherm : bool - Indicate whether the matrix is hermitian or not - vecs : bool {True , False} - Flag for requesting eigenvectors - sparse : bool {False , True} - Flag to use sparse solver - sort : str {'low' , 'high} - Return lowest or highest eigenvals/vecs - eigvals : int - Number of eigenvals/vecs to return. Default = 0 (return all) - tol : float - Tolerance for sparse eigensolver. Default = 0 (Machine precision) - maxiter : int - Max. number of iterations used by sparse sigensolver. - - Returns - ------- - Array of eigenvalues and (by default) array of corresponding Eigenvectors. + Args: + data (csr_matrix): Input matrix. - """ + isherm (bool): Indicate whether the matrix is hermitian or not - if debug: - print(inspect.stack()[0][3]) + vecs (bool): Flag for requesting eigenvectors + sparse (bool): Flag to use sparse solver + + sort (str): Return lowest or highest eigenvals/vecs + + eigvals (int): Number of eigenvals/vecs to return. + Default = 0 (return all) + + tol (float): Tolerance for sparse eigensolver. + Default = 0 (Machine precision) + + maxiter (int): Max. number of iterations used by sparse sigensolver. + + Returns: + array: Eigenvalues and (by default) array of corresponding Eigenvectors. + + Raises: + TypeError: Invalid input. + ValueError: Invalid input. + + """ if data.shape[0] != data.shape[1]: raise TypeError("Can only diagonalize square matrices") @@ -393,18 +385,17 @@ def sp_eigs(data, isherm, vecs=True, sparse=False, sort='low', def sp_expm(A, sparse=False): """ - Sparse matrix exponential. + Sparse matrix exponential. """ if _isdiag(A.indices, A.indptr, A.shape[0]): - A = sp.diags(np.exp(A.diagonal()), shape=A.shape, - format='csr', dtype=complex) + A = sp.diags(np.exp(A.diagonal()), shape=A.shape, + format='csr', dtype=complex) return A if sparse: E = spla.expm(A.tocsc()) else: E = spla.expm(A.toarray()) return sp.csr_matrix(E) - def sp_permute(A, rperm=(), cperm=(), safe=True): @@ -414,21 +405,20 @@ def sp_permute(A, rperm=(), cperm=(), safe=True): Here, the permutation arrays specify the new order of the rows and columns. i.e. [0,1,2,3,4] -> [3,0,4,1,2]. - Parameters - ---------- - A : csr_matrix, csc_matrix - Input matrix. - rperm : array_like of integers - Array of row permutations. - cperm : array_like of integers - Array of column permutations. - safe : bool - Check structure of permutation arrays. - - Returns - ------- - perm_csr : csr_matrix, csc_matrix - CSR or CSC matrix with permuted rows/columns. + Args: + A (csr_matrix): Input matrix. + + rperm (ndarray): Array of row permutations. + + cperm (ndarray): Array of column permutations. + + safe (bool): Check structure of permutation arrays. + + Returns: + csr_matrix: CSR matrix with permuted rows/columns. + + Raises: + ValueError: Invalid input. """ rperm = np.asarray(rperm, dtype=np.int32) @@ -441,9 +431,9 @@ def sp_permute(A, rperm=(), cperm=(), safe=True): cperm = np.arange(ncols, dtype=np.int32) if safe: if len(np.setdiff1d(rperm, np.arange(nrows))) != 0: - raise Exception('Invalid row permutation array.') + raise ValueError('Invalid row permutation array.') if len(np.setdiff1d(cperm, np.arange(ncols))) != 0: - raise Exception('Invalid column permutation array.') + raise ValueError('Invalid column permutation array.') shp = A.shape kind = A.getformat() @@ -452,7 +442,7 @@ def sp_permute(A, rperm=(), cperm=(), safe=True): elif kind == 'csc': flag = 1 else: - raise Exception('Input must be Qobj, CSR, or CSC matrix.') + raise ValueError('Input must be Qobj, CSR, or CSC matrix.') data, ind, ptr = _sparse_permute(A.data, A.indices, A.indptr, nrows, ncols, rperm, cperm, flag) @@ -469,21 +459,20 @@ def sp_reverse_permute(A, rperm=(), cperm=(), safe=True): Here, the permutation arrays specify the order of the rows and columns used to permute the original array. - Parameters - ---------- - A : csr_matrix, csc_matrix - Input matrix. - rperm : array_like of integers - Array of row permutations. - cperm : array_like of integers - Array of column permutations. - safe : bool - Check structure of permutation arrays. - - Returns - ------- - perm_csr : csr_matrix, csc_matrix - CSR or CSC matrix with permuted rows/columns. + Args: + A (csr_matrix): Input matrix. + + rperm (ndarray): Array of row permutations. + + cperm (ndarray): Array of column permutations. + + safe (bool): Check structure of permutation arrays. + + Returns: + csr_matrix: CSR matrix with permuted rows/columns. + + Raises: + Exception: Invalid permutation. """ rperm = np.asarray(rperm, dtype=np.int32) @@ -526,19 +515,14 @@ def sp_bandwidth(A): If the matrix is symmetric then the upper and lower bandwidths are identical. Diagonal matrices have a bandwidth equal to one. - Parameters - ---------- - A : csr_matrix, csc_matrix - Input matrix + Args: + A (csr_matrix): Input matrix + + Returns: + tuple: Maximum, lower, and upper bandwidths - Returns - ------- - mb : int - Maximum bandwidth of matrix. - lb : int - Lower bandwidth of matrix. - ub : int - Upper bandwidth of matrix. + Raises: + Exception: Invalid input. """ nrows = A.shape[0] @@ -561,10 +545,14 @@ def sp_profile(A): If the matrix is symmetric then the upper and lower profiles are identical. Diagonal matrices have zero profile. - Parameters - ---------- - A : csr_matrix, csc_matrix - Input matrix + Args: + A (csr_matrix): Input matrix + + Returns: + tuple: Maximum, lower, and upper profiles. + + Raises: + TypeError: Invalid inputs. """ if sp.isspmatrix_csr(A): up = _sparse_profile(A.indices, A.indptr, A.shape[0]) @@ -584,17 +572,16 @@ def sp_profile(A): def sp_isdiag(A): """Determine if sparse CSR matrix is diagonal. - - Parameters - ---------- - A : csr_matrix, csc_matrix - Input matrix - - Returns - ------- - isdiag : int - True if matix is diagonal, False otherwise. - + + Args: + A (csr_matrix); Input matrix + + Returns: + int: True if matix is diagonal, False otherwise. + + Raises: + TypeError: Invalid input. + """ if not sp.isspmatrix_csr(A): raise TypeError('Input sparse matrix must be in CSR format.') diff --git a/qiskit/providers/aer/openpulse/qutip_lite/states.py b/qiskit/providers/aer/openpulse/qutip_lite/states.py index 7808a9a1eb..bce56a0272 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/states.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/states.py @@ -44,6 +44,10 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name + +"""States +""" import numpy as np from scipy import arange, conj @@ -57,45 +61,21 @@ def basis(N, n=0, offset=0): """Generates the vector representation of a Fock state. - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. - - n : int - Integer corresponding to desired number state, defaults - to 0 if omitted. - - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the state. + Args: + N (int): Number of Fock states in Hilbert space. - Returns - ------- - state : :class:`qutip.Qobj` - Qobj representing the requested number state ``|n>``. + n (int): Integer corresponding to desired number + state, defaults to 0 if omitted. - Examples - -------- - >>> basis(5,2) - Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket - Qobj data = - [[ 0.+0.j] - [ 0.+0.j] - [ 1.+0.j] - [ 0.+0.j] - [ 0.+0.j]] + offset (int): The lowest number state that is included + in the finite number state representation + of the state. - Notes - ----- + Returns: + Qobj: Qobj representing the requested number state ``|n>``. - A subtle incompatibility with the quantum optics toolbox: In QuTiP:: - - basis(N, 0) = ground state - - but in the qotoolbox:: - - basis(N, 1) = ground state + Raises: + ValueError: Invalid input value. """ if (not isinstance(N, (int, np.integer))) or N < 0: @@ -109,18 +89,16 @@ def basis(N, n=0, offset=0): data = np.array([1], dtype=complex) ind = np.array([0], dtype=np.int32) - ptr = np.array([0]*((n - offset)+1)+[1]*(N-(n-offset)),dtype=np.int32) + ptr = np.array([0]*((n - offset)+1)+[1]*(N-(n-offset)), dtype=np.int32) - return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,1)), isherm=False) + return Qobj(fast_csr_matrix((data, ind, ptr), shape=(N, 1)), isherm=False) def qutrit_basis(): """Basis states for a three level system (qutrit) - Returns - ------- - qstates : array - Array of qutrit basis vectors + Returns: + array: Array of qutrit basis vectors """ return np.array([basis(3, 0), basis(3, 1), basis(3, 2)], dtype=object) @@ -131,49 +109,22 @@ def coherent(N, alpha, offset=0, method='operator'): Constructed using displacement operator on vacuum state. - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. + Args: + N (int): Number of Fock states in Hilbert space. - alpha : float/complex - Eigenvalue of coherent state. + alpha (complex): Eigenvalue of coherent state. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the state. Using a non-zero offset will make the - default method 'analytic'. + offset (int): The lowest number state that is included in the finite + number state representation of the state. Using a + non-zero offset will make the default method 'analytic'. - method : string {'operator', 'analytic'} - Method for generating coherent state. + method (str): Method for generating coherent state. - Returns - ------- - state : qobj - Qobj quantum object for coherent state - - Examples - -------- - >>> coherent(5,0.25j) - Quantum object: dims = [[5], [1]], shape = [5, 1], type = ket - Qobj data = - [[ 9.69233235e-01+0.j ] - [ 0.00000000e+00+0.24230831j] - [ -4.28344935e-02+0.j ] - [ 0.00000000e+00-0.00618204j] - [ 7.80904967e-04+0.j ]] - - Notes - ----- - Select method 'operator' (default) or 'analytic'. With the - 'operator' method, the coherent state is generated by displacing - the vacuum state using the displacement operator defined in the - truncated Hilbert space of size 'N'. This method guarantees that the - resulting state is normalized. With 'analytic' method the coherent state - is generated using the analytical formula for the coherent state - coefficients in the Fock basis. This method does not guarantee that the - state is normalized if truncated to a small number of Fock states, - but would in that case give more accurate coefficients. + Returns: + Qobj: Qobj quantum object for coherent state + + Raises: + TypeError: Invalid input. """ if method == "operator" and offset == 0: @@ -206,48 +157,21 @@ def coherent_dm(N, alpha, offset=0, method='operator'): Constructed via outer product of :func:`qutip.states.coherent` - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. + Parameters: + N (int): Number of Fock states in Hilbert space. - alpha : float/complex - Eigenvalue for coherent state. + alpha (complex): Eigenvalue for coherent state. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the state. + offset (int): The lowest number state that is included in the + finite number state representation of the state. - method : string {'operator', 'analytic'} - Method for generating coherent density matrix. + method (str): Method for generating coherent density matrix. - Returns - ------- - dm : qobj - Density matrix representation of coherent state. - - Examples - -------- - >>> coherent_dm(3,0.25j) - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 0.93941695+0.j 0.00000000-0.23480733j -0.04216943+0.j ] - [ 0.00000000+0.23480733j 0.05869011+0.j 0.00000000-0.01054025j] - [-0.04216943+0.j 0.00000000+0.01054025j 0.00189294+0.j\ - ]] - - Notes - ----- - Select method 'operator' (default) or 'analytic'. With the - 'operator' method, the coherent density matrix is generated by displacing - the vacuum state using the displacement operator defined in the - truncated Hilbert space of size 'N'. This method guarantees that the - resulting density matrix is normalized. With 'analytic' method the coherent - density matrix is generated using the analytical formula for the coherent - state coefficients in the Fock basis. This method does not guarantee that - the state is normalized if truncated to a small number of Fock states, - but would in that case give more accurate coefficients. + Returns: + Qobj: Density matrix representation of coherent state. + + Raises: + TypeError: Invalid input. """ if method == "operator": @@ -268,28 +192,15 @@ def fock_dm(N, n=0, offset=0): Constructed via outer product of :func:`qutip.states.fock`. - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. + Args: + N (int): Number of Fock states in Hilbert space. - n : int - ``int`` for desired number state, defaults to 0 if omitted. + n (int): Desired number state, defaults to 0 if omitted. - Returns - ------- - dm : qobj - Density matrix representation of Fock state. - - Examples - -------- - >>> fock_dm(3,1) - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 0.+0.j 0.+0.j 0.+0.j] - [ 0.+0.j 1.+0.j 0.+0.j] - [ 0.+0.j 0.+0.j 0.+0.j]] + offset (int): Energy level offset. + + Returns: + Qobj: Density matrix representation of Fock state. """ psi = basis(N, n, offset=offset) @@ -302,27 +213,15 @@ def fock(N, n=0, offset=0): Same as :func:`qutip.states.basis`. - Parameters - ---------- - N : int - Number of states in the Hilbert space. + Args: + N (int): Number of states in the Hilbert space. - n : int - ``int`` for desired number state, defaults to 0 if omitted. + n (int): Desired number state, defaults to 0 if omitted. - Returns - ------- - Requested number state :math:`\\left|n\\right>`. - - Examples - -------- - >>> fock(4,3) - Quantum object: dims = [[4], [1]], shape = [4, 1], type = ket - Qobj data = - [[ 0.+0.j] - [ 0.+0.j] - [ 0.+0.j] - [ 1.+0.j]] + offset (int): Energy level offset. + + Returns: + Qobj: Requested number state :math:`\\left|n\\right>`. """ return basis(N, n, offset=offset) @@ -331,54 +230,20 @@ def fock(N, n=0, offset=0): def thermal_dm(N, n, method='operator'): """Density matrix for a thermal state of n particles - Parameters - ---------- - N : int - Number of basis states in Hilbert space. + Args: + N (int): Number of basis states in Hilbert space. - n : float - Expectation value for number of particles in thermal state. + n (float): Expectation value for number of particles + in thermal state. - method : string {'operator', 'analytic'} - ``string`` that sets the method used to generate the - thermal state probabilities + method (str): Sets the method used to generate the + thermal state probabilities - Returns - ------- - dm : qobj - Thermal state density matrix. - - Examples - -------- - >>> thermal_dm(5, 1) - Quantum object: dims = [[5], [5]], \ -shape = [5, 5], type = oper, isHerm = True - Qobj data = - [[ 0.51612903 0. 0. 0. 0. ] - [ 0. 0.25806452 0. 0. 0. ] - [ 0. 0. 0.12903226 0. 0. ] - [ 0. 0. 0. 0.06451613 0. ] - [ 0. 0. 0. 0. 0.03225806]] - - - >>> thermal_dm(5, 1, 'analytic') - Quantum object: dims = [[5], [5]], \ -shape = [5, 5], type = oper, isHerm = True - Qobj data = - [[ 0.5 0. 0. 0. 0. ] - [ 0. 0.25 0. 0. 0. ] - [ 0. 0. 0.125 0. 0. ] - [ 0. 0. 0. 0.0625 0. ] - [ 0. 0. 0. 0. 0.03125]] - - Notes - ----- - The 'operator' method (default) generates - the thermal state using the truncated number operator ``num(N)``. This - is the method that should be used in computations. The - 'analytic' method uses the analytic coefficients derived in - an infinite Hilbert space. The analytic form is not necessarily normalized, - if truncated too aggressively. + Returns: + Qobj: Thermal state density matrix. + + Raises: + ValueError: Invalid input. """ if n == 0: @@ -406,15 +271,14 @@ def maximally_mixed_dm(N): Returns the maximally mixed density matrix for a Hilbert space of dimension N. - Parameters - ---------- - N : int - Number of basis states in Hilbert space. + Args: + N (int): Number of basis states in Hilbert space. - Returns - ------- - dm : qobj - Thermal state density matrix. + Returns: + Qobj: Thermal state density matrix. + + Raises: + ValueError: Invalid input. """ if (not isinstance(N, (int, np.int64))) or N <= 0: raise ValueError("N must be integer N > 0") @@ -428,27 +292,14 @@ def ket2dm(Q): """Takes input ket or bra vector and returns density matrix formed by outer product. - Parameters - ---------- - Q : qobj - Ket or bra type quantum object. + Args: + Q (Qobj): Ket or bra type quantum object. - Returns - ------- - dm : qobj - Density matrix formed by outer product of `Q`. - - Examples - -------- - >>> x=basis(3,2) - >>> ket2dm(x) - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 0.+0.j 0.+0.j 0.+0.j] - [ 0.+0.j 0.+0.j 0.+0.j] - [ 0.+0.j 0.+0.j 1.+0.j]] + Returns: + Qobj: Density matrix formed by outer product of `Q`. + Raises: + TypeError: Invalid input. """ if Q.type == 'ket': out = Q * Q.dag() @@ -465,22 +316,18 @@ def ket2dm(Q): def projection(N, n, m, offset=0): """The projection operator that projects state :math:`|m>` on state :math:`|n>`. - Parameters - ---------- - N : int - Number of basis states in Hilbert space. + Args: + N (int): Number of basis states in Hilbert space. - n, m : float - The number states in the projection. + n (float): The number states in the projection. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the projector. + m (float): The number states in the projection. - Returns - ------- - oper : qobj - Requested projection operator. + offset (int): The lowest number state that is included in + the finite number state representation of the projector. + + Returns: + Qobj: Requested projection operator. """ ket1 = basis(N, n, offset=offset) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py index 6afa16d543..8d701db624 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -56,7 +56,7 @@ from .sparse import sp_reshape from .cy.spmath import zcsr_kron # pylint: disable=no-name-in-module - +# pylint: disable=dangerous-default-value def liouvillian(H, c_ops=[], data_only=False, chi=None): """Assembles the Liouvillian superoperator from a Hamiltonian and a ``list`` of collapse operators. @@ -64,13 +64,18 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): Args: H (qobj.Qobj): System Hamiltonian. - c_ops (qobj.Qobj or array_like): A single collapse operator or an array. + c_ops (qobj.Qobj or array_like): A single collapse operator + or an array. + data_only (bool): Return data only. + + chi (flaot): Multiplication factor. Returns: qobj.Qobj: Liouvillian superoperator. Raises: ValueError: Chi must be list of len(c_ops). + TypeError: Invalidinput types. """ if isinstance(c_ops, (Qobj)): @@ -158,18 +163,18 @@ def lindblad_dissipator(a, b=None, data_only=False, chi=None): \\mathcal{D}[a,b]\\rho = a \\rho b^\\dagger - \\frac{1}{2}a^\\dagger b\\rho - \\frac{1}{2}\\rho a^\\dagger b - Parameters - ---------- - a : Qobj or QobjEvo - Left part of collapse operator. + Args: + a (Qobj): Left part of collapse operator. - b : Qobj or QobjEvo (optional) - Right part of collapse operator. If not specified, b defaults to a. + b (Qobj): Right part of collapse operator. If not specified, + b defaults to a. - Returns - ------- - D : qobj, QobjEvo - Lindblad dissipator superoperator. + data_only (bool): Return data only. + + chi (flaot): Multiplication factor. + + Returns: + Qobj: Lindblad dissipator superoperator. """ if b is None: b = a @@ -242,15 +247,14 @@ def mat2vec_index(N, i, j): def spost(A): """Superoperator formed from post-multiplication by operator A - Parameters - ---------- - A : Qobj or QobjEvo - Quantum operator for post multiplication. + Args: + A (Qobj): Quantum operator for post multiplication. - Returns - ------- - super : Qobj or QobjEvo - Superoperator formed from input qauntum object. + Returns: + Qobj: Superoperator formed from input qauntum object. + + Raises: + TypeError: Invalid inputs. """ if not isinstance(A, Qobj): raise TypeError('Input is not a quantum object') @@ -268,15 +272,14 @@ def spost(A): def spre(A): """Superoperator formed from pre-multiplication by operator A. - Parameters - ---------- - A : Qobj or QobjEvo - Quantum operator for pre-multiplication. + Args: + A (Qobj): Quantum operator for pre-multiplication. - Returns - -------- - super :Qobj or QobjEvo - Superoperator formed from input quantum object. + Returns: + Qobj: Superoperator formed from input quantum object. + + Raises: + TypeError: Invalid input type. """ if not isinstance(A, Qobj): raise TypeError('Input is not a quantum object') From b416b2145a91dd5e281550776423f4baf7409815 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Fri, 9 Aug 2019 16:24:08 -0400 Subject: [PATCH 26/31] lint operators --- .../aer/openpulse/qutip_lite/operators.py | 543 +++++------------- 1 file changed, 152 insertions(+), 391 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/operators.py b/qiskit/providers/aer/openpulse/qutip_lite/operators.py index 6dac4db91e..b26e643220 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/operators.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/operators.py @@ -45,6 +45,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name + """ This module contains functions for generating Qobj representation of a variety of commonly occuring quantum operators. @@ -61,48 +63,17 @@ def jmat(j, *args): """Higher-order spin operators: - Parameters - ---------- - j : float - Spin of operator - - args : str - Which operator to return 'x','y','z','+','-'. - If no args given, then output is ['x','y','z'] + Args: + j (float): Spin of operator - Returns - ------- - jmat : qobj / ndarray - ``qobj`` for requested spin operator(s). + args (str): Which operator to return 'x','y','z','+','-'. + If no args given, then output is ['x','y','z'] + Returns: + Qobj: Requested spin operator(s). - Examples - -------- - >>> jmat(1) - [ Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 0. 0.70710678 0. ] - [ 0.70710678 0. 0.70710678] - [ 0. 0.70710678 0. ]] - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 0.+0.j 0.-0.70710678j 0.+0.j ] - [ 0.+0.70710678j 0.+0.j 0.-0.70710678j] - [ 0.+0.j 0.+0.70710678j 0.+0.j ]] - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 1. 0. 0.] - [ 0. 0. 0.] - [ 0. 0. -1.]]] - - - Notes - ----- - If no 'args' input, then returns array of ['x','y','z'] operators. - + Raises: + TypeError: Invalid input. """ if (scipy.fix(2 * j) != 2 * j) or (j < 0): raise TypeError('j must be a non-negative integer or half-integer') @@ -137,7 +108,7 @@ def _jplus(j): ind = np.arange(1, N, dtype=np.int32) ptr = np.array(list(range(N-1))+[N-1]*2, dtype=np.int32) ptr[-1] = N-1 - return fast_csr_matrix((data,ind,ptr), shape=(N,N)) + return fast_csr_matrix((data, ind, ptr), shape=(N, N)) def _jz(j): @@ -145,19 +116,19 @@ def _jz(j): Internal functions for generating the data representing the J-z operator. """ N = int(2*j+1) - data = np.array([j-k for k in range(N) if (j-k)!=0], dtype=complex) + data = np.array([j-k for k in range(N) if (j-k) != 0], dtype=complex) # Even shaped matrix - if (N % 2 == 0): + if N % 2 == 0: ind = np.arange(N, dtype=np.int32) - ptr = np.arange(N+1,dtype=np.int32) + ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N # Odd shaped matrix else: j = int(j) - ind = np.array(list(range(j))+list(range(j+1,N)), dtype=np.int32) - ptr = np.array(list(range(j+1))+list(range(j,N)), dtype=np.int32) + ind = np.array(list(range(j))+list(range(j+1, N)), dtype=np.int32) + ptr = np.array(list(range(j+1))+list(range(j, N)), dtype=np.int32) ptr[-1] = N-1 - return fast_csr_matrix((data,ind,ptr), shape=(N,N)) + return fast_csr_matrix((data, ind, ptr), shape=(N, N)) # @@ -183,15 +154,11 @@ def spin_Jx(j): def spin_Jy(j): """Spin-j y operator - Parameters - ---------- - j : float - Spin of operator + Args: + j (float): Spin of operator - Returns - ------- - op : Qobj - ``qobj`` representation of the operator. + Returns: + Qobj: representation of the operator. """ return jmat(j, 'y') @@ -200,15 +167,11 @@ def spin_Jy(j): def spin_Jz(j): """Spin-j z operator - Parameters - ---------- - j : float - Spin of operator + Args: + j (float): Spin of operator - Returns - ------- - op : Qobj - ``qobj`` representation of the operator. + Returns: + Qobj: representation of the operator. """ return jmat(j, 'z') @@ -217,15 +180,11 @@ def spin_Jz(j): def spin_Jm(j): """Spin-j annihilation operator - Parameters - ---------- - j : float - Spin of operator + Parameters: + j (float): Spin of operator - Returns - ------- - op : Qobj - ``qobj`` representation of the operator. + Returns: + Qobj: representation of the operator. """ return jmat(j, '-') @@ -234,15 +193,11 @@ def spin_Jm(j): def spin_Jp(j): """Spin-j creation operator - Parameters - ---------- - j : float - Spin of operator + Args: + j (float): Spin of operator - Returns - ------- - op : Qobj - ``qobj`` representation of the operator. + Returns: + Qobj: representation of the operator. """ return jmat(j, '+') @@ -251,15 +206,11 @@ def spin_Jp(j): def spin_J_set(j): """Set of spin-j operators (x, y, z) - Parameters - ---------- - j : float - Spin of operators + Args: + j (float): Spin of operators - Returns - ------- - list : list of Qobj - list of ``qobj`` representating of the spin operator. + Returns: + list: list of ``qobj`` representating of the spin operator. """ return jmat(j) @@ -353,41 +304,28 @@ def sigmaz(): # out = destroy(N), N is integer value & N>0 # def destroy(N, offset=0): - '''Destruction (lowering) operator. + """Destruction (lowering) operator. - Parameters - ---------- - N : int - Dimension of Hilbert space. + Args: + N (int): Dimension of Hilbert space. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + offset (int): (default 0) The lowest number state that is included + in the finite number state representation of the operator. - Returns - ------- - oper : qobj - Qobj for lowering operator. + Returns: + Qobj: Qobj for lowering operator. - Examples - -------- - >>> destroy(4) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isHerm = False - Qobj data = - [[ 0.00000000+0.j 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j] - [ 0.00000000+0.j 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j] - [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j] - [ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j]] + Raises: + ValueError: Invalid input. - ''' + """ if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) - ind = np.arange(1,N, dtype=np.int32) + ind = np.arange(1, N, dtype=np.int32) ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N-1 - return Qobj(fast_csr_matrix((data, ind, ptr),shape=(N,N)), isherm=False) + return Qobj(fast_csr_matrix((data, ind, ptr), shape=(N, N)), isherm=False) # @@ -395,34 +333,21 @@ def destroy(N, offset=0): # out = create(N), N is integer value & N>0 # def create(N, offset=0): - '''Creation (raising) operator. + """Creation (raising) operator. - Parameters - ---------- - N : int - Dimension of Hilbert space. + Args: + N (int): Dimension of Hilbert space. - Returns - ------- - oper : qobj - Qobj for raising operator. + offset (int): (default 0) The lowest number state that is included + in the finite number state representation of the operator. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + Returns: + Qobj: Qobj for raising operator. - Examples - -------- - >>> create(4) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isHerm = False - Qobj data = - [[ 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j] - [ 1.00000000+0.j 0.00000000+0.j 0.00000000+0.j 0.00000000+0.j] - [ 0.00000000+0.j 1.41421356+0.j 0.00000000+0.j 0.00000000+0.j] - [ 0.00000000+0.j 0.00000000+0.j 1.73205081+0.j 0.00000000+0.j]] + Raises: + ValueError: Invalid inputs. - ''' + """ if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") qo = destroy(N, offset=offset) # create operator using destroy function @@ -437,28 +362,16 @@ def qeye(N): """ Identity operator - Parameters - ---------- - N : int or list of ints - Dimension of Hilbert space. If provided as a list of ints, - then the dimension is the product over this list, but the - ``dims`` property of the new Qobj are set to this list. - - Returns - ------- - oper : qobj - Identity operator Qobj. + Args: + N (int): Dimension of Hilbert space. If provided as a list of ints, + then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. - Examples - -------- - >>> qeye(3) - Quantum object: dims = [[3], [3]], \ -shape = [3, 3], type = oper, isHerm = True - Qobj data = - [[ 1. 0. 0.] - [ 0. 1. 0.] - [ 0. 0. 1.]] + Returns: + Qobj: Identity operator Qobj. + Raises: + ValueError: Invalid input. """ N = int(N) if N < 0: @@ -488,19 +401,13 @@ def position(N, offset=0): """ Position operator x=1/sqrt(2)*(a+a.dag()) - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. - - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + Args: + N (int): Number of Fock states in Hilbert space. - Returns - ------- - oper : qobj - Position operator as Qobj. + offset (int): (default 0) The lowest number state that is included + in the finite number state representation of the operator. + Returns: + Qobj: Position operator as Qobj. """ a = destroy(N, offset=offset) return 1.0 / np.sqrt(2.0) * (a + a.dag()) @@ -510,19 +417,14 @@ def momentum(N, offset=0): """ Momentum operator p=-1j/sqrt(2)*(a-a.dag()) - Parameters - ---------- - N : int - Number of Fock states in Hilbert space. - - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + Args: + N (int): Number of Fock states in Hilbert space. - Returns - ------- - oper : qobj - Momentum operator as Qobj. + offset (int): (default 0) The lowest number state that is + included in the finite number state + representation of the operator. + Returns: + Qobj: Momentum operator as Qobj. """ a = destroy(N, offset=offset) return -1j / np.sqrt(2.0) * (a - a.dag()) @@ -531,78 +433,44 @@ def momentum(N, offset=0): def num(N, offset=0): """Quantum object for number operator. - Parameters - ---------- - N : int - The dimension of the Hilbert space. + Args: + N (int): The dimension of the Hilbert space. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + offset(int): (default 0) The lowest number state that is included + in the finite number state representation of the operator. - Returns - ------- - oper: qobj - Qobj for number operator. - - Examples - -------- - >>> num(4) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isHerm = True - Qobj data = - [[0 0 0 0] - [0 1 0 0] - [0 0 2 0] - [0 0 0 3]] + Returns: + Qobj: Qobj for number operator. """ if offset == 0: - data = np.arange(1,N, dtype=complex) - ind = np.arange(1,N, dtype=np.int32) - ptr = np.array([0]+list(range(0,N)), dtype=np.int32) + data = np.arange(1, N, dtype=complex) + ind = np.arange(1, N, dtype=np.int32) + ptr = np.array([0]+list(range(0, N)), dtype=np.int32) ptr[-1] = N-1 else: data = np.arange(offset, offset + N, dtype=complex) ind = np.arange(N, dtype=np.int32) - ptr = np.arange(N+1,dtype=np.int32) + ptr = np.arange(N+1, dtype=np.int32) ptr[-1] = N - return Qobj(fast_csr_matrix((data,ind,ptr), shape=(N,N)), isherm=True) + return Qobj(fast_csr_matrix((data, ind, ptr), + shape=(N, N)), isherm=True) def squeeze(N, z, offset=0): """Single-mode Squeezing operator. + Args: + N (int): Dimension of hilbert space. - Parameters - ---------- - N : int - Dimension of hilbert space. - - z : float/complex - Squeezing parameter. - - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + z (complex): Squeezing parameter. - Returns - ------- - oper : :class:`qutip.qobj.Qobj` - Squeezing operator. + offset (int): (default 0) The lowest number state that is included + in the finite number state representation of the operator. - - Examples - -------- - >>> squeeze(4, 0.25) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isHerm = False - Qobj data = - [[ 0.98441565+0.j 0.00000000+0.j 0.17585742+0.j 0.00000000+0.j] - [ 0.00000000+0.j 0.95349007+0.j 0.00000000+0.j 0.30142443+0.j] - [-0.17585742+0.j 0.00000000+0.j 0.98441565+0.j 0.00000000+0.j] - [ 0.00000000+0.j -0.30142443+0.j 0.00000000+0.j 0.95349007+0.j]] + Returns: + Qobj:`Squeezing operator. """ a = destroy(N, offset=offset) @@ -613,26 +481,15 @@ def squeeze(N, z, offset=0): def squeezing(a1, a2, z): """Generalized squeezing operator. - .. math:: + Args: + a1 (Qobj): Operator 1. - S(z) = \\exp\\left(\\frac{1}{2}\\left(z^*a_1a_2 - - za_1^\\dagger a_2^\\dagger\\right)\\right) + a2 (Qobj): Operator 2. - Parameters - ---------- - a1 : :class:`qutip.qobj.Qobj` - Operator 1. + z (complex): Squeezing parameter. - a2 : :class:`qutip.qobj.Qobj` - Operator 2. - - z : float/complex - Squeezing parameter. - - Returns - ------- - oper : :class:`qutip.qobj.Qobj` - Squeezing operator. + Returns: + Qobj: Squeezing operator. """ b = 0.5 * (np.conj(z) * (a1 * a2) - z * (a1.dag() * a2.dag())) @@ -642,34 +499,17 @@ def squeezing(a1, a2, z): def displace(N, alpha, offset=0): """Single-mode displacement operator. - Parameters - ---------- - N : int - Dimension of Hilbert space. - - alpha : float/complex - Displacement amplitude. + Args: + N (int): Dimension of Hilbert space. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. + alpha (complex): Displacement amplitude. - Returns - ------- - oper : qobj - Displacement operator. - - Examples - --------- - >>> displace(4,0.25) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isHerm = False - Qobj data = - [[ 0.96923323+0.j -0.24230859+0.j 0.04282883+0.j -0.00626025+0.j] - [ 0.24230859+0.j 0.90866411+0.j -0.33183303+0.j 0.07418172+0.j] - [ 0.04282883+0.j 0.33183303+0.j 0.84809499+0.j -0.41083747+0.j] - [ 0.00626025+0.j 0.07418172+0.j 0.41083747+0.j 0.90866411+0.j]] + offset (int): The lowest number state that is included + in the finite number state + representation of the operator. + Returns: + Qobj: Displacement operator. """ a = destroy(N, offset=offset) D = (alpha * a.dag() - np.conj(alpha) * a).expm() @@ -680,6 +520,19 @@ def commutator(A, B, kind="normal"): """ Return the commutator of kind `kind` (normal, anti) of the two operators A and B. + + Args: + A (Qobj): Operator A. + + B (Qobj): Operator B. + + kind (str): 'normal' or 'anti' commutator. + + Returns: + Qobj: Commutator + + Raises: + TypeError: Invalid input. """ if kind == 'normal': return A * B - B * A @@ -691,93 +544,20 @@ def commutator(A, B, kind="normal"): raise TypeError("Unknown commutator kind '%s'" % kind) -def qutrit_ops(): - """ - Operators for a three level system (qutrit). - - Returns - ------- - opers: array - `array` of qutrit operators. - - """ - from qutip.states import qutrit_basis - - one, two, three = qutrit_basis() - sig11 = one * one.dag() - sig22 = two * two.dag() - sig33 = three * three.dag() - sig12 = one * two.dag() - sig23 = two * three.dag() - sig31 = three * one.dag() - return np.array([sig11, sig22, sig33, sig12, sig23, sig31], - dtype=object) - - -def qdiags(diagonals, offsets, dims=None, shape=None): - """ - Constructs an operator from an array of diagonals. - - Parameters - ---------- - diagonals : sequence of array_like - Array of elements to place along the selected diagonals. - - offsets : sequence of ints - Sequence for diagonals to be set: - - k=0 main diagonal - - k>0 kth upper diagonal - - k<0 kth lower diagonal - dims : list, optional - Dimensions for operator - - shape : list, tuple, optional - Shape of operator. If omitted, a square operator large enough - to contain the diagonals is generated. - - See Also - -------- - scipy.sparse.diags : for usage information. - - Notes - ----- - This function requires SciPy 0.11+. - - Examples - -------- - >>> qdiags(sqrt(range(1, 4)), 1) - Quantum object: dims = [[4], [4]], \ -shape = [4, 4], type = oper, isherm = False - Qobj data = - [[ 0. 1. 0. 0. ] - [ 0. 0. 1.41421356 0. ] - [ 0. 0. 0. 1.73205081] - [ 0. 0. 0. 0. ]] - - """ - data = sp.diags(diagonals, offsets, shape, format='csr', dtype=complex) - if not dims: - dims = [[], []] - if not shape: - shape = [] - return Qobj(data, dims, list(shape)) - def qzero(N): """ Zero operator - Parameters - ---------- - N : int or list of ints - Dimension of Hilbert space. If provided as a list of ints, - then the dimension is the product over this list, but the - ``dims`` property of the new Qobj are set to this list. - - Returns - ------- - qzero : qobj - Zero operator Qobj. + Args: + N (int or list): Dimension of Hilbert space. If provided as a + list of ints, then the dimension is the product + over this list, but the ``dims`` property of the + new Qobj are set to this list. + Returns: + Qobj: Zero operator Qobj. + Raises: + ValueError: Invalid input. """ N = int(N) if (not isinstance(N, (int, np.integer))) or N < 0: @@ -785,31 +565,20 @@ def qzero(N): return Qobj(sp.csr_matrix((N, N), dtype=complex), isherm=True) -def charge(Nmax, Nmin=None, frac = 1): +def charge(Nmax, Nmin=None, frac=1): """ Generate the diagonal charge operator over charge states from Nmin to Nmax. - Parameters - ---------- - Nmax : int - Maximum charge state to consider. - - Nmin : int (default = -Nmax) - Lowest charge state to consider. + Args: + Nmax (int): Maximum charge state to consider. - frac : float (default = 1) - Specify fractional charge if needed. - - Returns - ------- - C : Qobj - Charge operator over [Nmin,Nmax]. + Nmin (int): (default = -Nmax) Lowest charge state to consider. - Notes - ----- - .. versionadded:: 3.2 + frac (float): (default = 1) Specify fractional charge if needed. + Returns: + Qobj: Charge operator over [Nmin,Nmax]. """ if Nmin is None: Nmin = -Nmax @@ -824,27 +593,19 @@ def charge(Nmax, Nmin=None, frac = 1): def tunneling(N, m=1): """ Tunneling operator with elements of the form - :math:`\sum |N> Date: Sat, 10 Aug 2019 05:02:07 -0400 Subject: [PATCH 27/31] lint --- .../aer/openpulse/qutip_lite/dimensions.py | 242 ++---------------- .../aer/openpulse/qutip_lite/expect.py | 9 +- .../aer/openpulse/qutip_lite/fastsparse.py | 1 + .../aer/openpulse/qutip_lite/qobj.py | 1 + 4 files changed, 29 insertions(+), 224 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py index 5a7ff4c0ba..836a4d5261 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py @@ -38,8 +38,25 @@ # by default. import numpy as np -from operator import getitem -from functools import partial + +def flatten(l): + """Flattens a list of lists to the first level. + + Given a list containing a mix of scalars and lists, + flattens down to a list of the scalars within the original + list. + + Args: + l (list): Input list + + Returns: + list: Flattened list. + + """ + if not isinstance(l, list): + return [l] + else: + return sum(map(flatten, l), []) def is_scalar(dims): """ @@ -93,76 +110,6 @@ def type_from_dims(dims, enforce_square=True): return 'other' -def flatten(l): - """Flattens a list of lists to the first level. - - Given a list containing a mix of scalars and lists, - flattens down to a list of the scalars within the original - list. - - Examples - -------- - - >>> print(flatten([[[0], 1], 2])) - [0, 1, 2] - - """ - if not isinstance(l, list): - return [l] - else: - return sum(map(flatten, l), []) - - -def deep_remove(l, *what): - """Removes scalars from all levels of a nested list. - - Given a list containing a mix of scalars and lists, - returns a list of the same structure, but where one or - more scalars have been removed. - - Examples - -------- - - >>> print(deep_remove([[[[0, 1, 2]], [3, 4], [5], [6, 7]]], 0, 5)) - [[[[1, 2]], [3, 4], [], [6, 7]]] - - """ - if isinstance(l, list): - # Make a shallow copy at this level. - l = l[:] - for to_remove in what: - if to_remove in l: - l.remove(to_remove) - else: - l = list(map(lambda elem: deep_remove(elem, to_remove), l)) - return l - - -def unflatten(l, idxs): - """Unflattens a list by a given structure. - - Given a list of scalars and a deep list of indices - as produced by `flatten`, returns an "unflattened" - form of the list. This perfectly inverts `flatten`. - - Examples - -------- - - >>> l = [[[10, 20, 30], [40, 50, 60]], [[70, 80, 90], [100, 110, 120]]] - >>> idxs = enumerate_flat(l) - >>> print(unflatten(flatten(l)), idxs) == l - True - - """ - acc = [] - for idx in idxs: - if isinstance(idx, list): - acc.append(unflatten(l, idx)) - else: - acc.append(l[idx]) - return acc - - def _enumerate_flat(l, idx=0): if not isinstance(l, list): # Found a scalar, so return and increment. @@ -197,29 +144,6 @@ def _collapse_dims_to_level(dims, level=1): return _collapse_composite_index(dims) else: return [_collapse_dims_to_level(index, level=level - 1) for index in dims] - -def collapse_dims_oper(dims): - """ - Given the dimensions specifications for a ket-, bra- or oper-type - Qobj, returns a dimensions specification describing the same shape - by collapsing all composite systems. For instance, the bra-type - dimensions specification ``[[2, 3], [1]]`` collapses to - ``[[6], [1]]``. - - Parameters - ---------- - - dims : list of lists of ints - Dimensions specifications to be collapsed. - - Returns - ------- - - collapsed_dims : list of lists of ints - Collapsed dimensions specification describing the same shape - such that ``len(collapsed_dims[0]) == len(collapsed_dims[1]) == 1``. - """ - return _collapse_dims_to_level(dims, 1) def collapse_dims_super(dims): """ @@ -229,11 +153,8 @@ def collapse_dims_super(dims): dimensions specification ``[[[2, 3], [2, 3]], [[2, 3], [2, 3]]]`` collapses to ``[[[6], [6]], [[6], [6]]]``. - Parameters - ---------- - - dims : list of lists of ints - Dimensions specifications to be collapsed. + Args: + dims (list): Dimensions specifications to be collapsed. Returns ------- @@ -245,6 +166,7 @@ def collapse_dims_super(dims): """ return _collapse_dims_to_level(dims, 2) + def enumerate_flat(l): """Labels the indices at which scalars occur in a flattened list. @@ -260,123 +182,3 @@ def enumerate_flat(l): """ return _enumerate_flat(l)[0] - - -def deep_map(fn, collection, over=(tuple, list)): - if isinstance(collection, over): - return type(collection)(deep_map(fn, el, over) for el in collection) - else: - return fn(collection) - - -def dims_to_tensor_perm(dims): - """ - Given the dims of a Qobj instance, returns a list representing - a permutation from the flattening of that dims specification to - the corresponding tensor indices. - - Parameters - ---------- - - dims : list - Dimensions specification for a Qobj. - - Returns - ------- - - perm : list - A list such that ``data[flatten(dims)[idx]]`` gives the - index of the tensor ``data`` corresponding to the ``idx``th - dimension of ``dims``. - """ - # We figure out the type of the dims specification, - # relaxing the requirement that operators be square. - # This means that dims_type need not coincide with - # Qobj.type, but that works fine for our purposes here. - dims_type = type_from_dims(dims, enforce_square=False) - perm = enumerate_flat(dims) - - # If type is oper, ket or bra, we don't need to do anything. - if dims_type in ('oper', 'ket', 'bra'): - return flatten(perm) - - # If the type is other, we need to figure out if the - # dims is superlike on its outputs and inputs - # This is the case if the dims type for left or right - # are, respectively, oper-like. - if dims_type == 'other': - raise NotImplementedError("Not yet implemented for type='other'.") - - # If we're still here, the story is more complicated. We'll - # follow the strategy of creating a permutation by using - # enumerate_flat then transforming the result to swap - # input and output indices of vectorized matrices, then flattening - # the result. We'll then rebuild indices using this permutation. - - - if dims_type in ('operator-ket', 'super'): - # Swap the input and output spaces of the right part of - # perm. - perm[1] = list(reversed(perm[1])) - - if dims_type in ('operator-bra', 'super'): - # Ditto, but for the left indices. - perm[0] = list(reversed(perm[0])) - - return flatten(perm) - -def dims_to_tensor_shape(dims): - """ - Given the dims of a Qobj instance, returns the shape of the - corresponding tensor. This helps, for instance, resolve the - column-stacking convention for superoperators. - - Parameters - ---------- - - dims : list - Dimensions specification for a Qobj. - - Returns - ------- - - tensor_shape : tuple - NumPy shape of the corresponding tensor. - """ - - perm = dims_to_tensor_perm(dims) - dims = flatten(dims) - - return tuple(map(partial(getitem, dims), perm)) - - -def dims_idxs_to_tensor_idxs(dims, indices): - """ - Given the dims of a Qobj instance, and some indices into - dims, returns the corresponding tensor indices. This helps - resolve, for instance, that column-stacking for superoperators, - oper-ket and oper-bra implies that the input and output tensor - indices are reversed from their order in dims. - - Parameters - ---------- - - dims : list - Dimensions specification for a Qobj. - - indices : int, list or tuple - Indices to convert to tensor indices. Can be specified - as a single index, or as a collection of indices. - In the latter case, this can be nested arbitrarily - deep. For instance, [0, [0, (2, 3)]]. - - Returns - ------- - - tens_indices : int, list or tuple - Container of the same structure as indices containing - the tensor indices for each element of indices. - """ - - perm = dims_to_tensor_perm(dims) - return deep_map(partial(getitem, perm), indices) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/expect.py b/qiskit/providers/aer/openpulse/qutip_lite/expect.py index 4fc74bddda..edaeba7be1 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/expect.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/expect.py @@ -53,7 +53,8 @@ __all__ = ['expect', 'variance'] import numpy as np -from .qobj import Qobj, isoper +from .qobj import Qobj +# pylint: disable=import-error, no-name-in-module from .cy.spmatfuncs import (cy_expect_rho_vec, cy_expect_psi, cy_spmm_tr, expect_csr_ket) @@ -65,10 +66,10 @@ def expect(oper, state): """Calculates the expectation value for operator(s) and state(s). Args: - oper (qobj.Qobj or list): A single or a `list` or operators + oper (Qobj or list): A single or a `list` or operators for expectation value. - state (qobj.Qobj or list): A single or a `list` of quantum states + state (Qobj or list): A single or a `list` of quantum states or density matrices. Returns: @@ -109,7 +110,7 @@ def _single_qobj_expect(oper, state): """ Private function used by expect to calculate expectation values of Qobjs. """ - if isoper(oper): + if oper.isoper: if oper.dims[1] != state.dims[0]: raise Exception('Operator and state do not have same tensor ' + 'structure: %s and %s' % diff --git a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py index 110602827d..353111401f 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py @@ -431,4 +431,5 @@ def _all_true(shape): #Need to do some trailing imports here #------------------------------------- +# pylint: disable=no-name-in-module, wrong-import-position from .cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_mult) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index e44118096f..57c05cf702 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -1693,5 +1693,6 @@ def evaluate(qobj_list, t, args): return q_sum + # pylint: disable=wrong-import-position from ..qutip_lite import states From 44411359d3898c16bf97e49e933d43cf4b4fb175 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 13 Aug 2019 09:31:36 -0400 Subject: [PATCH 28/31] lint --- qiskit/providers/aer/openpulse/qobj/digest.py | 106 +++++++++++------- .../providers/aer/openpulse/qobj/op_system.py | 4 +- .../providers/aer/openpulse/qobj/operators.py | 88 +++++++-------- .../providers/aer/openpulse/qobj/opparse.py | 10 +- 4 files changed, 114 insertions(+), 94 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 5717bd535d..92fea7be83 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -11,11 +11,15 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=eval-used, exec-used, invalid-name + +"""A module of routines for digesting a PULSE qobj +into something we can actually use. +""" from collections import OrderedDict import numpy as np import numpy.linalg as la -from qiskit.exceptions import QiskitError from .op_system import OPSystem from .opparse import HamiltonianParser, NoiseParser from .operators import qubit_occ_oper_dressed @@ -24,9 +28,6 @@ from ..cy.utils import oplist_to_array from . import op_qobj as op -"""A module of routines for digesting a PULSE qobj -into something we can actually use. -""" def digest_pulse_obj(qobj): @@ -38,6 +39,10 @@ def digest_pulse_obj(qobj): Returns: OPSystem: The parsed qobj. + + Raises: + Exception: Invalid options + ValueError: Invalid channel selection. """ # Output data object out = OPSystem() @@ -76,12 +81,12 @@ def digest_pulse_obj(qobj): out.global_data['memory_slots'] = config_dict['memory_slots'] else: err_str = 'Number of memory_slots must be specific in Qobj config' - raise QiskitError(err_str) + raise ValueError(err_str) if 'memory' in config_keys: out.global_data['memory'] = config_dict['memory'] else: - out.global_data['memory'] = False + out.global_data['memory'] = False out.global_data['n_registers'] = 0 if 'n_registers' in config_keys: @@ -95,8 +100,8 @@ def digest_pulse_obj(qobj): # Attach the ODE options allowed_ode_options = ['atol', 'rtol', 'nsteps', 'max_step', - 'num_cpus', 'norm_tol', 'norm_steps', - 'rhs_reuse', 'rhs_filename'] + 'num_cpus', 'norm_tol', 'norm_steps', + 'rhs_reuse', 'rhs_filename'] user_set_ode_options = {} if 'ode_options' in config_keys_sim: for key, val in config_dict_sim['ode_options'].items(): @@ -109,7 +114,7 @@ def digest_pulse_obj(qobj): # Step #1: Parse hamiltonian representation if 'hamiltonian' not in config_keys_sim: - raise QiskitError('Qobj must have hamiltonian in config to simulate.') + raise ValueError('Qobj must have hamiltonian in config to simulate.') else: ham = config_dict_sim['hamiltonian'] @@ -159,7 +164,7 @@ def digest_pulse_obj(qobj): # Step #2: Get Hamiltonian channels - out.channels = get_hamiltonian_channels(out.system, qubit_list) + out.channels = get_hamiltonian_channels(out.system) h_diag, evals, estates = get_diag_hamiltonian(out.system, out.vars, out.channels) @@ -167,7 +172,7 @@ def digest_pulse_obj(qobj): #convert estates into a qobj estates_qobj = [] for kk in range(len(estates[:,])): - estates_qobj.append(op.state(estates[:,kk])) + estates_qobj.append(op.state(estates[:, kk])) out.h_diag = np.ascontiguousarray(h_diag.real) out.evals = evals @@ -175,7 +180,7 @@ def digest_pulse_obj(qobj): # Set initial state out.initial_state = 0*op.basis(len(evals), 1) - for idx, estate_coef in enumerate(estates[:,0]): + for idx, estate_coef in enumerate(estates[:, 0]): out.initial_state += estate_coef*op.basis(len(evals), idx) #init_fock_state(dim_osc, dim_qub) @@ -183,9 +188,9 @@ def digest_pulse_obj(qobj): out.freqs = OrderedDict() for key in out.channels.keys(): chidx = int(key[1:]) - if key[0]=='D': + if key[0] == 'D': out.freqs[key] = config_dict['qubit_lo_freq'][chidx] - elif key[0]=='U': + elif key[0] == 'U': out.freqs[key] = 0 for u_lo_idx in config_dict_sim['u_channel_lo'][chidx]: if u_lo_idx['q'] < len(config_dict['qubit_lo_freq']): @@ -206,7 +211,7 @@ def digest_pulse_obj(qobj): # Step #4: Get dt if 'dt' not in config_dict_sim.keys(): - raise QiskitError('Qobj must have a dt value to simulate.') + raise ValueError('Qobj must have a dt value to simulate.') else: out.dt = config_dict_sim['dt'] @@ -233,7 +238,7 @@ def digest_pulse_obj(qobj): # Add in measurement operators # Not sure if this will work for multiple measurements - if len(exp_struct['acquire']) > 0: + if any(exp_struct['acquire']): for acq in exp_struct['acquire']: for jj in acq[1]: if jj > qubit_list[-1]: @@ -241,11 +246,11 @@ def digest_pulse_obj(qobj): if not out.global_data['measurement_ops'][jj]: out.global_data['measurement_ops'][jj] = \ qubit_occ_oper_dressed(jj, - estates_qobj, - h_osc=dim_osc, - h_qub=dim_qub, - level=out.global_data['q_level_meas'] - ) + estates_qobj, + h_osc=dim_osc, + h_qub=dim_qub, + level=out.global_data['q_level_meas'] + ) out.experiments.append(exp_struct) @@ -262,9 +267,9 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): parsed_ham (list): A list holding ops and strings from the Hamiltonian of a specific quantum system. - ham_vars: dictionary of variables + ham_vars (dict): dictionary of variables - channels: drive channels (set to 0) + channels (dict): drive channels (set to 0) Returns: h_diag: diagonal elements of the hamiltonian @@ -274,9 +279,6 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): Raises: Exception: Missing index on channel. """ - - pi = np.pi - #Get the diagonal elements of the hamiltonian with all the #drive terms set to zero for chan in channels: @@ -285,7 +287,7 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): #might be a better solution to replace the 'var' in the hamiltonian #string with 'op_system.vars[var]' for var in ham_vars: - exec('%s=%f'%(var,ham_vars[var])) + exec('%s=%f'%(var, ham_vars[var])) H_full = np.zeros(np.shape(parsed_ham[0][0].full()), dtype=complex) @@ -298,20 +300,20 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): eval_mapping = [] for ii in range(len(evals)): - eval_mapping.append(np.argmax(np.abs(estates[:,ii]))) + eval_mapping.append(np.argmax(np.abs(estates[:, ii]))) evals2 = evals.copy() estates2 = estates.copy() - for ii in range(len(eval_mapping)): - evals2[eval_mapping[ii]] = evals[ii] - estates2[:,eval_mapping[ii]] = estates[:,ii] + for ii, val in enumerate(eval_mapping): + evals2[val] = evals[ii] + estates2[:, val] = estates[:, ii] return h_diag, evals2, estates2 -def get_hamiltonian_channels(parsed_ham, qubit_list=None): +def get_hamiltonian_channels(parsed_ham): """ Get all the qubit channels D_i and U_i in the string representation of a system Hamiltonian. @@ -319,8 +321,6 @@ def get_hamiltonian_channels(parsed_ham, qubit_list=None): parsed_ham (list): A list holding ops and strings from the Hamiltonian of a specific quantum system. - qubit_list: whitelist of qubits - Returns: list: A list of all channels in Hamiltonian string. @@ -365,18 +365,21 @@ def build_pulse_arrays(qobj): qobj (Qobj): A pulse-qobj instance. Returns: - ndarray, ndarray, dict: Returns all pulses in one array, + tuple: Returns all pulses in one array, an array of start indices for pulses, and dict that maps pulses to the index at which the pulses start. """ qobj_pulses = qobj['config']['pulse_library'] pulse_dict = {} total_pulse_length = 0 - for kk, pulse in enumerate(qobj_pulses): - pulse_dict[pulse['name']] = kk + + num_pulse = 0 + for pulse in qobj_pulses: + pulse_dict[pulse['name']] = num_pulse total_pulse_length += len(pulse['samples']) + num_pulse += 1 - idx = kk+1 + idx = num_pulse+1 #now go through experiments looking for PV gates pv_pulses = [] for exp in qobj['experiments']: @@ -394,7 +397,7 @@ def build_pulse_arrays(qobj): stop = 0 ind = 1 - for kk, pulse in enumerate(qobj_pulses): + for _, pulse in enumerate(qobj_pulses): stop = pulses_idx[ind-1] + len(pulse['samples']) pulses_idx[ind] = stop oplist_to_array(pulse['samples'], pulses, pulses_idx[ind-1]) @@ -410,6 +413,23 @@ def build_pulse_arrays(qobj): def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt, qubit_list=None): + """Converts an experiment to a better formatted structure + + Args: + experiment (dict): An experiment. + ham_chans (dict): The channels in the Hamiltonian. + pulse_inds (array): Array of pulse indices. + pulse_to_int (array): Qobj pulses labeled by ints. + dt (float): Pulse time resolution. + qubit_list (list): List of qubits. + + Returns: + dict: The output formatted structure. + + Raises: + ValueError: Channel not in Hamiltonian. + TypeError: Incorrect snapshot type. + """ #TO DO: Error check that operations are restricted to qubit list max_time = 0 structs = {} @@ -457,7 +477,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, # Frame changes elif inst['name'] == 'fc': - structs['channels'][chan_name][1].extend([inst['t0'],inst['phase'], cond]) + structs['channels'][chan_name][1].extend([inst['t0'], inst['phase'], cond]) # A standard pulse else: @@ -509,7 +529,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, # conditionals elif inst['name'] == 'bfunc': bfun_vals = [inst['t0'], inst['mask'], inst['relation'], - inst['val'], inst['register']] + inst['val'], inst['register']] if 'memory' in inst.keys(): bfun_vals.append(inst['memory']) else: @@ -527,7 +547,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, # snapshots elif inst['name'] == 'snapshot': if inst['type'] != 'state': - raise QiskitError("Snapshots must be of type 'state'") + raise TypeError("Snapshots must be of type 'state'") structs['snapshot'].append([inst['t0'], inst['label']]) # Add time to tlist @@ -555,4 +575,4 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, if len(structs['acquire']) > 1 or structs['tlist'][-1] > structs['acquire'][-1][0]: structs['can_sample'] = False - return structs \ No newline at end of file + return structs diff --git a/qiskit/providers/aer/openpulse/qobj/op_system.py b/qiskit/providers/aer/openpulse/qobj/op_system.py index 2099143480..504dccb119 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_system.py +++ b/qiskit/providers/aer/openpulse/qobj/op_system.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. # pylint: disable=invalid-name +"The OpenPulse simulator system class" + class OPSystem(): """ A Class that holds all the information needed to simulate a given PULSE qobj @@ -51,4 +53,4 @@ def __init__(self): # diagonal elements of the hamiltonian self.h_diag = None # eigenvalues of the time-independent hamiltonian - self.evals = None \ No newline at end of file + self.evals = None diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 13030509cf..3b2adb8607 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -11,25 +11,26 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=invalid-name + +"""Module for creating quantum operators.""" + import numpy as np import scipy.linalg as la from ..qobj import op_qobj as op - def gen_oper(opname, index, h_osc, h_qub, states=None): """Generate quantum operators. - Parameters - ---------- - opname (str): Name of the operator to be returned. - index (int): Index of operator. - h_osc (dict): Dimension of oscillator subspace - h_qub (dict): Dimension of qubit subspace - states (tuple): State indices of projection operator. - - Returns - ------- - out_oper (qutip.Qobj): quantum operator for target qubit. + Args: + opname (str): Name of the operator to be returned. + index (int): Index of operator. + h_osc (dict): Dimension of oscillator subspace + h_qub (dict): Dimension of qubit subspace + states (tuple): State indices of projection operator. + + Returns: + Qobj: quantum operator for target qubit. """ opr_tmp = None @@ -39,10 +40,10 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): is_qubit = True dim = h_qub.get(index, 2) - if opname in ['X', 'Y', 'Z'] and dim>2: - if opname=='X': + if opname in ['X', 'Y', 'Z'] and dim > 2: + if opname == 'X': opr_tmp = op.get_oper('A', dim)+op.get_oper('C', dim) - elif opname=='Y': + elif opname == 'Y': opr_tmp = -1j*op.get_oper('A', dim)+1j*op.get_oper('C', dim) else: opr_tmp = op.get_oper('I', dim) - op.get_oper('N', dim) @@ -89,9 +90,8 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): h_qub (dict): Dict of number of levels in each qubit system. level (int): Level of qubit system to be measured. - Returns - ------- - out_oper (qutip.Qobj): Occupation number operator for target qubit. + Returns: + Qobj: Occupation number operator for target qubit. """ # reverse sort by index rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] @@ -115,17 +115,15 @@ def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): subsystems, and the qubit last. This does it for a dressed systems assuming estates has the same ordering - Parameters - ---------- - target_qubit (int): Qubit for which operator is built. - estates: eigenstates in the dressed frame - h_osc (dict): Dict of number of levels in each oscillator. - h_qub (dict): Dict of number of levels in each qubit system. - level (int): Level of qubit system to be measured. + Args: + target_qubit (int): Qubit for which operator is built. + estates (list): eigenstates in the dressed frame + h_osc (dict): Dict of number of levels in each oscillator. + h_qub (dict): Dict of number of levels in each qubit system. + level (int): Level of qubit system to be measured. - Returns - ------- - out_oper (qutip.Qobj): Occupation number operator for target qubit. + Returns: + Qobj: Occupation number operator for target qubit. """ # reverse sort by index rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] @@ -134,9 +132,9 @@ def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): # osc_n * … * osc_0 * qubit_n * … * qubit_0 states = [] proj_op = 0*op.fock_dm(len(estates), 0) - for ii,dd in rev_h_osc: - states.append(op.basis(dd,0)) - for ii,dd in rev_h_qub: + for ii, dd in rev_h_osc: + states.append(op.basis(dd, 0)) + for ii, dd in rev_h_qub: if ii == target_qubit: states.append(op.basis(dd, level)) else: @@ -145,9 +143,9 @@ def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): state = op.tensor(states) - for ii in range(len(estates)): - if state[ii]==1: - proj_op += estates[ii] * estates[ii].dag() + for ii, estate in enumerate(estates): + if state[ii] == 1: + proj_op += estate * estate.dag() return proj_op @@ -163,7 +161,7 @@ def measure_outcomes(measured_qubits, state_vector, measure_ops, seed (int): Optional seed to RandomState for reproducibility. Returns: - outcomes (str): String of binaries representing measured qubit values. + str: String of binaries representing measured qubit values. """ outcome_len = max(measured_qubits)+1 # Create random generator with given seed (if any). @@ -189,17 +187,15 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): """Builds and applies the projection operator associated with a given qubit measurement result onto a state vector. - Parameters - ---------- - measured_qubits (list): measured qubit indices. - results (list): results of qubit measurements. - h_qub (dict): Dict of number of levels in each qubit system. - h_osc (dict): Dict of number of levels in each oscillator. - state_vector (ndarray): State vector. + Args: + measured_qubits (list): measured qubit indices. + results (list): results of qubit measurements. + h_qub (dict): Dict of number of levels in each qubit system. + h_osc (dict): Dict of number of levels in each oscillator. + state_vector (ndarray): State vector. Returns: - ---------- - proj_state (qutip.Qobj): State vector after projector applied, and normalized. + Qobj: State vector after projector applied, and normalized. """ # reverse sort by index @@ -222,7 +218,7 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): return psi - +# pylint: disable=dangerous-default-value def init_fock_state(h_osc, h_qub, noise_dict={}): """ Generate initial Fock state, in the number state basis, for an oscillator in a thermal state defined @@ -233,7 +229,7 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): h_qub (dict): Dimension of qubit subspace noise_dict (dict): Dictionary of thermal particles for each oscillator subspace Returns: - qutip.Qobj: State vector + Qobj: State vector """ # reverse sort by index rev_h_osc = sorted(h_osc.items(), key=lambda x: x[0])[::-1] diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index 74977632ca..13903ec940 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -13,6 +13,8 @@ # that they have been altered from the originals. # pylint: disable=invalid-name +""" The openpulse simulator parser""" + import re import copy from collections import namedtuple, OrderedDict @@ -117,7 +119,7 @@ def _expand_sum(self): raise Exception('Missing correct number of brackets in %s' % ham) # find correct sum-bracket correspondence - if len(p_sums) == 0: + if any(p_sums) == 0: ham_out.append(ham) else: itr = p_sums[0].group('itr') @@ -202,12 +204,12 @@ def _tokenizer(self, op_str, qubit_list=None): prev = _key break else: - raise Exception('Invalid input string %s is found', op_str) + raise Exception('Invalid input string %s is found' % op_str) # split coefficient coef = '' if any([k.type == 'Var' for k in token_list]): - for ii in range(len(token_list)): + for ii, _ in enumerate(token_list): if token_list[ii].name == '*': if all([k.type != 'Var' for k in token_list[ii+1:]]): coef = ''.join([k.name for k in token_list[:ii]]) @@ -364,7 +366,7 @@ def math_priority(o1, o2): else: return True - +# pylint: disable=dangerous-default-value def parse_binop(op_str, operands={}, cast_str=True): """ Calculate binary operation in string format """ From e16d0797380c3528daca5c14f624e696dad03201 Mon Sep 17 00:00:00 2001 From: Paul Nation Date: Tue, 13 Aug 2019 10:00:39 -0400 Subject: [PATCH 29/31] lint --- .../providers/aer/openpulse/qobj/op_qobj.py | 6 ++- .../aer/openpulse/qutip_lite/dimensions.py | 31 +++++++++--- .../providers/aer/openpulse/solver/codegen.py | 21 +++++--- .../aer/openpulse/solver/data_config.py | 15 +++--- .../aer/openpulse/solver/monte_carlo.py | 14 +++--- .../providers/aer/openpulse/solver/opsolve.py | 49 +++++++++---------- .../providers/aer/openpulse/solver/options.py | 12 ++--- .../aer/openpulse/solver/rhs_utils.py | 3 ++ .../aer/openpulse/solver/settings.py | 2 + .../providers/aer/openpulse/solver/unitary.py | 7 ++- .../providers/aer/openpulse/solver/zvode.py | 3 +- 11 files changed, 97 insertions(+), 66 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py index bc6b083d55..3c092981a2 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_qobj.py +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -11,7 +11,9 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=invalid-name +# pylint: disable=invalid-name, no-name-in-module, import-error + +"""Operators to use in simulator""" import numpy as np from ..qutip_lite import operators as ops @@ -90,7 +92,7 @@ def project(dim, states): if ket in range(dim) and bra in range(dim): return st.basis(dim, ket) * st.basis(dim, bra).dag() else: - raise Exception('States are specified on the outside of Hilbert space %s', states) + raise Exception('States are specified on the outside of Hilbert space %s' % states) def tensor(list_qobj): diff --git a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py index 836a4d5261..394ee124a3 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py @@ -1,3 +1,17 @@ +# -*- coding: utf-8 -*- + +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. @@ -30,6 +44,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name """ Internal use module for manipulating dims specifications. """ @@ -48,7 +63,7 @@ def flatten(l): Args: l (list): Input list - + Returns: list: Flattened list. @@ -66,12 +81,14 @@ def is_scalar(dims): return np.prod(flatten(dims)) == 1 def is_vector(dims): + """Is a vector""" return ( isinstance(dims, list) and isinstance(dims[0], (int, np.integer)) ) def is_vectorized_oper(dims): + """Is a vectorized operator.""" return ( isinstance(dims, list) and isinstance(dims[0], list) @@ -79,6 +96,7 @@ def is_vectorized_oper(dims): def type_from_dims(dims, enforce_square=True): + """Get the type of operator from dims structure""" bra_like, ket_like = map(is_scalar, dims) if bra_like: @@ -156,13 +174,10 @@ def collapse_dims_super(dims): Args: dims (list): Dimensions specifications to be collapsed. - Returns - ------- - - collapsed_dims : list of lists of ints - Collapsed dimensions specification describing the same shape - such that ``len(collapsed_dims[i][j]) == 1`` for ``i`` and ``j`` - in ``range(2)``. + Returns: + list: Collapsed dimensions specification describing the same shape + such that ``len(collapsed_dims[i][j]) == 1`` for ``i`` and ``j`` + in ``range(2)``. """ return _collapse_dims_to_level(dims, 2) diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index 4ba0ec6ab5..c6dbfe3f85 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -16,10 +16,13 @@ # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. # All rights reserved. +# pylint: disable=invalid-name + +"""OpenPulse runtime code generator""" import os import sys -from ..qutip_lite import cy as cy +from ..qutip_lite import cy from . import settings _cython_path = os.path.abspath(cy.__file__).replace('__init__.py', '') @@ -155,8 +158,8 @@ def func_vars(self): func_vars.append("# Eval the time-dependent terms and do SPMV.") for idx in range(len(self.op_system.system)+1): - if (idx==len(self.op_system.system) and - (len(self.op_system.system) < self.num_ham_terms)): + if (idx == len(self.op_system.system) and + (len(self.op_system.system) < self.num_ham_terms)): #this is the noise term term = [1.0, 1.0] elif idx < len(self.op_system.system): @@ -183,7 +186,8 @@ def func_vars(self): func_vars.append(sp2 + sp2 + "coef = conj(td%d)"%idx) func_vars.append(sp1 + sp2 + "else:") func_vars.append(sp2 + sp2 + "coef = td%d"%idx) - func_vars.append(sp1 + sp2 + "dot += coef*osc_term*data%d[jj]*vec[idx%d[jj]];"%(idx,idx)) + func_vars.append(sp1 + sp2 + \ + "dot += coef*osc_term*data%d[jj]*vec[idx%d[jj]];"%(idx, idx)) func_vars.append(sp2 + "out[row] += dot;") #remove the diagonal terms @@ -193,6 +197,8 @@ def func_vars(self): return func_vars def func_end(self): + """End of the RHS function. + """ end_str = [""] end_str.append("# Convert to NumPy array, grab ownership, and return.") end_str.append("cdef np.npy_intp dims = num_rows") @@ -205,11 +211,13 @@ def func_end(self): return end_str def func_header(op_system): + """Header for the RHS function. + """ func_vars = ["", 'cdef size_t row, jj', 'cdef unsigned int row_start, row_end', 'cdef unsigned int num_rows = vec.shape[0]', 'cdef double complex dot, osc_term, coef', - "cdef double complex * " + - 'out = PyDataMem_NEW_ZEROED(num_rows,sizeof(complex))' + "cdef double complex * " + + 'out = PyDataMem_NEW_ZEROED(num_rows,sizeof(complex))' ] func_vars.append("") @@ -272,4 +280,3 @@ def cython_checks(): return ["""@cython.cdivision(True) @cython.boundscheck(False) @cython.wraparound(False)"""] - diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py index d86f761c10..01deec2ae3 100644 --- a/qiskit/providers/aer/openpulse/solver/data_config.py +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -11,6 +11,9 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=invalid-name + +"""Data configuration module""" import numpy as np pi = np.pi @@ -31,8 +34,8 @@ def op_data_config(op_system): # take care of collapse operators, if any op_system.global_data['c_num'] = 0 if op_system.noise: - op_system.global_data['c_num'] = len(op_system.noise) - op_system.global_data['num_h_terms'] += 1 + op_system.global_data['c_num'] = len(op_system.noise) + op_system.global_data['num_h_terms'] += 1 op_system.global_data['c_ops_data'] = [] op_system.global_data['c_ops_ind'] = [] @@ -98,19 +101,19 @@ def op_data_config(op_system): ode_var_str += "exp['channels']['%s'][0], " % chan ode_var_str += "exp['channels']['%s'][1]" % chan if chan != final_chan or var_list: - ode_var_str+= ', ' + ode_var_str += ', ' #now do the variables for idx, var in enumerate(var_list): ode_var_str += "global_data['vars'][%s]" % idx if var != final_var or freq_list: - ode_var_str+= ', ' + ode_var_str += ', ' #now do the freq for idx, freq in enumerate(freq_list): ode_var_str += "global_data['freqs'][%s]" % idx if freq != final_freq: - ode_var_str+= ', ' + ode_var_str += ', ' # Add register ode_var_str += ", register" @@ -118,4 +121,4 @@ def op_data_config(op_system): #Convert inital state to flat array in global_data op_system.global_data['initial_state'] = \ - op_system.initial_state.full().ravel() \ No newline at end of file + op_system.initial_state.full().ravel() diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index d3d747742f..34a9e27bdc 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -12,22 +12,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - # This file is part of QuTiP: Quantum Toolbox in Python. # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. # All rights reserved. +# pylint: disable=no-name-in-module, import-error, unused-variable, invalid-name + +"""Monte carlo wave function solver.""" from math import log import numpy as np from scipy.integrate import ode from scipy.linalg.blas import get_blas_funcs -from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr from qiskit.providers.aer.openpulse.solver.zvode import qiskit_zvode -from qiskit.providers.aer.openpulse.cy.memory import write_memory from qiskit.providers.aer.openpulse.cy.measure import (occ_probabilities, write_shots_memory) +from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv_csr dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -37,7 +38,7 @@ def monte_carlo(seed, exp, global_data, ode_options): at times tlist for a single trajectory. """ - cy_rhs_func = global_data['rhs_func'] + cy_rhs_func = global_data['rhs_func'] rng = np.random.RandomState(seed) tlist = exp['tlist'] snapshots = [] @@ -64,6 +65,7 @@ def monte_carlo(seed, exp, global_data, ode_options): _inst = 'ODE.set_f_params(%s)' % global_data['string'] code = compile(_inst, '', 'exec') + # pylint: disable=exec-used exec(code) # initialize ODE solver for RHS @@ -77,7 +79,7 @@ def monte_carlo(seed, exp, global_data, ode_options): max_step=ode_options.max_step ) # Forces complex ODE solving - if not len(ODE._y): + if not any(ODE._y): ODE.t = 0.0 ODE._y = np.array([0.0], complex) ODE._integrator.reset(len(ODE._y), ODE.jac is not None) @@ -122,7 +124,7 @@ def monte_carlo(seed, exp, global_data, ode_options): if (abs(rand_vals[0] - norm2_guess) < ode_options.norm_tol * rand_vals[0]): break - elif (norm2_guess < rand_vals[0]): + elif norm2_guess < rand_vals[0]: # t_guess is still > t_jump t_final = t_guess norm2_psi = norm2_guess diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index d9fc5043f7..afe410ffd5 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -11,25 +11,22 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +# pylint: disable=no-name-in-module, import-error, invalid-name """The main OpenPulse solver routine. """ import time import numpy as np -from numpy.random import RandomState, randint from scipy.linalg.blas import get_blas_funcs -from collections import OrderedDict -from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr, spmv, spmv_csr +from qiskit.tools.parallel import parallel_map, CPU_COUNT +from ..qutip_lite.cy.spmatfuncs import cy_expect_psi_csr from ..qutip_lite.cy.utilities import _cython_build_cleanup from ..qobj.operators import apply_projector -from .codegen import OPCodegen from .rhs_utils import _op_generate_rhs, _op_func_load from .data_config import op_data_config from .unitary import unitary_evolution from .monte_carlo import monte_carlo -from qiskit.tools.parallel import parallel_map, CPU_COUNT -from ..cy.measure import write_shots_memory dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) @@ -86,9 +83,9 @@ def __init__(self, op_system): # preallocate ntraj arrays for state vectors, collapse times, and # which operator self.collapse_times = [[] for kk in - range(op_system.global_data['shots'])] + range(op_system.global_data['shots'])] self.collapse_operators = [[] for kk in - range(op_system.global_data['shots'])] + range(op_system.global_data['shots'])] # setup seeds array if op_system.global_data['seed']: prng = np.random.RandomState(op_system.global_data['seed']) @@ -99,7 +96,8 @@ def __init__(self, op_system): exp['seed'] = prng.randint(np.iinfo(np.int32).max-1) def run(self): - + """Runs the solver. + """ map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} @@ -117,13 +115,12 @@ def run(self): if self.op_system.can_sample: start = time.time() exp_results = parallel_map(unitary_evolution, - self.op_system.experiments, - task_args=(self.op_system.global_data, - self.op_system.ode_options - ), - **map_kwargs - ) - + self.op_system.experiments, + task_args=(self.op_system.global_data, + self.op_system.ode_options + ), + **map_kwargs + ) end = time.time() exp_times = (np.ones(len(self.op_system.experiments))* (end-start)/len(self.op_system.experiments)) @@ -181,13 +178,13 @@ def run(self): memory = exp_results[idx_exp] # meas_level 2 return the shots - if m_lev==2: + if m_lev == 2: # convert the memory **array** into a n # integer # e.g. [1,0] -> 2 int_mem = memory.dot(np.power(2.0, - np.arange(memory.shape[1]-1, -1, -1))).astype(int) + np.arange(memory.shape[1]-1, -1, -1))).astype(int) # if the memory flag is set return each shot if self.op_system.global_data['memory']: @@ -204,13 +201,11 @@ def run(self): results['data']['counts'] = hex_dict # meas_level 1 returns the - elif m_lev==1: - - + elif m_lev == 1: - if m_ret=='avg': + if m_ret == 'avg': - memory = [np.mean(memory,0)] + memory = [np.mean(memory, 0)] # convert into the right [real, complex] pair form for json # this should be cython? @@ -220,9 +215,9 @@ def run(self): results['data']['memory'].append([]) for mem_slot in mem_shot: results['data']['memory'][-1].append( - [np.real(mem_slot), np.imag(mem_slot)]) + [np.real(mem_slot), np.imag(mem_slot)]) - if m_ret=='avg': + if m_ret == 'avg': results['data']['memory'] = results['data']['memory'][0] @@ -243,7 +238,7 @@ def _proj_measurement(pid, ophandler, tt, state, memory, register=None): for key, acq in ophandler._acqs.items(): if pid < 0: - if len(acq.m_slot) > 0: + if any(acq.m_slot): mem_slot_id = acq.m_slot[-1] else: continue @@ -273,7 +268,7 @@ def _proj_measurement(pid, ophandler, tt, state, memory, register=None): results.append(outcome) # projection - if len(qubits) > 0: + if any(qubits): psi_proj = apply_projector(qubits, results, ophandler.h_qub, ophandler.h_osc, state) else: psi_proj = state diff --git a/qiskit/providers/aer/openpulse/solver/options.py b/qiskit/providers/aer/openpulse/solver/options.py index 019894f105..deae400076 100644 --- a/qiskit/providers/aer/openpulse/solver/options.py +++ b/qiskit/providers/aer/openpulse/solver/options.py @@ -12,10 +12,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""OpenPulse options""" class OPoptions(object): """ - Class of options for opsolver. Options can be specified either as + Class of options for opsolver. Options can be specified either as arguments to the constructor:: opts = Options(order=10, ...) @@ -38,7 +39,7 @@ class OPoptions(object): max_step (float, 0): Maximum step size (0 = automatic) num_cpus (int): Number of cpus used by mcsolver (default = # of cpus). norm_tol (float, 1e-3): Tolerance used when finding wavefunction norm. - norm_steps (int, 5): Max. number of steps used to find wavefunction norm + norm_steps (int, 5): Max. number of steps used to find wavefunction norm to within norm_tol shots (int, 1024): Number of shots to run. rhs_reuse (bool, False): Reuse RHS compiled function. @@ -46,7 +47,7 @@ class OPoptions(object): seeds (ndarray, None): Array containing random number seeds for repeatible shots. reuse_seeds (bool, False): Reuse seeds, if already generated. - store_final_state (bool, False): Whether or not to store the final state + store_final_state (bool, False): Whether or not to store the final state of the evolution. """ @@ -57,7 +58,7 @@ def __init__(self, atol=1e-8, rtol=1e-6, method='adams', order=12, rhs_filename=None, shots=1024, store_final_state=False, seeds=None, reuse_seeds=False): - + # Absolute tolerance (default = 1e-8) self.atol = atol # Relative tolerance (default = 1e-6) @@ -104,6 +105,3 @@ def __str__(self): return str(vars(self)) def __repr__(self): return self.__str__() - - - diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index 3ccfe13b8c..98c19a54f3 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -12,6 +12,8 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Routines for generating and loading runtime RHS function""" + import os from .codegen import OPCodegen from . import settings as op_set @@ -39,6 +41,7 @@ def _op_func_load(op_system): """ code = compile('from ' + op_system.global_data['rhs_file_name'] + ' import cy_td_ode_rhs', '', 'exec') + # pylint: disable=exec-used exec(code, globals()) # pylint: disable=undefined-variable op_system.global_data['rhs_func'] = cy_td_ode_rhs diff --git a/qiskit/providers/aer/openpulse/solver/settings.py b/qiskit/providers/aer/openpulse/solver/settings.py index 05445eed40..b54af5ff6a 100644 --- a/qiskit/providers/aer/openpulse/solver/settings.py +++ b/qiskit/providers/aer/openpulse/solver/settings.py @@ -12,5 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. +"""Holds OpenPulse solver settings""" + # Code generation counter CGEN_NUM = 0 diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index 242ad42ade..f3084e11b2 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -12,7 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # pylint: disable=unused-variable, no-name-in-module, protected-access, -# pylint: disable=invalid-name +# pylint: disable=invalid-name, import-error, exec-used """Module for unitary pulse evolution. """ @@ -37,7 +37,10 @@ def unitary_evolution(exp, global_data, ode_options): ode_options (OPoptions): Options for the underlying ODE solver. Returns: - Stuff + array: Memory of shots. + + Raises: + Exception: Error in ODE solver. """ cy_rhs_func = global_data['rhs_func'] rng = np.random.RandomState(exp['seed']) diff --git a/qiskit/providers/aer/openpulse/solver/zvode.py b/qiskit/providers/aer/openpulse/solver/zvode.py index 219a117dc4..f96cfceb79 100644 --- a/qiskit/providers/aer/openpulse/solver/zvode.py +++ b/qiskit/providers/aer/openpulse/solver/zvode.py @@ -16,8 +16,9 @@ # # Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. # All rights reserved. +# pylint: disable=no-value-for-parameter, invalid-name -# pylint: disable=no-value-for-parameter +"""Hack for the ZVODE solver to do the correct stepping without restart.""" from scipy.integrate._ode import zvode From de48cb8903b1d1e0280d084230cad8e5c21c0776 Mon Sep 17 00:00:00 2001 From: David McKay Date: Wed, 14 Aug 2019 16:26:59 -0400 Subject: [PATCH 30/31] Add identity qubit op, fix error in converting from Z to N (#6) --- qiskit/providers/aer/openpulse/qobj/operators.py | 2 +- qiskit/providers/aer/openpulse/qobj/opparse.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 3b2adb8607..83b4229a08 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -46,7 +46,7 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): elif opname == 'Y': opr_tmp = -1j*op.get_oper('A', dim)+1j*op.get_oper('C', dim) else: - opr_tmp = op.get_oper('I', dim) - op.get_oper('N', dim) + opr_tmp = op.get_oper('I', dim) - 2*op.get_oper('N', dim) else: is_qubit = False diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index 13903ec940..bc73219b01 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -26,7 +26,7 @@ Token = namedtuple('Token', ('type', 'name')) ham_elements = OrderedDict( - QubOpr=re.compile(r"(?PO|Sp|Sm|X|Y|Z)(?P[0-9]+)"), + QubOpr=re.compile(r"(?PO|Sp|Sm|X|Y|Z|I)(?P[0-9]+)"), PrjOpr=re.compile(r"P(?P[0-9]+),(?P[0-9]+),(?P[0-9]+)"), CavOpr=re.compile(r"(?PA|C|N)(?P[0-9]+)"), Func=re.compile(r"(?P[a-z]+)\("), From ffbe7fe385ecc55b9e7a38613dbd69b7b4460526 Mon Sep 17 00:00:00 2001 From: David McKay Date: Tue, 17 Sep 2019 17:03:06 -0400 Subject: [PATCH 31/31] changed example to use fake backend, updated for pep8 (#7) * changed example to use fake backend, updated for pep8 * fix lint errors --- example/pulse_sim.ipynb | 39 ++-- qiskit/providers/aer/openpulse/__init__.py | 9 +- qiskit/providers/aer/openpulse/qobj/digest.py | 214 +++++++++--------- .../providers/aer/openpulse/qobj/op_qobj.py | 2 + .../providers/aer/openpulse/qobj/op_system.py | 1 + .../providers/aer/openpulse/qobj/operators.py | 19 +- .../providers/aer/openpulse/qobj/opparse.py | 15 +- .../aer/openpulse/qutip_lite/cy/pyxbuilder.py | 21 +- .../aer/openpulse/qutip_lite/cy/utilities.py | 21 +- .../aer/openpulse/qutip_lite/dimensions.py | 13 +- .../aer/openpulse/qutip_lite/expect.py | 1 + .../aer/openpulse/qutip_lite/fastsparse.py | 39 ++-- .../aer/openpulse/qutip_lite/operators.py | 38 ++-- .../aer/openpulse/qutip_lite/qobj.py | 147 ++++++------ .../aer/openpulse/qutip_lite/sparse.py | 12 +- .../aer/openpulse/qutip_lite/states.py | 18 +- .../aer/openpulse/qutip_lite/superoperator.py | 5 +- .../providers/aer/openpulse/solver/codegen.py | 44 ++-- .../aer/openpulse/solver/data_config.py | 12 +- .../aer/openpulse/solver/monte_carlo.py | 3 +- .../providers/aer/openpulse/solver/opsolve.py | 45 ++-- .../providers/aer/openpulse/solver/options.py | 4 +- .../aer/openpulse/solver/rhs_utils.py | 3 +- .../providers/aer/openpulse/solver/unitary.py | 7 +- 24 files changed, 387 insertions(+), 345 deletions(-) diff --git a/example/pulse_sim.ipynb b/example/pulse_sim.ipynb index bcab81db2d..e2e6a8751f 100644 --- a/example/pulse_sim.ipynb +++ b/example/pulse_sim.ipynb @@ -33,7 +33,8 @@ "import qiskit.pulse as pulse\n", "import qiskit.pulse.pulse_lib as pulse_lib\n", "from qiskit.compiler import assemble\n", - "import random" + "import random\n", + "from qiskit.test.mock import FakeOpenPulse2Q" ] }, { @@ -62,9 +63,8 @@ "metadata": {}, "outputs": [], "source": [ - "#Get a pulse configuration from pok\n", - "provider = qiskit.IBMQ.get_provider(hub='ibm-q-dev',group='qiskit', project='ignis')\n", - "backend_real = provider.get_backend('ibmq_poughkeepsie')\n", + "#Get a pulse configuration from the fake backend\n", + "backend_real = FakeOpenPulse2Q()\n", "back_config = backend_real.configuration().to_dict()\n", "system = pulse.PulseChannelSpec.from_backend(backend_real)" ] @@ -201,18 +201,19 @@ "hamiltonian = {}\n", "hamiltonian['h_str'] = []\n", "#Q0 terms\n", - "hamiltonian['h_str'].append('pi*(2*v0-alpha0)*O0')\n", - "hamiltonian['h_str'].append('pi*alpha0*O0*O0')\n", - "hamiltonian['h_str'].append('2*pi*r*X0||D0')\n", - "hamiltonian['h_str'].append('2*pi*r*X0||U0')\n", + "hamiltonian['h_str'].append('np.pi*(2*v0-alpha0)*O0')\n", + "hamiltonian['h_str'].append('np.pi*alpha0*O0*O0')\n", + "hamiltonian['h_str'].append('2*np.pi*r*X0||D0')\n", + "hamiltonian['h_str'].append('2*np.pi*r*X0||U1')\n", + "hamiltonian['h_str'].append('2*np.pi*r*X1||U0')\n", "\n", "#Q1 terms\n", - "hamiltonian['h_str'].append('pi*(2*v1-alpha1)*O1')\n", - "hamiltonian['h_str'].append('pi*alpha1*O1*O1')\n", - "hamiltonian['h_str'].append('X1||r*D1')\n", + "hamiltonian['h_str'].append('np.pi*(2*v1-alpha1)*O1')\n", + "hamiltonian['h_str'].append('np.pi*alpha1*O1*O1')\n", + "hamiltonian['h_str'].append('2*np.pi*r*X0||D1')\n", "\n", "#Exchange coupling betwene Q0 and Q1\n", - "hamiltonian['h_str'].append('2*pi*j*(Sp0*Sm1+Sm0*Sp1)')\n", + "hamiltonian['h_str'].append('2*np.pi*j*(Sp0*Sm1+Sm0*Sp1)')\n", "hamiltonian['vars'] = {'v0': 5.00, 'v1': 5.1, 'j': 0.01, \n", " 'r': 0.02, 'alpha0': -0.33, 'alpha1': -0.33}\n", "\n", @@ -399,12 +400,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "Pi Amplitude 0.314328\n" + "Pi Amplitude 0.315141\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -571,7 +572,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -676,7 +677,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -691,12 +692,12 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 27, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -809,7 +810,7 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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kJCQfO1bUlmdywars2eEh4dTr149jx7THapKp06dGDp0KLfeeisZGRncd999BAcHc8cddzBkyBASExPZu3cvvXr14ueff8bX1/ec42zevJmUlJSz1nXq1Gm1qja/WAzFfVRVbhfics10qjoB5zOYmzdvrvm95d6mKCgbrM5lQ2HUef369RcctVTYFixYQHBwMHfddVf2unHjxlG7dm2qV69O3759iYqKIioqivr167N+/XratGlzznECAwNp2jR/s+0X98SRjOMxnFmigT1eisUYY87y7Mx1JO055tFjxlcP4+nrLjnv9nXr1tGsWbOz1oWFhRETE8OKFSvo3bt39vro6Gh2797t0fjA+53jFzMDuN05uqo1kKKqe70dlDHGeIuq5joqSlXJreuhMEaAebXFISKf4nhGc5SIJANP43xWsqq+BczG8RznzcAJYIh3IjXGmHNdqGVQWC655BK++OKLs9YdO3aM/fv3c/PNN5/VwkhOTqZ69eoej8Hbo6oGqGo1VfVT1WhVfU9V33ImDZyjqe5R1bqqemluneLGGFOWXHXVVZw4cYLJkycDkJGRwcMPP8zIkSPp1asXX3zxBadPn2bbtm1s2rSJli1bejyG4n6pyhhjjAsR4auvvuLzzz8nLi6OihUr4uPjwxNPPMEll1zCDTfcQHx8PN27d2f8+PG5jqgqqOLeOW6MMSaHmjVrMmPGDACWLl3KgAEDWL16Nc2aNeORRx7hueeeK9TzW+IwxpgSrG3btuzYkacH93mMXaoyxhjjFkscxhhj3GKJwxhjjFsscRhjjHGLJQ5jjDFuscRhjDElzPmmVT906BDXXnstISEhjBw5stDOb4nDGGNKEFWlT58+XH/99WzatIlNmzZx8uRJ/v73vxMYGMiTTz7JSy+9VKgxWOIwxpgS5LvvviMwMJAhQxxT9/n6+vLqq68yefJkVJU2bdoQGBhYqDHYDYDGGJNfcx6DfWs9e8yql0KP58+7+ULTqm/evJm6det6Np5cWIvDGGNKkAtNq15UrMVhjDH5dYGWQWG50LTqDRo0ID09vdBjsBaHMcaUIBeaVj0oKKhIYrDEYYwxJciFplUHSEhI4KGHHuKDDz4gOjqapKQkj8dgl6qMMaaEudC06r/99huhoaGFen5LHMYYU4LZtOrGGGOKPUscxhhj3GKJwxhjjFsscRhjjHGL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\n", "text/plain": [ "
" ] diff --git a/qiskit/providers/aer/openpulse/__init__.py b/qiskit/providers/aer/openpulse/__init__.py index 7c63cf6d57..ad3da6e242 100644 --- a/qiskit/providers/aer/openpulse/__init__.py +++ b/qiskit/providers/aer/openpulse/__init__.py @@ -11,17 +11,18 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - +"""Init for openpulse""" import distutils.sysconfig import numpy as np from .qutip_lite.cy import pyxbuilder as pbldr # Remove -Wstrict-prototypes from cflags -cfg_vars = distutils.sysconfig.get_config_vars() -if "CFLAGS" in cfg_vars: - cfg_vars["CFLAGS"] = cfg_vars["CFLAGS"].replace("-Wstrict-prototypes", "") +CFG_VARS = distutils.sysconfig.get_config_vars() +if "CFLAGS" in CFG_VARS: + CFG_VARS["CFLAGS"] = CFG_VARS["CFLAGS"].replace("-Wstrict-prototypes", "") # Setup pyximport +# pylint: disable=no-member pbldr.install(setup_args={'include_dirs': [np.get_include()]}) del pbldr diff --git a/qiskit/providers/aer/openpulse/qobj/digest.py b/qiskit/providers/aer/openpulse/qobj/digest.py index 92fea7be83..ae1ee1a8fe 100644 --- a/qiskit/providers/aer/openpulse/qobj/digest.py +++ b/qiskit/providers/aer/openpulse/qobj/digest.py @@ -29,7 +29,6 @@ from . import op_qobj as op - def digest_pulse_obj(qobj): """Takes an input PULSE obj an disgests it into things we can actually make use of. @@ -47,7 +46,7 @@ def digest_pulse_obj(qobj): # Output data object out = OPSystem() - #get the config settings from the qobj + # get the config settings from the qobj config_dict_sim = qobj['config']['sim_config'] config_dict = qobj['config'] @@ -92,8 +91,7 @@ def digest_pulse_obj(qobj): if 'n_registers' in config_keys: out.global_data['n_registers'] = config_dict['n_registers'] - - #which level to measure + # which level to measure out.global_data['q_level_meas'] = 1 if 'q_level_meas' in config_keys_sim: out.global_data['q_level_meas'] = int(config_dict_sim['q_level_meas']) @@ -107,61 +105,58 @@ def digest_pulse_obj(qobj): for key, val in config_dict_sim['ode_options'].items(): if key not in allowed_ode_options: raise Exception('Invalid ode_option: {}'.format(key)) - else: - user_set_ode_options[key] = val + user_set_ode_options[key] = val out.ode_options = OPoptions(**user_set_ode_options) - # Step #1: Parse hamiltonian representation if 'hamiltonian' not in config_keys_sim: raise ValueError('Qobj must have hamiltonian in config to simulate.') - else: - ham = config_dict_sim['hamiltonian'] - - out.vars = OrderedDict(ham['vars']) - out.global_data['vars'] = list(out.vars.values()) - - # Get qubit subspace dimensions - if 'qub' in ham.keys(): - dim_qub = ham['qub'] - _dim_qub = {} - # Convert str keys to int keys - for key, val in ham['qub'].items(): - _dim_qub[int(key)] = val - dim_qub = _dim_qub - else: - dim_qub = {}.fromkeys(range(config_dict_sim['n_qubits']), 2) - - # Get oscillator subspace dimensions - if 'osc' in ham.keys(): - dim_osc = ham['osc'] - _dim_osc = {} - # Convert str keys to int keys - for key, val in dim_osc.items(): - _dim_osc[int(key)] = val - dim_osc = _dim_osc - else: - dim_osc = {} - - # Parse the Hamiltonian - system = HamiltonianParser(h_str=ham['h_str'], - dim_osc=dim_osc, - dim_qub=dim_qub) - system.parse(qubit_list) - out.system = system.compiled - - if 'noise' in config_dict_sim.keys(): - noise = NoiseParser(noise_dict=config_dict_sim['noise'], - dim_osc=dim_osc, dim_qub=dim_qub) - noise.parse() - - out.noise = noise.compiled - if any(out.noise): - out.can_sample = False - out.global_data['c_num'] = len(out.noise) - else: - out.noise = None + ham = config_dict_sim['hamiltonian'] + + out.vars = OrderedDict(ham['vars']) + out.global_data['vars'] = list(out.vars.values()) + + # Get qubit subspace dimensions + if 'qub' in ham.keys(): + dim_qub = ham['qub'] + _dim_qub = {} + # Convert str keys to int keys + for key, val in ham['qub'].items(): + _dim_qub[int(key)] = val + dim_qub = _dim_qub + else: + dim_qub = {}.fromkeys(range(config_dict_sim['n_qubits']), 2) + + # Get oscillator subspace dimensions + if 'osc' in ham.keys(): + dim_osc = ham['osc'] + _dim_osc = {} + # Convert str keys to int keys + for key, val in dim_osc.items(): + _dim_osc[int(key)] = val + dim_osc = _dim_osc + else: + dim_osc = {} + + # Parse the Hamiltonian + system = HamiltonianParser(h_str=ham['h_str'], + dim_osc=dim_osc, + dim_qub=dim_qub) + system.parse(qubit_list) + out.system = system.compiled + + if 'noise' in config_dict_sim.keys(): + noise = NoiseParser(noise_dict=config_dict_sim['noise'], + dim_osc=dim_osc, dim_qub=dim_qub) + noise.parse() + + out.noise = noise.compiled + if any(out.noise): + out.can_sample = False + out.global_data['c_num'] = len(out.noise) + else: + out.noise = None # Step #2: Get Hamiltonian channels out.channels = get_hamiltonian_channels(out.system) @@ -169,22 +164,21 @@ def digest_pulse_obj(qobj): h_diag, evals, estates = get_diag_hamiltonian(out.system, out.vars, out.channels) - #convert estates into a qobj + # convert estates into a qobj estates_qobj = [] - for kk in range(len(estates[:,])): + for kk in range(len(estates[:, ])): estates_qobj.append(op.state(estates[:, kk])) out.h_diag = np.ascontiguousarray(h_diag.real) out.evals = evals - # Set initial state - out.initial_state = 0*op.basis(len(evals), 1) + out.initial_state = 0 * op.basis(len(evals), 1) for idx, estate_coef in enumerate(estates[:, 0]): - out.initial_state += estate_coef*op.basis(len(evals), idx) - #init_fock_state(dim_osc, dim_qub) + out.initial_state += estate_coef * op.basis(len(evals), idx) + # init_fock_state(dim_osc, dim_qub) - #Setup freqs for the channels + # Setup freqs for the channels out.freqs = OrderedDict() for key in out.channels.keys(): chidx = int(key[1:]) @@ -196,7 +190,7 @@ def digest_pulse_obj(qobj): if u_lo_idx['q'] < len(config_dict['qubit_lo_freq']): qfreq = config_dict['qubit_lo_freq'][u_lo_idx['q']] qscale = u_lo_idx['scale'][0] - out.freqs[key] += qfreq*qscale + out.freqs[key] += qfreq * qscale else: raise ValueError("Channel is not D or U") @@ -212,22 +206,21 @@ def digest_pulse_obj(qobj): # Step #4: Get dt if 'dt' not in config_dict_sim.keys(): raise ValueError('Qobj must have a dt value to simulate.') - else: - out.dt = config_dict_sim['dt'] - + out.dt = config_dict_sim['dt'] - # Set the ODE solver max step to be the half the width of the smallest pulse + # Set the ODE solver max step to be the half the + # width of the smallest pulse min_width = np.iinfo(np.int32).max for key, val in out.pulse_to_int.items(): if key != 'pv': - stop = out.global_data['pulse_indices'][val+1] + stop = out.global_data['pulse_indices'][val + 1] start = out.global_data['pulse_indices'][val] - min_width = min(min_width, stop-start) - out.ode_options.max_step = min_width/2 * out.dt + min_width = min(min_width, stop - start) + out.ode_options.max_step = min_width / 2 * out.dt # Step #6: Convert experiments to data structures. - out.global_data['measurement_ops'] = [None]*config_dict_sim['n_qubits'] + out.global_data['measurement_ops'] = [None] * config_dict_sim['n_qubits'] for exp in qobj['experiments']: exp_struct = experiment_to_structs(exp, @@ -250,14 +243,14 @@ def digest_pulse_obj(qobj): h_osc=dim_osc, h_qub=dim_qub, level=out.global_data['q_level_meas'] - ) - + ) out.experiments.append(exp_struct) if not exp_struct['can_sample']: out.can_sample = False return out + def get_diag_hamiltonian(parsed_ham, ham_vars, channels): """ Get the diagonal elements of the hamiltonian and get the @@ -279,20 +272,20 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): Raises: Exception: Missing index on channel. """ - #Get the diagonal elements of the hamiltonian with all the - #drive terms set to zero + # Get the diagonal elements of the hamiltonian with all the + # drive terms set to zero for chan in channels: - exec('%s=0'%chan) + exec('%s=0' % chan) - #might be a better solution to replace the 'var' in the hamiltonian - #string with 'op_system.vars[var]' + # might be a better solution to replace the 'var' in the hamiltonian + # string with 'op_system.vars[var]' for var in ham_vars: - exec('%s=%f'%(var, ham_vars[var])) + exec('%s=%f' % (var, ham_vars[var])) H_full = np.zeros(np.shape(parsed_ham[0][0].full()), dtype=complex) for hpart in parsed_ham: - H_full += hpart[0].full()*eval(hpart[1]) + H_full += hpart[0].full() * eval(hpart[1]) h_diag = np.diag(H_full) @@ -312,7 +305,6 @@ def get_diag_hamiltonian(parsed_ham, ham_vars, channels): return h_diag, evals2, estates2 - def get_hamiltonian_channels(parsed_ham): """ Get all the qubit channels D_i and U_i in the string representation of a system Hamiltonian. @@ -329,22 +321,24 @@ def get_hamiltonian_channels(parsed_ham): """ out_channels = [] for _, ham_str in parsed_ham: - chan_idx = [i for i, letter in enumerate(ham_str) if letter in ['D', 'U']] + chan_idx = [i for i, letter in enumerate(ham_str) if + letter in ['D', 'U']] for ch in chan_idx: - if (ch+1) == len(ham_str) or not ham_str[ch+1].isdigit(): - raise Exception('Channel name must include an integer labeling the qubit.') + if (ch + 1) == len(ham_str) or not ham_str[ch + 1].isdigit(): + raise Exception('Channel name must include' + + 'an integer labeling the qubit.') for kk in chan_idx: done = False offset = 0 while not done: offset += 1 - if not ham_str[kk+offset].isdigit(): + if not ham_str[kk + offset].isdigit(): done = True # In case we hit the end of the string - elif (kk+offset+1) == len(ham_str): + elif (kk + offset + 1) == len(ham_str): done = True offset += 1 - temp_chan = ham_str[kk:kk+offset] + temp_chan = ham_str[kk:kk + offset] if temp_chan not in out_channels: out_channels.append(temp_chan) out_channels.sort(key=lambda x: (int(x[1:]), x[0])) @@ -379,8 +373,8 @@ def build_pulse_arrays(qobj): total_pulse_length += len(pulse['samples']) num_pulse += 1 - idx = num_pulse+1 - #now go through experiments looking for PV gates + idx = num_pulse + 1 + # now go through experiments looking for PV gates pv_pulses = [] for exp in qobj['experiments']: for pulse in exp['instructions']: @@ -393,24 +387,25 @@ def build_pulse_arrays(qobj): pulse_dict['pv'] = pv_pulses pulses = np.empty(total_pulse_length, dtype=complex) - pulses_idx = np.zeros(idx+1, dtype=np.uint32) + pulses_idx = np.zeros(idx + 1, dtype=np.uint32) stop = 0 ind = 1 for _, pulse in enumerate(qobj_pulses): - stop = pulses_idx[ind-1] + len(pulse['samples']) + stop = pulses_idx[ind - 1] + len(pulse['samples']) pulses_idx[ind] = stop - oplist_to_array(pulse['samples'], pulses, pulses_idx[ind-1]) + oplist_to_array(pulse['samples'], pulses, pulses_idx[ind - 1]) ind += 1 for pv in pv_pulses: - stop = pulses_idx[ind-1] + 1 + stop = pulses_idx[ind - 1] + 1 pulses_idx[ind] = stop - oplist_to_array([pv[0]], pulses, pulses_idx[ind-1]) + oplist_to_array([pv[0]], pulses, pulses_idx[ind - 1]) ind += 1 return pulses, pulses_idx, pulse_dict + def experiment_to_structs(experiment, ham_chans, pulse_inds, pulse_to_int, dt, qubit_list=None): """Converts an experiment to a better formatted structure @@ -430,7 +425,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, ValueError: Channel not in Hamiltonian. TypeError: Incorrect snapshot type. """ - #TO DO: Error check that operations are restricted to qubit list + # TO DO: Error check that operations are restricted to qubit list max_time = 0 structs = {} structs['header'] = experiment['header'] @@ -442,9 +437,10 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, structs['snapshot'] = [] structs['tlist'] = [] structs['can_sample'] = True - # This is a list that tells us whether the last PV pulse on a channel needs to + # This is a list that tells us whether + # the last PV pulse on a channel needs to # be assigned a final time based on the next pulse on that channel - pv_needs_tf = [0]*len(ham_chans) + pv_needs_tf = [0] * len(ham_chans) # The instructions are time-ordered so just loop through them. for inst in experiment['instructions']: @@ -483,7 +479,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, else: start = inst['t0'] pulse_int = pulse_to_int[inst['name']] - pulse_width = (pulse_inds[pulse_int+1]-pulse_inds[pulse_int])*dt + pulse_width = (pulse_inds[pulse_int + 1] - pulse_inds[pulse_int]) * dt stop = start + pulse_width structs['channels'][chan_name][0].extend([start, stop, pulse_int, cond]) @@ -494,7 +490,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, # measurements if inst['name'] == 'acquire': - #Better way?? + # Better way?? qlist2 = [] mlist2 = [] if qubit_list is None: @@ -506,21 +502,19 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, qlist2.append(qb) mlist2.append(inst['memory_slot'][qind]) - - - acq_vals = [inst['t0'], np.asarray(qlist2, dtype=np.uint32), np.asarray(mlist2, dtype=np.uint32) - ] + ] if 'register_slot' in inst.keys(): - acq_vals.append(np.asarray(inst['register_slot'], dtype=np.uint32)) + acq_vals.append(np.asarray(inst['register_slot'], + dtype=np.uint32)) else: acq_vals.append(None) structs['acquire'].append(acq_vals) - #update max_time - max_time = max(max_time, inst['t0']+dt*inst['duration']) + # update max_time + max_time = max(max_time, inst['t0'] + dt * inst['duration']) # Add time to tlist if inst['t0'] not in structs['tlist']: @@ -537,7 +531,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, structs['cond'].append(acq_vals) - #update max_time + # update max_time max_time = max(max_time, inst['t0']) # Add time to tlist @@ -554,7 +548,7 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, if inst['t0'] not in structs['tlist']: structs['tlist'].append(inst['t0']) - #update max_time + # update max_time max_time = max(max_time, inst['t0']) # If any PVs still need time then they are at the end @@ -567,10 +561,12 @@ def experiment_to_structs(experiment, ham_chans, pulse_inds, # Convert lists to numpy arrays for key in structs['channels'].keys(): - structs['channels'][key][0] = np.asarray(structs['channels'][key][0], dtype=float) - structs['channels'][key][1] = np.asarray(structs['channels'][key][1], dtype=float) + structs['channels'][key][0] = np.asarray(structs['channels'][key][0], + dtype=float) + structs['channels'][key][1] = np.asarray(structs['channels'][key][1], + dtype=float) - structs['tlist'] = np.asarray([0]+structs['tlist'], dtype=float) + structs['tlist'] = np.asarray([0] + structs['tlist'], dtype=float) if len(structs['acquire']) > 1 or structs['tlist'][-1] > structs['acquire'][-1][0]: structs['can_sample'] = False diff --git a/qiskit/providers/aer/openpulse/qobj/op_qobj.py b/qiskit/providers/aer/openpulse/qobj/op_qobj.py index 3c092981a2..e879d7fdea 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_qobj.py +++ b/qiskit/providers/aer/openpulse/qobj/op_qobj.py @@ -22,6 +22,7 @@ from ..qutip_lite.qobj import Qobj from ..qutip_lite.cy.spmatfuncs import (spmv_csr, cy_expect_psi_csr) + def sigmax(dim=2): """Qiskit wrapper of sigma-X operator. """ @@ -163,6 +164,7 @@ def basis(level, pos): """ return st.basis(level, pos) + def state(state_vec): """ Qiskit wrapper of qobj """ diff --git a/qiskit/providers/aer/openpulse/qobj/op_system.py b/qiskit/providers/aer/openpulse/qobj/op_system.py index 504dccb119..f21b537810 100644 --- a/qiskit/providers/aer/openpulse/qobj/op_system.py +++ b/qiskit/providers/aer/openpulse/qobj/op_system.py @@ -15,6 +15,7 @@ "The OpenPulse simulator system class" + class OPSystem(): """ A Class that holds all the information needed to simulate a given PULSE qobj diff --git a/qiskit/providers/aer/openpulse/qobj/operators.py b/qiskit/providers/aer/openpulse/qobj/operators.py index 83b4229a08..c3c5f38054 100644 --- a/qiskit/providers/aer/openpulse/qobj/operators.py +++ b/qiskit/providers/aer/openpulse/qobj/operators.py @@ -19,6 +19,7 @@ import scipy.linalg as la from ..qobj import op_qobj as op + def gen_oper(opname, index, h_osc, h_qub, states=None): """Generate quantum operators. @@ -42,11 +43,12 @@ def gen_oper(opname, index, h_osc, h_qub, states=None): if opname in ['X', 'Y', 'Z'] and dim > 2: if opname == 'X': - opr_tmp = op.get_oper('A', dim)+op.get_oper('C', dim) + opr_tmp = op.get_oper('A', dim) + op.get_oper('C', dim) elif opname == 'Y': - opr_tmp = -1j*op.get_oper('A', dim)+1j*op.get_oper('C', dim) + opr_tmp = (-1j * op.get_oper('A', dim) + + 1j * op.get_oper('C', dim)) else: - opr_tmp = op.get_oper('I', dim) - 2*op.get_oper('N', dim) + opr_tmp = op.get_oper('I', dim) - 2 * op.get_oper('N', dim) else: is_qubit = False @@ -109,6 +111,7 @@ def qubit_occ_oper(target_qubit, h_osc, h_qub, level=0): return op.tensor(opers) + def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): """Builds the occupation number operator for a target qubit in a qubit oscillator system, where the oscillator are the first @@ -131,7 +134,7 @@ def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): # osc_n * … * osc_0 * qubit_n * … * qubit_0 states = [] - proj_op = 0*op.fock_dm(len(estates), 0) + proj_op = 0 * op.fock_dm(len(estates), 0) for ii, dd in rev_h_osc: states.append(op.basis(dd, 0)) for ii, dd in rev_h_qub: @@ -140,7 +143,6 @@ def qubit_occ_oper_dressed(target_qubit, estates, h_osc, h_qub, level=0): else: states.append(op.state(np.ones(dd))) - state = op.tensor(states) for ii, estate in enumerate(estates): @@ -163,7 +165,7 @@ def measure_outcomes(measured_qubits, state_vector, measure_ops, Returns: str: String of binaries representing measured qubit values. """ - outcome_len = max(measured_qubits)+1 + outcome_len = max(measured_qubits) + 1 # Create random generator with given seed (if any). rng_gen = np.random.RandomState(seed) rnds = rng_gen.rand(outcome_len) @@ -218,7 +220,8 @@ def apply_projector(measured_qubits, results, h_qub, h_osc, state_vector): return psi -# pylint: disable=dangerous-default-value + +# pylint: disable=dangerous-default-value,comparison-with-callable def init_fock_state(h_osc, h_qub, noise_dict={}): """ Generate initial Fock state, in the number state basis, for an oscillator in a thermal state defined @@ -246,7 +249,7 @@ def init_fock_state(h_osc, h_qub, noise_dict={}): # consider finite thermal excitation levels = np.arange(dd) beta = np.log(1.0 / n_thermal + 1.0) - diags = np.exp(-beta * levels) + diags = np.exp(-1.0 * beta * levels) diags /= np.sum(diags) cum_sum = np.cumsum(diags) idx = np.where(np.random.random() < cum_sum)[0][0] diff --git a/qiskit/providers/aer/openpulse/qobj/opparse.py b/qiskit/providers/aer/openpulse/qobj/opparse.py index bc73219b01..53bee5c34e 100644 --- a/qiskit/providers/aer/openpulse/qobj/opparse.py +++ b/qiskit/providers/aer/openpulse/qobj/opparse.py @@ -133,7 +133,7 @@ def _expand_sum(self): # substitute iterator value _temp = [] - for kk in range(_l, _u+1): + for kk in range(_l, _u + 1): trg_s = ham[p_sums[0].end():p_brks[ii].start()] # generate replacement pattern pattern = {} @@ -211,9 +211,9 @@ def _tokenizer(self, op_str, qubit_list=None): if any([k.type == 'Var' for k in token_list]): for ii, _ in enumerate(token_list): if token_list[ii].name == '*': - if all([k.type != 'Var' for k in token_list[ii+1:]]): + if all([k.type != 'Var' for k in token_list[ii + 1:]]): coef = ''.join([k.name for k in token_list[:ii]]) - token_list = token_list[ii+1:] + token_list = token_list[ii + 1:] break else: raise Exception('Invalid order of operators and coefficients in %s' % op_str) @@ -366,6 +366,7 @@ def math_priority(o1, o2): else: return True + # pylint: disable=dangerous-default-value def parse_binop(op_str, operands={}, cast_str=True): """ Calculate binary operation in string format @@ -392,22 +393,22 @@ def parse_binop(op_str, operands={}, cast_str=True): if val0.isdecimal() and val1.isdecimal(): retv = int(val0) + int(val1) else: - retv = '+'.join(str(val0), str(val1)) + retv = '+'.join([str(val0), str(val1)]) elif key == 'sub': if val0.isdecimal() and val1.isdecimal(): retv = int(val0) - int(val1) else: - retv = '-'.join(str(val0), str(val1)) + retv = '-'.join([str(val0), str(val1)]) elif key == 'mul': if val0.isdecimal() and val1.isdecimal(): retv = int(val0) * int(val1) else: - retv = '*'.join(str(val0), str(val1)) + retv = '*'.join([str(val0), str(val1)]) elif key == 'div': if val0.isdecimal() and val1.isdecimal(): retv = int(val0) / int(val1) else: - retv = '/'.join(str(val0), str(val1)) + retv = '/'.join([str(val0), str(val1)]) else: retv = 0 break diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py index acd1e9fbe0..2d1ca0b4be 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/pyxbuilder.py @@ -44,23 +44,30 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### -import sys, os +"""Utility for making the build options for compiling the Hamiltonian""" +import sys +import os import pyximport -from pyximport import install -old_get_distutils_extension = pyximport.pyximport.get_distutils_extension +OLD_EXT = pyximport.pyximport.get_distutils_extension + def new_get_distutils_extension(modname, pyxfilename, language_level=None): - extension_mod, setup_args = old_get_distutils_extension(modname, pyxfilename, language_level) - extension_mod.language='c++' + """Get the distutils extension""" + extension_mod, setup_args = OLD_EXT(modname, pyxfilename, language_level) + extension_mod.language = 'c++' # If on Win and Python version >= 3.5 and not in MSYS2 (i.e. Visual studio compile) - if sys.platform == 'win32' and int(str(sys.version_info[0])+str(sys.version_info[1])) >= 35 and os.environ.get('MSYSTEM') is None: + if sys.platform == 'win32' and \ + (int(str(sys.version_info[0]) + str(sys.version_info[1])) >= 35) \ + and os.environ.get('MSYSTEM') is None: + extension_mod.extra_compile_args = ['/w', '/O1'] else: extension_mod.extra_compile_args = ['-w', '-O1'] if sys.platform == 'darwin': extension_mod.extra_compile_args.append('-mmacosx-version-min=10.9') extension_mod.extra_link_args = ['-mmacosx-version-min=10.9'] - return extension_mod,setup_args + return extension_mod, setup_args + pyximport.pyximport.get_distutils_extension = new_get_distutils_extension diff --git a/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py index f79772eaa5..1c9cd5ffdf 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/cy/utilities.py @@ -44,24 +44,29 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +# pylint: disable=invalid-name, no-name-in-module, import-error +"""Cython utilities""" import os + def _cython_build_cleanup(tdname, build_dir=None): + """Cleanup cython build files + """ if build_dir is None: build_dir = os.path.join(os.path.expanduser('~'), '.pyxbld') - + # Remove tdname.pyx pyx_file = tdname + ".pyx" try: os.remove(pyx_file) - except: + except OSError: pass - + # Remove temp build files - for dirpath, subdirs, files in os.walk(build_dir): - for f in files: - if f.startswith(tdname): + for dirpath, _, files in os.walk(build_dir): + for file in files: + if file.startswith(tdname): try: - os.remove(os.path.join(dirpath,f)) - except: + os.remove(os.path.join(dirpath, file)) + except OSError: pass diff --git a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py index 394ee124a3..c65b481838 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/dimensions.py @@ -49,11 +49,13 @@ Internal use module for manipulating dims specifications. """ -__all__ = [] # Everything should be explicitly imported, not made available - # by default. +__all__ = [] +# Everything should be explicitly imported, not made available +# by default. import numpy as np + def flatten(l): """Flattens a list of lists to the first level. @@ -73,6 +75,7 @@ def flatten(l): else: return sum(map(flatten, l), []) + def is_scalar(dims): """ Returns True if a dims specification is effectively @@ -80,6 +83,7 @@ def is_scalar(dims): """ return np.prod(flatten(dims)) == 1 + def is_vector(dims): """Is a vector""" return ( @@ -87,6 +91,7 @@ def is_vector(dims): isinstance(dims[0], (int, np.integer)) ) + def is_vectorized_oper(dims): """Is a vectorized operator.""" return ( @@ -95,6 +100,7 @@ def is_vectorized_oper(dims): ) +# pylint: disable=too-many-return-statements def type_from_dims(dims, enforce_square=True): """Get the type of operator from dims structure""" bra_like, ket_like = map(is_scalar, dims) @@ -142,6 +148,7 @@ def _enumerate_flat(l, idx=0): acc.append(labels) return acc, idx + def _collapse_composite_index(dims): """ Given the dimensions specification for a composite index @@ -152,6 +159,7 @@ def _collapse_composite_index(dims): """ return [np.prod(dims)] + def _collapse_dims_to_level(dims, level=1): """ Recursively collapses all indices in a dimensions specification @@ -163,6 +171,7 @@ def _collapse_dims_to_level(dims, level=1): else: return [_collapse_dims_to_level(index, level=level - 1) for index in dims] + def collapse_dims_super(dims): """ Given the dimensions specifications for an operator-ket-, operator-bra- or diff --git a/qiskit/providers/aer/openpulse/qutip_lite/expect.py b/qiskit/providers/aer/openpulse/qutip_lite/expect.py index edaeba7be1..6843391ca9 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/expect.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/expect.py @@ -105,6 +105,7 @@ def expect(oper, state): else: raise TypeError('Arguments must be quantum objects') + # pylint: disable=inconsistent-return-statements def _single_qobj_expect(oper, state): """ diff --git a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py index 353111401f..e77c68eefc 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/fastsparse.py @@ -57,6 +57,7 @@ from scipy.sparse.sputils import (upcast, isdense, isscalarlike, get_index_dtype) from scipy.sparse.base import SparseEfficiencyWarning + class fast_csr_matrix(csr_matrix): """ A subclass of scipy.sparse.csr_matrix that skips the data format @@ -64,12 +65,12 @@ class fast_csr_matrix(csr_matrix): """ # pylint: disable=super-init-not-called def __init__(self, args=None, shape=None, dtype=None, copy=False): - if args is None: #Build zero matrix + if args is None: # Build zero matrix if shape is None: raise Exception('Shape must be given when building zero matrix.') self.data = np.array([], dtype=complex) self.indices = np.array([], dtype=np.int32) - self.indptr = np.zeros(shape[0]+1, dtype=np.int32) + self.indptr = np.zeros(shape[0] + 1, dtype=np.int32) self._shape = tuple(int(s) for s in shape) else: @@ -83,7 +84,7 @@ def __init__(self, args=None, shape=None, dtype=None, copy=False): self.indices = np.array(args[1], dtype=np.int32, copy=copy) self.indptr = np.array(args[2], dtype=np.int32, copy=copy) if shape is None: - self._shape = tuple([len(self.indptr)-1]*2) + self._shape = tuple([len(self.indptr) - 1] * 2) else: self._shape = tuple(int(s) for s in shape) self.dtype = complex @@ -130,12 +131,13 @@ def _binopt(self, other, op): # too much waste, trim arrays indices = indices.copy() data = data.copy() - if isinstance(other, fast_csr_matrix) and (not op in bool_ops): + if isinstance(other, fast_csr_matrix) and (op not in bool_ops): A = fast_csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) else: A = csr_matrix((data, indices, indptr), dtype=data.dtype, shape=self.shape) return A + # pylint: disable=too-many-return-statements def multiply(self, other): """Point-wise multiplication by another matrix, vector, or scalar. @@ -210,7 +212,7 @@ def _mul_sparse_matrix(self, other): other = csr_matrix(other) # convert to this format idx_dtype = get_index_dtype((self.indptr, self.indices, other.indptr, other.indices), - maxval=M*N) + maxval=M * N) indptr = np.empty(major_axis + 1, dtype=idx_dtype) fn = getattr(_sparsetools, self.format + '_matmat_pass1') @@ -249,6 +251,7 @@ def _scalar_binopt(self, other, op): res.eliminate_zeros() return res + # pylint: disable=too-many-return-statements def __eq__(self, other): # Scalar other. if isscalarlike(other): @@ -270,7 +273,7 @@ def __eq__(self, other): elif isspmatrix(other): warn("Comparing sparse matrices using == is inefficient, try using" " != instead.", SparseEfficiencyWarning) - #TODO sparse broadcasting + # TODO sparse broadcasting if self.shape != other.shape: return False elif self.format != other.format: @@ -281,6 +284,7 @@ def __eq__(self, other): else: return False + # pylint: disable=too-many-return-statements def __ne__(self, other): # Scalar other. if isscalarlike(other): @@ -302,7 +306,7 @@ def __ne__(self, other): return self.todense() != other # Sparse other. elif isspmatrix(other): - #TODO sparse broadcasting + # TODO sparse broadcasting if self.shape != other.shape: return True elif self.format != other.format: @@ -316,7 +320,8 @@ def _inequality(self, other, op, op_name, bad_scalar_msg): if isscalarlike(other): if other == 0 and op_name in ('_le_', '_ge_'): raise NotImplementedError(" >= and <= don't work with 0.") - elif op(0, other): + + if op(0, other): warn(bad_scalar_msg, SparseEfficiencyWarning) other_arr = np.empty(self.shape, dtype=np.result_type(other)) other_arr.fill(other) @@ -329,10 +334,10 @@ def _inequality(self, other, op, op_name, bad_scalar_msg): return op(self.todense(), other) # Sparse other. elif isspmatrix(other): - #TODO sparse broadcasting + # TODO sparse broadcasting if self.shape != other.shape: raise ValueError("inconsistent shapes") - elif self.format != other.format: + if self.format != other.format: other = other.asformat(self.format) if op_name not in ('_ge_', '_le_'): return self._binopt(other, op_name) @@ -359,6 +364,7 @@ def _with_data(self, data, copy=True): else: return fast_csr_matrix((data, self.indices, self.indptr), shape=self.shape, dtype=data.dtype) + # pylint: disable=arguments-differ def transpose(self): """ @@ -413,23 +419,22 @@ def fast_identity(N): """ data = np.ones(N, dtype=complex) ind = np.arange(N, dtype=np.int32) - ptr = np.arange(N+1, dtype=np.int32) + ptr = np.arange(N + 1, dtype=np.int32) ptr[-1] = N return fast_csr_matrix((data, ind, ptr), shape=(N, N)) -#Convenience functions -#-------------------- +# Convenience functions +# -------------------- def _all_true(shape): A = csr_matrix((np.ones(np.prod(shape), dtype=np.bool_), np.tile(np.arange(shape[1], dtype=np.int32), shape[0]), - np.arange(0, np.prod(shape)+1, shape[1], dtype=np.int32)), + np.arange(0, np.prod(shape) + 1, shape[1], dtype=np.int32)), shape=shape) return A - -#Need to do some trailing imports here -#------------------------------------- +# Need to do some trailing imports here +# ------------------------------------- # pylint: disable=no-name-in-module, wrong-import-position from .cy.spmath import (zcsr_transpose, zcsr_adjoint, zcsr_mult) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/operators.py b/qiskit/providers/aer/openpulse/qutip_lite/operators.py index b26e643220..4812b050c1 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/operators.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/operators.py @@ -57,9 +57,8 @@ import scipy.sparse as sp from .fastsparse import fast_csr_matrix, fast_identity -# + # Spin operators -# def jmat(j, *args): """Higher-order spin operators: @@ -106,8 +105,8 @@ def _jplus(j): data = (np.sqrt(j * (j + 1.0) - (m + 1.0) * m))[1:] N = m.shape[0] ind = np.arange(1, N, dtype=np.int32) - ptr = np.array(list(range(N-1))+[N-1]*2, dtype=np.int32) - ptr[-1] = N-1 + ptr = np.array(list(range(N - 1)) + [N - 1] * 2, dtype=np.int32) + ptr[-1] = N - 1 return fast_csr_matrix((data, ind, ptr), shape=(N, N)) @@ -115,19 +114,19 @@ def _jz(j): """ Internal functions for generating the data representing the J-z operator. """ - N = int(2*j+1) - data = np.array([j-k for k in range(N) if (j-k) != 0], dtype=complex) + N = int(2 * j + 1) + data = np.array([j - k for k in range(N) if (j - k) != 0], dtype=complex) # Even shaped matrix if N % 2 == 0: ind = np.arange(N, dtype=np.int32) - ptr = np.arange(N+1, dtype=np.int32) + ptr = np.arange(N + 1, dtype=np.int32) ptr[-1] = N # Odd shaped matrix else: j = int(j) - ind = np.array(list(range(j))+list(range(j+1, N)), dtype=np.int32) - ptr = np.array(list(range(j+1))+list(range(j, N)), dtype=np.int32) - ptr[-1] = N-1 + ind = np.array(list(range(j)) + list(range(j + 1, N)), dtype=np.int32) + ptr = np.array(list(range(j + 1)) + list(range(j, N)), dtype=np.int32) + ptr[-1] = N - 1 return fast_csr_matrix((data, ind, ptr), shape=(N, N)) @@ -321,10 +320,10 @@ def destroy(N, offset=0): """ if not isinstance(N, (int, np.integer)): # raise error if N not integer raise ValueError("Hilbert space dimension must be integer value") - data = np.sqrt(np.arange(offset+1, N+offset, dtype=complex)) + data = np.sqrt(np.arange(offset + 1, N + offset, dtype=complex)) ind = np.arange(1, N, dtype=np.int32) - ptr = np.arange(N+1, dtype=np.int32) - ptr[-1] = N-1 + ptr = np.arange(N + 1, dtype=np.int32) + ptr[-1] = N - 1 return Qobj(fast_csr_matrix((data, ind, ptr), shape=(N, N)), isherm=False) @@ -446,12 +445,12 @@ def num(N, offset=0): if offset == 0: data = np.arange(1, N, dtype=complex) ind = np.arange(1, N, dtype=np.int32) - ptr = np.array([0]+list(range(0, N)), dtype=np.int32) - ptr[-1] = N-1 + ptr = np.array([0] + list(range(0, N)), dtype=np.int32) + ptr[-1] = N - 1 else: data = np.arange(offset, offset + N, dtype=complex) ind = np.arange(N, dtype=np.int32) - ptr = np.arange(N+1, dtype=np.int32) + ptr = np.arange(N + 1, dtype=np.int32) ptr[-1] = N return Qobj(fast_csr_matrix((data, ind, ptr), @@ -582,14 +581,13 @@ def charge(Nmax, Nmin=None, frac=1): """ if Nmin is None: Nmin = -Nmax - diag = np.arange(Nmin, Nmax+1, dtype=float) + diag = np.arange(Nmin, Nmax + 1, dtype=float) if frac != 1: diag *= frac C = sp.diags(diag, 0, format='csr', dtype=complex) return Qobj(C, isherm=True) - def tunneling(N, m=1): """ Tunneling operator with elements of the form @@ -603,9 +601,11 @@ def tunneling(N, m=1): Returns: Qobj: Tunneling operator. """ - diags = [np.ones(N-m, dtype=int), np.ones(N-m, dtype=int)] + + diags = [np.ones(N - m, dtype=int), np.ones(N - m, dtype=int)] T = sp.diags(diags, [m, -m], format='csr', dtype=complex) return Qobj(T, isherm=True) + # pylint: disable=wrong-import-position from .qobj import Qobj diff --git a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py index 57c05cf702..b67f953a58 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/qobj.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/qobj.py @@ -64,9 +64,9 @@ ceil, copysign, cos, cosh, degrees, e, exp, expm1, fabs, floor, fmod, frexp, hypot, isinf, isnan, ldexp, log, log10, log1p, modf, pi, radians, sin, sinh, sqrt, tan, tanh, trunc) -from qiskit.providers.aer.version import __version__ import scipy.sparse as sp import scipy.linalg as la +from qiskit.providers.aer.version import __version__ from .settings import (auto_tidyup, auto_tidyup_dims, atol, auto_tidyup_atol) from .fastsparse import fast_csr_matrix, fast_identity from .sparse import (sp_eigs, sp_expm, sp_fro_norm, sp_max_norm, @@ -78,7 +78,7 @@ from .cy.sparse_utils import cy_tidyup -class Qobj(object): +class Qobj(): """A class for representing quantum objects, such as quantum operators and states. @@ -184,6 +184,7 @@ class Qobj(object): """ __array_priority__ = 100 # sets Qobj priority above numpy arrays + # pylint: disable=dangerous-default-value, redefined-builtin def __init__(self, inpt=None, dims=[[], []], shape=[], type=None, isherm=None, copy=True, @@ -205,6 +206,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], Raises: Exception: Something bad happened. """ + self._isherm = isherm self._type = type self.superrep = superrep @@ -258,7 +260,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], self._data = fast_csr_matrix(shape=(N, M)) - elif isinstance(inpt, list) or isinstance(inpt, tuple): + elif isinstance(inpt, (list, tuple)): # case where input is a list data = np.array(inpt) if len(data.shape) == 1: @@ -281,7 +283,7 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], do_copy = copy if not isinstance(inpt, fast_csr_matrix): _tmp = sp.csr_matrix(inpt, dtype=complex, copy=do_copy) - _tmp.sort_indices() #Make sure indices are sorted. + _tmp.sort_indices() # Make sure indices are sorted. do_copy = 0 else: _tmp = inpt @@ -320,10 +322,9 @@ def __init__(self, inpt=None, dims=[[], []], shape=[], # check if root of shape is int if (sub_shape % 1) != 0: raise Exception('Invalid shape for a super operator.') - else: - sub_shape = int(sub_shape) - self.dims = [[[sub_shape], [sub_shape]]]*2 + sub_shape = int(sub_shape) + self.dims = [[[sub_shape], [sub_shape]]] * 2 if superrep: self.superrep = superrep @@ -342,14 +343,14 @@ def get_data(self): """Gets underlying data.""" return self._data - #Here we perfrom a check of the csr matrix type during setting of Q.data + # Here we perfrom a check of the csr matrix type during setting of Q.data def set_data(self, data): """Data setter """ if not isinstance(data, fast_csr_matrix): raise TypeError('Qobj data must be in fast_csr format.') - else: - self._data = data + + self._data = data data = property(get_data, set_data) def __add__(self, other): @@ -359,9 +360,9 @@ def __add__(self, other): self._isunitary = None if not isinstance(other, Qobj): - if isinstance(other, (int, float, complex, np.integer, np.floating, - np.complexfloating, np.ndarray, list, tuple)) \ - or sp.issparse(other): + if isinstance(other, (int, float, complex, np.integer, + np.floating, np.complexfloating, np.ndarray, + list, tuple)) or sp.issparse(other): other = Qobj(other) else: return NotImplemented @@ -474,6 +475,7 @@ def __rsub__(self, other): """ return (-self) + other + # pylint: disable=too-many-return-statements def __mul__(self, other): """ MULTIPLICATION with Qobj on LEFT [ ex. Qobj*4 ] @@ -488,9 +490,9 @@ def __mul__(self, other): out.dims = dims if auto_tidyup: out.tidyup() - if (auto_tidyup_dims - and not isinstance(dims[0][0], list) - and not isinstance(dims[1][0], list)): + if (auto_tidyup_dims and not + isinstance(dims[0][0], list) and not + isinstance(dims[1][0], list)): # If neither left or right is a superoperator, # we should implicitly partial trace over # matching dimensions of 1. @@ -498,8 +500,8 @@ def __mul__(self, other): # to have uneven length (non-square Qobjs). # We use None as padding so that it doesn't match anything, # and will never cause a partial trace on the other side. - mask = [l == r == 1 for l, r in zip_longest(dims[0], dims[1], - fillvalue=None)] + mask = [ll == r == 1 for ll, r in + zip_longest(dims[0], dims[1], fillvalue=None)] # To ensure that there are still any dimensions left, we # use max() to add a dimensions list of [1] if all matching dims # are traced out of that side. @@ -547,7 +549,6 @@ def __mul__(self, other): else: return self.data * other - elif isinstance(other, list): # if other is a list, do element-wise multiplication return np.array([self * item for item in other], @@ -664,12 +665,9 @@ def __eq__(self, other): """ EQUALITY operator. """ - if (isinstance(other, Qobj) and - self.dims == other.dims and - not np.any(np.abs((self.data - other.data).data) > atol)): - return True - else: - return False + return bool(isinstance(other, Qobj) and + self.dims == other.dims and + not np.any(np.abs((self.data - other.data).data) > atol)) def __ne__(self, other): """ @@ -694,7 +692,7 @@ def __pow__(self, n, m=None): # calculates powers of Qobj out.superrep = self.superrep return out.tidyup() if auto_tidyup else out - except: + except ValueError: raise ValueError('Invalid choice of exponent.') def __abs__(self): @@ -752,11 +750,13 @@ def __call__(self, other): if not isinstance(other, Qobj): raise TypeError("Only defined for quantum objects.") - elif self.type == "oper": + if self.type == "oper": if other.type == "ket": return self * other else: raise TypeError("Can only act oper on ket.") + else: + return None def __getstate__(self): # defines what happens when Qobj object gets pickled @@ -850,7 +850,7 @@ def _format_element(_, n, d): s += _format_element(m, n, self.data[m, n]) s += r'\\' - elif M > 10 and N <= 10: + elif N <= 10 < M: # truncated vertically elongated matrix output for m in range(5): for n in range(N): @@ -866,7 +866,7 @@ def _format_element(_, n, d): s += _format_element(m, n, self.data[m, n]) s += r'\\' - elif M <= 10 and N > 10: + elif M <= 10 < N: # truncated horizontally elongated matrix output for m in range(M): for n in range(5): @@ -934,7 +934,7 @@ def norm(self, norm=None, sparse=False, tol=0, maxiter=100000): """ if self.type in ['oper', 'super']: if norm is None or norm == 'tr': - _op = self*self.dag() + _op = self * self.dag() vals = sp_eigs(_op.data, _op.isherm, vecs=False, sparse=sparse, tol=tol, maxiter=maxiter) return np.sum(np.sqrt(np.abs(vals))) @@ -1109,7 +1109,6 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): else: raise TypeError('Invalid operand for matrix square root') - def cosm(self): """Cosine of a quantum operator. @@ -1135,7 +1134,6 @@ def cosm(self): else: raise TypeError('Invalid operand for matrix square root') - def sinm(self): """Sine of a quantum operator. @@ -1158,8 +1156,8 @@ def sinm(self): """ if self.dims[0][0] == self.dims[1][0]: return -0.5j * ((1j * self).expm() - (-1j * self).expm()) - else: - raise TypeError('Invalid operand for matrix square root') + + raise TypeError('Invalid operand for matrix square root') def unit(self, inplace=False, norm=None, sparse=False, @@ -1171,9 +1169,11 @@ def unit(self, inplace=False, Args: inplace (bool): Do an in-place normalization norm (str): Requested norm for states / operators. - sparse (bool): Use sparse eigensolver for trace norm. Does not affect other norms. + sparse (bool): Use sparse eigensolver for trace norm. + Does not affect other norms. tol (float): Tolerance used by sparse eigensolver. - maxiter (int): Number of maximum iterations performed by sparse eigensolver. + maxiter (int): Number of maximum iterations performed by + sparse eigensolver. Returns: qobj.Qobj: Normalized quantum object if not in-place, else None. @@ -1186,6 +1186,8 @@ def unit(self, inplace=False, tol=tol, maxiter=maxiter) self.data /= nrm + + return None elif not inplace: out = self / self.norm(norm=norm, sparse=sparse, tol=tol, maxiter=maxiter) @@ -1212,8 +1214,8 @@ def tidyup(self, atol=auto_tidyup_atol): """ if self.data.nnz: - #This does the tidyup and returns True if - #The sparse data needs to be shortened + # This does the tidyup and returns True if + # The sparse data needs to be shortened if cy_tidyup(self.data.data, atol, self.data.nnz): self.data.eliminate_zeros() return self @@ -1259,7 +1261,6 @@ def transform(self, inpt, inverse=False, sparse=True): else: raise TypeError('Invalid operand for basis transformation') - # transform data if inverse: if self.isket: @@ -1291,7 +1292,6 @@ def transform(self, inpt, inverse=False, sparse=True): else: return out - def matrix_element(self, bra, ket): """Calculates a matrix element. @@ -1313,14 +1313,14 @@ def matrix_element(self, bra, ket): if not self.isoper: raise TypeError("Can only get matrix elements for an operator.") - else: - if bra.isbra and ket.isket: - return zcsr_mat_elem(self.data, bra.data, ket.data, 1) + if bra.isbra and ket.isket: + return zcsr_mat_elem(self.data, bra.data, ket.data, 1) - elif bra.isket and ket.isket: - return zcsr_mat_elem(self.data, bra.data, ket.data, 0) - else: - raise TypeError("Can only calculate matrix elements for bra and ket vectors.") + elif bra.isket and ket.isket: + return zcsr_mat_elem(self.data, bra.data, ket.data, 0) + else: + raise TypeError("Can only calculate matrix elements " + + "for bra and ket vectors.") def overlap(self, other): """Overlap between two state vectors or two operators. @@ -1355,39 +1355,44 @@ def overlap(self, other): if isinstance(other, Qobj): + returnval = 0 + if self.isbra: if other.isket: - return zcsr_inner(self.data, other.data, 1) + returnval = zcsr_inner(self.data, other.data, 1) elif other.isbra: - #Since we deal mainly with ket vectors, the bra-bra combo - #is not common, and not optimized. - return zcsr_inner(self.data, other.dag().data, 1) + # Since we deal mainly with ket vectors, the bra-bra combo + # is not common, and not optimized. + returnval = zcsr_inner(self.data, other.dag().data, 1) elif other.isoper: - return (states.ket2dm(self).dag() * other).tr() + returnval = (states.ket2dm(self).dag() * other).tr() else: - raise TypeError("Can only calculate overlap for state vector Qobjs") + raise TypeError("Can only calculate overlap for " + + "state vector Qobjs") elif self.isket: if other.isbra: - return zcsr_inner(other.data, self.data, 1) + returnval = zcsr_inner(other.data, self.data, 1) elif other.isket: - return zcsr_inner(self.data, other.data, 0) + returnval = zcsr_inner(self.data, other.data, 0) elif other.isoper: - return (states.ket2dm(self).dag() * other).tr() + returnval = (states.ket2dm(self).dag() * other).tr() else: - raise TypeError("Can only calculate overlap for state vector Qobjs") + raise TypeError("Can only calculate overlap for " + + "state vector Qobjs") elif self.isoper: if other.isket or other.isbra: - return (self.dag() * states.ket2dm(other)).tr() + returnval = (self.dag() * states.ket2dm(other)).tr() elif other.isoper: - return (self.dag() * other).tr() + returnval = (self.dag() * other).tr() else: - raise TypeError("Can only calculate overlap for state vector Qobjs") - - - raise TypeError("Can only calculate overlap for state vector Qobjs") + raise TypeError("Can only calculate overlap " + + "for state vector Qobjs") + else: + raise TypeError("Can only calculate overlap for state vector Qobjs") + return returnval def eigenstates(self, sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000): @@ -1438,11 +1443,9 @@ def eigenstates(self, sparse=False, sort='low', norms = np.array([ket.norm() for ket in ekets]) return evals, ekets / norms - def eigenenergies(self, sparse=False, sort='low', eigvals=0, tol=0, maxiter=100000): """Eigenenergies of a quantum object. - Eigenenergies (eigenvalues) are defined for operators or superoperators only. @@ -1504,7 +1507,7 @@ def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): if safe: if tol == 0: tol = 1e-15 - if (grndval[1]-grndval[0]) <= 10*tol: + if (grndval[1] - grndval[0]) <= 10 * tol: print("WARNING: Ground state may be degenerate. " "Use Q.eigenstates()") new_dims = [self.dims[0], [1] * len(self.dims[0])] @@ -1550,14 +1553,10 @@ def check_isunitary(self): """ if self.isoper: eye_data = fast_identity(self.shape[0]) - return not (np.any(np.abs((self.data*self.dag().data - - eye_data).data) - > atol) - or - np.any(np.abs((self.dag().data*self.data - - eye_data).data) > - atol) - ) + return not (np.any(np.abs((self.data * self.dag().data - + eye_data).data) > atol) or + np.any(np.abs((self.dag().data * self.data - + eye_data).data) > atol)) else: return False diff --git a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py index 4c44e6671d..22c1971380 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/sparse.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/sparse.py @@ -71,6 +71,7 @@ _dznrm2 = get_blas_funcs("znrm2") + def sp_fro_norm(data): """ Frobius norm for sparse matrix @@ -116,6 +117,7 @@ def sp_one_norm(A): A.indptr, A.shape[0], A.shape[1]) + # pylint: disable=redefined-builtin def sp_reshape(A, shape, format='csr'): """ @@ -448,8 +450,8 @@ def sp_permute(A, rperm=(), cperm=(), safe=True): nrows, ncols, rperm, cperm, flag) if kind == 'csr': return fast_csr_matrix((data, ind, ptr), shape=shp) - elif kind == 'csc': - return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) + + return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) def sp_reverse_permute(A, rperm=(), cperm=(), safe=True): @@ -503,8 +505,8 @@ def sp_reverse_permute(A, rperm=(), cperm=(), safe=True): if kind == 'csr': return fast_csr_matrix((data, ind, ptr), shape=shp) - elif kind == 'csc': - return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) + + return sp.csc_matrix((data, ind, ptr), shape=shp, dtype=data.dtype) def sp_bandwidth(A): @@ -567,7 +569,7 @@ def sp_profile(A): else: raise TypeError('Input sparse matrix must be in CSR or CSC format.') - return up+lp, lp, up + return up + lp, lp, up def sp_isdiag(A): diff --git a/qiskit/providers/aer/openpulse/qutip_lite/states.py b/qiskit/providers/aer/openpulse/qutip_lite/states.py index bce56a0272..3bfd4f4a2d 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/states.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/states.py @@ -89,7 +89,8 @@ def basis(N, n=0, offset=0): data = np.array([1], dtype=complex) ind = np.array([0], dtype=np.int32) - ptr = np.array([0]*((n - offset)+1)+[1]*(N-(n-offset)), dtype=np.int32) + ptr = np.array([0] * ((n - offset) + 1) + [1] * (N - (n - offset)), + dtype=np.int32) return Qobj(fast_csr_matrix((data, ind, ptr), shape=(N, 1)), isherm=False) @@ -136,15 +137,15 @@ def coherent(N, alpha, offset=0, method='operator'): elif method == "analytic" or offset > 0: - sqrtn = np.sqrt(np.arange(offset, offset+N, dtype=complex)) - sqrtn[0] = 1 # Get rid of divide by zero warning - data = alpha/sqrtn + sqrtn = np.sqrt(np.arange(offset, offset + N, dtype=complex)) + sqrtn[0] = 1 # Get rid of divide by zero warning + data = alpha / sqrtn if offset == 0: data[0] = np.exp(-abs(alpha)**2 / 2.0) else: - s = np.prod(np.sqrt(np.arange(1, offset + 1))) # sqrt factorial + s = np.prod(np.sqrt(np.arange(1, offset + 1))) # sqrt factorial data[0] = np.exp(-abs(alpha)**2 / 2.0) * alpha**(offset) / s - np.cumprod(data, out=sqrtn) # Reuse sqrtn array + np.cumprod(data, out=sqrtn) # Reuse sqrtn array return Qobj(sqrtn) else: @@ -252,7 +253,7 @@ def thermal_dm(N, n, method='operator'): i = arange(N) if method == 'operator': beta = np.log(1.0 / n + 1.0) - diags = np.exp(-beta * i) + diags = np.exp(-1 * beta * i) diags = diags / np.sum(diags) # populates diagonal terms using truncated operator expression rm = sp.spdiags(diags, 0, N, N, format='csr') @@ -283,7 +284,8 @@ def maximally_mixed_dm(N): if (not isinstance(N, (int, np.int64))) or N <= 0: raise ValueError("N must be integer N > 0") - dm = sp.spdiags(np.ones(N, dtype=complex)/float(N), 0, N, N, format='csr') + dm = sp.spdiags(np.ones(N, dtype=complex) / float(N), + 0, N, N, format='csr') return Qobj(dm, isherm=True) diff --git a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py index 8d701db624..aaf148f624 100755 --- a/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py +++ b/qiskit/providers/aer/openpulse/qutip_lite/superoperator.py @@ -56,6 +56,7 @@ from .sparse import sp_reshape from .cy.spmath import zcsr_kron # pylint: disable=no-name-in-module + # pylint: disable=dangerous-default-value def liouvillian(H, c_ops=[], data_only=False, chi=None): """Assembles the Liouvillian superoperator from a Hamiltonian @@ -134,8 +135,8 @@ def liouvillian(H, c_ops=[], data_only=False, chi=None): cd = c_.data.H c = c_.data if chi: - data = data + np.exp(1j * chi[idx]) * \ - zcsr_kron(c.conj(), c) + data = data + (np.exp(1j * chi[idx]) * + zcsr_kron(c.conj(), c)) else: data = data + zcsr_kron(c.conj(), c) cdc = cd * c diff --git a/qiskit/providers/aer/openpulse/solver/codegen.py b/qiskit/providers/aer/openpulse/solver/codegen.py index c6dbfe3f85..a044066071 100644 --- a/qiskit/providers/aer/openpulse/solver/codegen.py +++ b/qiskit/providers/aer/openpulse/solver/codegen.py @@ -27,10 +27,10 @@ _cython_path = os.path.abspath(cy.__file__).replace('__init__.py', '') _cython_path = _cython_path.replace("\\", "/") -_include_string = "'"+_cython_path+"complex_math.pxi'" +_include_string = "'" + _cython_path + "complex_math.pxi'" -class OPCodegen(object): +class OPCodegen(): """ Class for generating cython code files at runtime. """ @@ -110,11 +110,11 @@ def ODE_func_header(self): "int[::1] idx%d, " % k + "int[::1] ptr%d" % k) - #Add global vaiables + # Add global vaiables input_vars += (",\n " + "complex[::1] pulse_array") input_vars += (",\n " + "unsigned int[::1] pulse_indices") - #Add per experiment variables + # Add per experiment variables for key in self.op_system.channels.keys(): input_vars += (",\n " + "double[::1] %s_pulses" % key) input_vars += (",\n " + "double[::1] %s_fc" % key) @@ -133,7 +133,6 @@ def ODE_func_header(self): func_end = "):" return [func_name + input_vars + func_end] - def channels(self): """Write out the channels """ @@ -141,26 +140,25 @@ def channels(self): channel_lines.append("# Compute complex channel values at time `t`") for chan, idx in self.op_system.channels.items(): - chan_str = "%s = chan_value(t, %s, %s_freq, " %(chan, idx, chan) + \ + chan_str = "%s = chan_value(t, %s, %s_freq, " % (chan, idx, chan) + \ "%s_pulses, pulse_array, pulse_indices, " % chan + \ "%s_fc, register)" % (chan) channel_lines.append(chan_str) channel_lines.append('') return channel_lines - def func_vars(self): """Writes the variables and spmv parts""" func_vars = [] sp1 = " " - sp2 = sp1+sp1 + sp2 = sp1 + sp1 func_vars.append("# Eval the time-dependent terms and do SPMV.") - for idx in range(len(self.op_system.system)+1): + for idx in range(len(self.op_system.system) + 1): if (idx == len(self.op_system.system) and (len(self.op_system.system) < self.num_ham_terms)): - #this is the noise term + # this is the noise term term = [1.0, 1.0] elif idx < len(self.op_system.system): term = self.op_system.system[idx] @@ -176,21 +174,21 @@ def func_vars(self): func_vars.append(sp1 + "for row in range(num_rows):") func_vars.append(sp2 + "dot = 0;") - func_vars.append(sp2 + "row_start = ptr%d[row];"%idx) - func_vars.append(sp2 + "row_end = ptr%d[row+1];"%idx) + func_vars.append(sp2 + "row_start = ptr%d[row];" % idx) + func_vars.append(sp2 + "row_end = ptr%d[row+1];" % idx) func_vars.append(sp2 + "for jj in range(row_start,row_end):") func_vars.append(sp1 + sp2 + - "osc_term = exp(1j*(energ[row]-energ[idx%d[jj]])*t)"%idx) - func_vars.append(sp1 + sp2 + "if rowPyDataMem_NEW_ZEROED(num_rows,sizeof(complex))' - ] + ] func_vars.append("") for val in op_system.channels: func_vars.append("cdef double complex %s" % val) - for kk in range(len(op_system.system)+1): + for kk in range(len(op_system.system) + 1): func_vars.append("cdef double complex td%s" % kk) return func_vars + def cython_preamble(): """ Returns list of code segments for Cython preamble. @@ -268,7 +268,7 @@ def cython_preamble(): from qiskit.providers.aer.openpulse.cy.channel_value cimport chan_value -include """+_include_string+""" +include """ + _include_string + """ """] return preamble diff --git a/qiskit/providers/aer/openpulse/solver/data_config.py b/qiskit/providers/aer/openpulse/solver/data_config.py index 01deec2ae3..7be174dd64 100644 --- a/qiskit/providers/aer/openpulse/solver/data_config.py +++ b/qiskit/providers/aer/openpulse/solver/data_config.py @@ -18,6 +18,7 @@ import numpy as np pi = np.pi + def op_data_config(op_system): """ Preps the data for the opsolver. @@ -67,14 +68,15 @@ def op_data_config(op_system): H = H + [H_noise] # construct data sets - op_system.global_data['h_ops_data'] = [-1.0j* hpart.data.data for hpart in H] + op_system.global_data['h_ops_data'] = [-1.0j * hpart.data.data + for hpart in H] op_system.global_data['h_ops_ind'] = [hpart.data.indices for hpart in H] op_system.global_data['h_ops_ptr'] = [hpart.data.indptr for hpart in H] # setup ode args string ode_var_str = "" - #diagonal elements + # diagonal elements ode_var_str += "global_data['h_diag_elems'], " # Hamiltonian data @@ -103,13 +105,13 @@ def op_data_config(op_system): if chan != final_chan or var_list: ode_var_str += ', ' - #now do the variables + # now do the variables for idx, var in enumerate(var_list): ode_var_str += "global_data['vars'][%s]" % idx if var != final_var or freq_list: ode_var_str += ', ' - #now do the freq + # now do the freq for idx, freq in enumerate(freq_list): ode_var_str += "global_data['freqs'][%s]" % idx if freq != final_freq: @@ -119,6 +121,6 @@ def op_data_config(op_system): ode_var_str += ", register" op_system.global_data['string'] = ode_var_str - #Convert inital state to flat array in global_data + # Convert inital state to flat array in global_data op_system.global_data['initial_state'] = \ op_system.initial_state.full().ravel() diff --git a/qiskit/providers/aer/openpulse/solver/monte_carlo.py b/qiskit/providers/aer/openpulse/solver/monte_carlo.py index 34a9e27bdc..679fef050b 100644 --- a/qiskit/providers/aer/openpulse/solver/monte_carlo.py +++ b/qiskit/providers/aer/openpulse/solver/monte_carlo.py @@ -32,6 +32,7 @@ dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) + def monte_carlo(seed, exp, global_data, ode_options): """ Monte Carlo algorithm returning state-vector or expectation values @@ -77,7 +78,7 @@ def monte_carlo(seed, exp, global_data, ode_options): first_step=ode_options.first_step, min_step=ode_options.min_step, max_step=ode_options.max_step - ) + ) # Forces complex ODE solving if not any(ODE._y): ODE.t = 0.0 diff --git a/qiskit/providers/aer/openpulse/solver/opsolve.py b/qiskit/providers/aer/openpulse/solver/opsolve.py index afe410ffd5..6f2526040f 100644 --- a/qiskit/providers/aer/openpulse/solver/opsolve.py +++ b/qiskit/providers/aer/openpulse/solver/opsolve.py @@ -37,6 +37,7 @@ # _cy_rhs_func = None + def opsolve(op_system): """Opsolver """ @@ -50,7 +51,7 @@ def opsolve(op_system): # build Hamiltonian data structures op_data_config(op_system) - # compile Cython RHS + # compile Cython RHS _op_generate_rhs(op_system) # Load cython function _op_func_load(op_system) @@ -61,10 +62,11 @@ def opsolve(op_system): # Results are stored in ophandler.result return out + # ----------------------------------------------------------------------------- # MONTE CARLO CLASS # ----------------------------------------------------------------------------- -class OP_mcwf(object): +class OP_mcwf(): """ Private class for solving Monte Carlo evolution """ @@ -91,16 +93,15 @@ def __init__(self, op_system): prng = np.random.RandomState(op_system.global_data['seed']) else: prng = np.random.RandomState( - np.random.randint(np.iinfo(np.int32).max-1)) + np.random.randint(np.iinfo(np.int32).max - 1)) for exp in op_system.experiments: - exp['seed'] = prng.randint(np.iinfo(np.int32).max-1) + exp['seed'] = prng.randint(np.iinfo(np.int32).max - 1) def run(self): """Runs the solver. """ map_kwargs = {'num_processes': self.op_system.ode_options.num_cpus} - # exp_results from the solvers return the values of the measurement # operators @@ -118,13 +119,12 @@ def run(self): self.op_system.experiments, task_args=(self.op_system.global_data, self.op_system.ode_options - ), + ), **map_kwargs - ) + ) end = time.time() - exp_times = (np.ones(len(self.op_system.experiments))* - (end-start)/len(self.op_system.experiments)) - + exp_times = (np.ones(len(self.op_system.experiments)) * + (end - start) / len(self.op_system.experiments)) # need to simulate each trajectory, so shots*len(experiments) times # Do a for-loop over experiments, and do shots in parallel_map @@ -134,15 +134,16 @@ def run(self): for exp in self.op_system.experiments: start = time.time() rng = np.random.RandomState(exp['seed']) - seeds = rng.randint(np.iinfo(np.int32).max-1, + seeds = rng.randint(np.iinfo(np.int32).max - 1, size=self.op_system.global_data['shots']) exp_res = parallel_map(monte_carlo, seeds, - task_args=(exp, self.op_system.global_data, + task_args=(exp, + self.op_system.global_data, self.op_system.ode_options - ), + ), **map_kwargs - ) + ) # exp_results is a list for each shot # so transform back to an array of shots @@ -150,17 +151,14 @@ def run(self): for exp_shot in exp_res: exp_res2.append(exp_shot[0].tolist()) - end = time.time() - exp_times.append(end-start) + exp_times.append(end - start) exp_results.append(np.array(exp_res2)) - - #format the data into the proper output + # format the data into the proper output all_results = [] for idx_exp, exp in enumerate(self.op_system.experiments): - m_lev = self.op_system.global_data['meas_level'] m_ret = self.op_system.global_data['meas_return'] @@ -184,7 +182,8 @@ def run(self): # integer # e.g. [1,0] -> 2 int_mem = memory.dot(np.power(2.0, - np.arange(memory.shape[1]-1, -1, -1))).astype(int) + np.arange(memory.shape[1] - 1, + -1, -1))).astype(int) # if the memory flag is set return each shot if self.op_system.global_data['memory']: @@ -220,7 +219,6 @@ def run(self): if m_ret == 'avg': results['data']['memory'] = results['data']['memory'][0] - all_results.append(results) _cython_build_cleanup(self.op_system.global_data['rhs_file_name']) @@ -232,7 +230,7 @@ def _proj_measurement(pid, ophandler, tt, state, memory, register=None): """ Projection measurement of quantum state """ - prng = np.random.RandomState(np.random.randint(np.iinfo(np.int32).max-1)) + prng = np.random.RandomState(np.random.randint(np.iinfo(np.int32).max - 1)) qubits = [] results = [] @@ -269,7 +267,8 @@ def _proj_measurement(pid, ophandler, tt, state, memory, register=None): # projection if any(qubits): - psi_proj = apply_projector(qubits, results, ophandler.h_qub, ophandler.h_osc, state) + psi_proj = apply_projector(qubits, results, ophandler.h_qub, + ophandler.h_osc, state) else: psi_proj = state diff --git a/qiskit/providers/aer/openpulse/solver/options.py b/qiskit/providers/aer/openpulse/solver/options.py index deae400076..4323f98eaf 100644 --- a/qiskit/providers/aer/openpulse/solver/options.py +++ b/qiskit/providers/aer/openpulse/solver/options.py @@ -14,7 +14,8 @@ """OpenPulse options""" -class OPoptions(object): + +class OPoptions(): """ Class of options for opsolver. Options can be specified either as arguments to the constructor:: @@ -103,5 +104,6 @@ def __init__(self, atol=1e-8, rtol=1e-6, method='adams', order=12, def __str__(self): return str(vars(self)) + def __repr__(self): return self.__str__() diff --git a/qiskit/providers/aer/openpulse/solver/rhs_utils.py b/qiskit/providers/aer/openpulse/solver/rhs_utils.py index 98c19a54f3..7071c914ea 100644 --- a/qiskit/providers/aer/openpulse/solver/rhs_utils.py +++ b/qiskit/providers/aer/openpulse/solver/rhs_utils.py @@ -26,11 +26,12 @@ def _op_generate_rhs(op_system): Args: op_system (OPSystem): An OpenPulse system object. """ - name = "rhs" + str(os.getpid()) + str(op_set.CGEN_NUM)+'_op' + name = "rhs" + str(os.getpid()) + str(op_set.CGEN_NUM) + '_op' op_system.global_data['rhs_file_name'] = name cgen = OPCodegen(op_system) cgen.generate(name + ".pyx") + def _op_func_load(op_system): """Loads the Cython function defined in the file `rhs_file_name.pyx` where `rhs_file_name` is diff --git a/qiskit/providers/aer/openpulse/solver/unitary.py b/qiskit/providers/aer/openpulse/solver/unitary.py index f3084e11b2..6139cb1a46 100644 --- a/qiskit/providers/aer/openpulse/solver/unitary.py +++ b/qiskit/providers/aer/openpulse/solver/unitary.py @@ -24,6 +24,7 @@ dznrm2 = get_blas_funcs("znrm2", dtype=np.float64) + def unitary_evolution(exp, global_data, ode_options): """ Calculates evolution when there is no noise, @@ -68,7 +69,7 @@ def unitary_evolution(exp, global_data, ode_options): _inst = 'ODE.set_f_params(%s)' % global_data['string'] code = compile(_inst, '', 'exec') - exec(code) # pylint disable=exec-used + exec(code) # pylint disable=exec-used if not ODE._y: ODE.t = 0.0 @@ -90,11 +91,11 @@ def unitary_evolution(exp, global_data, ode_options): # set channel and frame change indexing arrays # Do final measurement at end - psi_rot = np.exp(-1j*global_data['h_diag_elems']*ODE.t) + psi_rot = np.exp(-1j * global_data['h_diag_elems'] * ODE.t) psi *= psi_rot qubits = exp['acquire'][0][1] memory_slots = exp['acquire'][0][2] probs = occ_probabilities(qubits, psi, global_data['measurement_ops']) - rand_vals = rng.rand(memory_slots.shape[0]*shots) + rand_vals = rng.rand(memory_slots.shape[0] * shots) write_shots_memory(memory, memory_slots, probs, rand_vals) return memory