1# This file is part of QuTiP: Quantum Toolbox in Python.
2#
3#    Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson.
4#    All rights reserved.
5#
6#    Redistribution and use in source and binary forms, with or without
7#    modification, are permitted provided that the following conditions are
8#    met:
9#
10#    1. Redistributions of source code must retain the above copyright notice,
11#       this list of conditions and the following disclaimer.
12#
13#    2. Redistributions in binary form must reproduce the above copyright
14#       notice, this list of conditions and the following disclaimer in the
15#       documentation and/or other materials provided with the distribution.
16#
17#    3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names
18#       of its contributors may be used to endorse or promote products derived
19#       from this software without specific prior written permission.
20#
21#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22#    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23#    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24#    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25#    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26#    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27#    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29#    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30#    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31#    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32###############################################################################
33from collections.abc import Iterable
34import numbers
35
36import numpy as np
37
38from qutip.qobj import Qobj
39from qutip.qobjevo import QobjEvo
40from qutip.qip.operations.gates import globalphase
41from qutip.tensor import tensor
42from qutip.mesolve import mesolve
43from qutip.qip.circuit import QubitCircuit
44from qutip.qip.device.processor import Processor
45
46
47__all__ = ['ModelProcessor']
48
49
50class ModelProcessor(Processor):
51    """
52    The base class for a circuit processor simulating a physical device,
53    e.g cavityQED, spinchain.
54    The available Hamiltonian of the system is predefined.
55    The processor can simulate the evolution under the given
56    control pulses either numerically or analytically.
57    It cannot be used alone, please refer to the sub-classes.
58    (Only additional attributes are documented here, for others please
59    refer to the parent class :class:`.Processor`)
60
61    Parameters
62    ----------
63    N: int
64        The number of component systems.
65
66    correct_global_phase: boolean, optional
67        If true, the analytical solution will track the global phase. It
68        has no effect on the numerical solution.
69
70    t1: list or float
71        Characterize the decoherence of amplitude damping for
72        each qubit. A list of size `N` or a float for all qubits.
73
74    t2: list of float
75        Characterize the decoherence of dephasing for
76        each qubit. A list of size `N` or a float for all qubits.
77
78    Attributes
79    ----------
80    params: dict
81        A Python dictionary contains the name and the value of the parameters
82        in the physical realization, such as laser frequency, detuning etc.
83
84    correct_global_phase: float
85        Save the global phase, the analytical solution
86        will track the global phase.
87        It has no effect on the numerical solution.
88    """
89    def __init__(self, N, correct_global_phase=True, t1=None, t2=None):
90        super(ModelProcessor, self).__init__(N, t1=t1, t2=t2)
91        self.correct_global_phase = correct_global_phase
92        self.global_phase = 0.
93        self._params = {}
94
95    def to_array(self, params, N):
96        """
97        Transfer a parameter to an array.
98        """
99        if isinstance(params, numbers.Real):
100            return np.asarray([params] * N)
101        elif isinstance(params, Iterable):
102            return np.asarray(params)
103
104    def set_up_params(self):
105        """
106        Save the parameters in the attribute `params` and check the validity.
107        (Defined in subclasses)
108
109        Notes
110        -----
111        All parameters will be multiplied by 2*pi for simplicity
112        """
113        raise NotImplementedError("Parameters should be defined in subclass.")
114
115    @property
116    def params(self):
117        return self._params
118
119    @params.setter
120    def params(self, par):
121        self._params = par
122
123    def run_state(self, init_state=None, analytical=False, qc=None,
124                  states=None, **kwargs):
125        """
126        If `analytical` is False, use :func:`qutip.mesolve` to
127        calculate the time of the state evolution
128        and return the result. Other arguments of mesolve can be
129        given as keyword arguments.
130        If `analytical` is True, calculate the propagator
131        with matrix exponentiation and return a list of matrices.
132
133        Parameters
134        ----------
135        init_state: Qobj
136            Initial density matrix or state vector (ket).
137
138        analytical: boolean
139            If True, calculate the evolution with matrices exponentiation.
140
141        qc: :class:`.QubitCircuit`, optional
142            A quantum circuit. If given, it first calls the ``load_circuit``
143            and then calculate the evolution.
144
145        states: :class:`qutip.Qobj`, optional
146         Old API, same as init_state.
147
148        **kwargs
149           Keyword arguments for the qutip solver.
150
151        Returns
152        -------
153        evo_result: :class:`qutip.Result`
154            If ``analytical`` is False,  an instance of the class
155            :class:`qutip.Result` will be returned.
156
157            If ``analytical`` is True, a list of matrices representation
158            is returned.
159        """
160        if qc is not None:
161            self.load_circuit(qc)
162        return super(ModelProcessor, self).run_state(
163            init_state=init_state, analytical=analytical,
164            states=states, **kwargs)
165
166    def get_ops_and_u(self):
167        """
168        Get the labels for each Hamiltonian.
169
170        Returns
171        -------
172        ctrls: list
173            The list of Hamiltonians
174        coeffs: array_like
175            The transposed pulse matrix
176        """
177        return (self.ctrls, self.get_full_coeffs().T)
178
179    def pulse_matrix(self, dt=0.01):
180        """
181        Generates the pulse matrix for the desired physical system.
182
183        Returns
184        -------
185        t, u, labels:
186            Returns the total time and label for every operation.
187        """
188        ctrls = self.ctrls
189        coeffs = self.get_full_coeffs().T
190
191        # FIXME This might becomes a problem if new tlist other than
192        # int the default pulses are added.
193        tlist = self.get_full_tlist()
194        dt_list = tlist[1:] - tlist[:-1]
195        t_tot = tlist[-1]
196        num_step = int(np.ceil(t_tot / dt))
197
198        t = np.linspace(0, t_tot, num_step)
199        u = np.zeros((len(ctrls), num_step))
200
201        t_start = 0
202        for n in range(len(dt_list)):
203            t_idx_len = int(np.floor(dt_list[n] / dt))
204            mm = 0
205            for m in range(len(ctrls)):
206                u[mm, t_start:(t_start + t_idx_len)] = (np.ones(t_idx_len) *
207                                                        coeffs[n, m])
208                mm += 1
209            t_start += t_idx_len
210
211        return t, u, self.get_operators_labels()
212