Beispiel #1
0
    def __init__(self, name: str, probs: typing.List[float], level: int):
        """

        Parameters
        ----------
        name: str
            what the name of the noise is. Determines how many probabilites are needed additionally.
        probs: list:
            a list of probabilities with which to apply the requested noise
        level: int:
            the number of qubits in the gates this noise acts upon.
        """
        probs = list_assignment(probs)
        if name not in noises_available:
            raise TequilaException(
                'The name you asked for, {}, is not recognized'.format(name))
        self._name = name
        self._level = int(level)

        if len(probs) != self.prob_length[name]:
            raise TequilaException(
                '{} noise requires {} probabilities; recieved {}'.format(
                    name, self.prob_length[name], len(probs)))
        if name in krausses:
            assert sum(probs) <= 1.
        self.probs = list_assignment(probs)
Beispiel #2
0
 def __init__(self,
              name,
              target: UnionList,
              control: UnionList = None,
              generator: QubitHamiltonian = None):
     self._name = name
     self._target = tuple(list_assignment(target))
     self._control = tuple(list_assignment(control))
     self.finalize()
     self.generator = generator
Beispiel #3
0
 def __init__(self, name, target: UnionList, control: UnionList = None):
     self._name = name
     self._target = tuple(list_assignment(target))
     self._control = tuple(list_assignment(control))
     self.finalize()
     # Set the active qubits
     if self.control:
         self._qubits = self.target + self.control
     else:
         self._qubits = self.target
     self._qubits = sorted(tuple(set(self._qubits)))
     self._max_qubit = self.compute_max_qubit()
Beispiel #4
0
def AmplitudeDamp(p: float, level: int):
    '''
    Returns a NoiseModel one QuantumNoise, corresponding to amplitude damping.
    this channel takes 1 to 0, but leaves 0 unaffected.
    kraus maps:

    E_0= [[1,0],
          [0,sqrt(1-p)]]
    E_1= [[0,sqrt(p)],
          [0,0]]

    Parameters
    ----------
    p: float:
        the probability with which the noise is applied.
    level: int:
        the # of qubits in operations to apply this noise to.

    Returns
    -------
    NoiseModel
    '''

    new = NoiseModel.wrap_noise(
        QuantumNoise(name='amplitude damp',
                     probs=list_assignment(p),
                     level=level))
    return new
Beispiel #5
0
 def __init__(self, generators: typing.Union[QubitHamiltonian, typing.List[QubitHamiltonian]],
              steps: int = 1,
              angles: typing.Union[list, numbers.Real, Variable] = None,
              control: typing.Union[list, int] = None,
              threshold: numbers.Real = 0.0,
              join_components: bool = True,
              randomize_component_order: bool = True,
              randomize: bool = True):
     """
     :param generators: list of generators
     :param angles: coefficients for each generator
     :param steps: Trotter Steps
     :param control: control qubits
     :param threshold: neglect terms in the given Hamiltonians if their coefficients are below this threshold
     :param join_components: The generators are trotterized together. If False the first generator is trotterized, then the second etc
     Note that for steps==1 as well as len(generators)==1 this has no effect
     :param randomize_component_order: randomize the order in the generators order before trotterizing
     :param randomize: randomize the trotter decomposition of each generator
     """
     super().__init__(name="Trotterized", target=self.extract_targets(generators), control=control)
     self.generators = list_assignment(generators)
     self.angles = angles
     self.steps = steps
     self.threshold = threshold
     self.join_components = join_components
     self.randomize_component_order = randomize_component_order
     self.randomize = randomize
     self.finalize()
Beispiel #6
0
def _initialize_power_gate(name: str,
                           target: typing.Union[list, int],
                           generator,
                           control: typing.Union[list, int] = None,
                           power=None,
                           angle=None) -> QCircuit:
    target = list_assignment(target)

    if angle is not None:
        angle = assign_variable(angle)
        if power is not None:
            power = power * angle / np.pi
        else:
            power = angle / np.pi

    if power is None or power in [1, 1.0]:
        gates = [
            impl.QGateImpl(name=name,
                           target=q,
                           control=control,
                           generator=generator(q)) for q in target
        ]
    else:
        gates = [
            impl.PowerGateImpl(name=name,
                               power=power,
                               target=q,
                               control=control,
                               generator=generator(q)) for q in target
        ]

    return QCircuit.wrap_gate(gates)
Beispiel #7
0
 def __init__(self,
              noises: typing.List[typing.Union[dict, QuantumNoise]] = None):
     if noises is None:
         self.noises = []
     else:
         self.noises = [
             QuantumNoise.from_dict(d) for d in list_assignment(noises)
         ]
Beispiel #8
0
def Z(qubit) -> QubitHamiltonian:
    """
    Initialize a single Pauli Z Operator

    Parameters
    ----------
    qubit: int or list of ints
        qubit(s) on which the operator should act

    Returns
    -------
    QubitHamiltonian

    """
    qubit = list_assignment(qubit)
    return pauli(qubit=qubit, type=["Z"] * len(qubit))
Beispiel #9
0
def Phase(target: typing.Union[list, int],
          control: typing.Union[list, int] = None,
          angle: typing.Union[typing.Hashable, numbers.Number] = None,
          *args,
          **kwargs) -> QCircuit:
    """
    Notes
    ----------
    Initialize an abstract phase gate which acts as

    .. math::
        S(\\phi) = \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i\\phi} \\end{pmatrix}

    Parameters
    ----------
    angle
        defines the phase, can be numeric type (static gate) or hashable non-numeric type (parametrized gate)
    target
        int or list of int
    control
        int or list of int

    Returns
    -------
    QCircuit object

    """

    # ensure backward compatibility
    if "phi" in kwargs:
        if angle is None:
            angle = kwargs["phi"]
        else:
            raise Exception(
                "tq.gates.Phase initialization: You gave two angles angle={} and phi={}. Please only use angle"
                .format(angle, kwargs["phi"]))

    if angle is None:
        angle = np.pi

    target = list_assignment(target)
    gates = [
        impl.PhaseGateImpl(phase=angle, target=q, control=control)
        for q in target
    ]

    return QCircuit.wrap_gate(gates)
Beispiel #10
0
def PhaseFlip(p: float, level: int):
    '''
    Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to applying  pauli Z with likelihood p.

    Parameters
    ----------
    p: float:
        the probability with which the noise is applied.
    level: int:
        the # of qubits in operations to apply this noise to.

    Returns
    -------
    NoiseModel
    '''

    new = NoiseModel.wrap_noise(
        QuantumNoise(name='phase flip', probs=list_assignment(p), level=level))
    return new
Beispiel #11
0
def RotationGate(axis: int,
                 angle: typing.Union[typing.Hashable, numbers.Number],
                 target: typing.Union[list, int],
                 control: typing.Union[list, int] = None,
                 assume_real=False):
    """
    Notes
    ----------
    Initialize an abstract rotation gate of the form

    .. math::
        R_{\\text{axis}}(\\text{angle}) = e^{-i\\frac{\\text{angle}}{2} \\sigma_{\\text{axis}}}


    Parameters
    ----------
    axis
        integer 1 for x, 2 for y, 3 for z
    angle
        Hashable type (will be treated as Variable) or Numeric type (static angle)
    target
        integer or list of integers
    control
        integer or list of integers
    assume_real
        enable improved gradient compilation for controlled gates (wavefunction needs to be real)

    Returns
    -------
    QCircuit object with this RotationGate
    """
    target = list_assignment(target)
    gates = [
        impl.RotationGateImpl(axis=axis,
                              angle=angle,
                              target=q,
                              control=control,
                              assume_real=assume_real) for q in target
    ]

    return QCircuit.wrap_gate(gates)
Beispiel #12
0
def DepolarizingError(p: float, level: int):
    '''
    Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to equal
    probabilities of each of the three pauli matrices being applied.

    Parameters
    ----------
    p: float:
        the probability with which the noise is applied.
    level: int:
        the # of qubits in operations to apply this noise to.

    Returns
    -------
    NoiseModel
    '''
    new = NoiseModel.wrap_noise(
        QuantumNoise(name='depolarizing',
                     probs=list_assignment(p),
                     level=level))
    return new
Beispiel #13
0
def PhaseAmplitudeDamp(p1: float, p2: float, level: int):
    '''
    Returns a NoiseModel with one QuantumNoise, having a kraus map corresponding to phase and amplitude damping.

    Parameters
    ----------
    p1: float:
        the probability with which phase is damped
    p2: float:
        the probability with which amplitude is damped
    level: int:
        the # of qubits in operations to apply this noise to.

    Returns
    -------
    NoiseModel
    '''
    new = NoiseModel.wrap_noise(
        QuantumNoise(name='phase-amplitude damp',
                     probs=list_assignment([p1, p2]),
                     level=level))
    return new
Beispiel #14
0
def _initialize_power_gate(name: str,
                           target: typing.Union[list, int],
                           generator,
                           control: typing.Union[list, int] = None,
                           power=None,
                           angle=None,
                           *args,
                           **kwargs) -> QCircuit:
    target = list_assignment(target)

    # allow angle instead of power in initialization for more consistency
    # if angle is given we just convert it
    if angle is not None:
        angle = assign_variable(angle)
        if power is not None:
            power = power * angle / np.pi
        else:
            power = angle / np.pi

    if power is None or power in [1, 1.0]:
        gates = [
            impl.QGateImpl(name=name,
                           target=q,
                           control=control,
                           generator=generator(q)) for q in target
        ]
    else:
        gates = [
            impl.PowerGateImpl(name=name,
                               power=power,
                               target=q,
                               control=control,
                               generator=generator(q),
                               *args,
                               **kwargs) for q in target
        ]

    return QCircuit.wrap_gate(gates)
Beispiel #15
0
def PhaseDamp(p: float, level: int):
    '''
    Returns a NoiseModel of one QuantumNoise, having a kraus map corresponding to phase damping;
    Krauss map is defined following Nielsen and Chuang;
    E_0= [[1,0],
          [0,sqrt(1-p)]]
    E_1= [[0,0],
          [0,sqrt(p)]]

    Parameters
    ----------
    p: float:
        the probability with which the noise is applied.
    level: int:
        the # of qubits in operations to apply this noise to.

    Returns
    -------
    NoiseModel
    '''

    new = NoiseModel.wrap_noise(
        QuantumNoise(name='phase damp', probs=list_assignment(p), level=level))
    return new
Beispiel #16
0
def Sm(qubit) -> QubitHamiltonian:
    """
    Notes
    ----------
    Initialize

    .. math::
        \\frac{1}{2} \\left( \\sigma_x + i \\sigma_y \\right)

    Parameters
    ----------
    qubit: int or list of ints
        qubit(s) on which the operator should act

    Returns
    -------
    QubitHamiltonian

    """
    qubit = list_assignment(qubit)
    result = I()
    for q in qubit:
        result *= 0.5 * (X(qubit=q) - 1.j * Y(qubit=q))
    return result
Beispiel #17
0
def get_torch_function(objective: Objective,
                       compile_args: dict = None,
                       input_vars: list = None):
    """
    build a torch autograd function that calls the Objective; return it, and other useful objects.

    Parameters
    ----------
    objective: Objective:
        the Objective to be transformed into a torch layer.
    compile_args: dict:
        a dictionary of arguments for the tequila compiler; used to render objectives callable
    input_vars: list:
        a list of variables; indicates which variables' values will be considered input to the layer.

    Returns
    -------
    tuple:
        the requisite pytorch autograd function, alongside necessary information for higher level classes.
    """

    if isinstance(objective, tuple) or isinstance(
            objective, list) or isinstance(objective, Objective):
        objective = vectorize(list_assignment(objective))
    comped_objective, compile_args, input_vars, weight_vars, i_grads, w_grads, first, second \
        = preamble(objective, compile_args, input_vars)
    samples = compile_args['samples']

    def tensor_fix(tensor, angles, first, second):
        """
        take a pytorch tensor and a dict of  int,Variable to create a variable,float dictionary therefrom.
        Parameters
        ----------
        tensor: torch.Tensor:
            a tensor.
        angles: torch.Tensor:
        first: dict:
            dict of int,Variable pairs indicating which position in Tensor corresponds to which variable.
        second: dict:
            dict of int,Variable pairs indicating which position in angles corresponds to which variable.
        Returns
        -------
        dict:
            dict of variable, float pairs. Can be used as call arg by underlying tq objectives
        """
        back = {}
        if tensor is not None:
            for i, val in enumerate(tensor):
                back[first[i]] = val.item()
        if angles is not None:
            for i, val in enumerate(angles):
                back[second[i]] = val.item()
        return back

    class _TorchFunction(torch.autograd.Function):
        """
        Internal class for the forward and backward passes of calling a tequila objective.

        Notes
        -----

        Though this class is a private class, some explanations of it's implementation may benefit
        curious users and later developers.

        Question: why is this class defined within a function?
        answer: because, since its defined entirely with staticmethods -- about which torch is quite particular --
        it is impossible to use the attribute 'self' to store information for later use.
        This means that, to wrap arbitrary tequila Objectives into this class, the class needs to see the in some
        kind of well contained scope; the function containing this class, get_torch_function, provides that scope.
        in particular, this scoping is used to associate, with this function, arbitrary tequila objectives,
        their gradients with respect to weights or inputs (that is, variables specified to be one or the other)
        and a small dictionary called pattern, which orders the tequila variables w.r.t the order in which a tensor
        of combined input values and weight values are passed down to the function.

        Though this class doesn't have any proper attributes seperate from those it inherits, we detail
        the non-torch objects called within the function here:

        For Forward
        comped_objective: Objective
            a compiled tequila objective; this function has merely wrapped around it to pass torch Tensors into it.


        For Forward and Backward
        samples: int or None:
            how many samples the user wants when sampling the Objective or it's gradients.


        methods called:
            tensor_fix:
                takes a tensor and an (int: Variable) dict and returns a (Variable: float) dict.

        """
        @staticmethod
        def forward(ctx, inputs, angles):
            """
            forward pass of the function.
            """
            ctx.save_for_backward(inputs, angles)
            call_args = tensor_fix(inputs, angles, first, second)
            result = comped_objective(variables=call_args, samples=samples)
            print(result)
            if not isinstance(result, np.ndarray):
                # this happens if the Objective is a scalar since that's usually more convenient for pure quantum stuff.
                result = np.array(result)
            if hasattr(inputs, 'device'):
                if inputs.device == 'cuda':
                    r = torch.from_numpy(result).to(inputs.device)
                else:
                    r = torch.from_numpy(result)
            else:
                r = torch.from_numpy(result)
            r.requires_grad_(True)
            return r

        @staticmethod
        def backward(ctx, grad_backward):
            inputs, angles = ctx.saved_tensors
            call_args = tensor_fix(inputs, angles, first, second)
            back_d = grad_backward.get_device()
            # build up weight and input gradient matrices... see what needs to be done to them.
            grad_outs = [None, None]
            for i, grads in enumerate([i_grads, w_grads]):
                if grads != {}:
                    g_keys = [j for j in grads.keys()]
                    probe = grads[
                        g_keys[0]]  # first entry will tell us number of output
                    dims = len(g_keys), len(probe)
                    arr = np.empty(dims, dtype=np.float)
                    for j, key in enumerate(g_keys):
                        line = grads[key]
                        for k, ob in enumerate(line):
                            arr[j, k] = ob(variables=call_args,
                                           samples=samples)
                    if back_d >= 0:
                        g_tensor = torch.as_tensor(arr,
                                                   dtype=grad_backward.dtype,
                                                   device=back_d)
                    else:
                        g_tensor = torch.as_tensor(arr,
                                                   dtype=grad_backward.dtype)

                    b = grad_backward.reshape(-1, 1)
                    jvp = torch.matmul(g_tensor, b)
                    jvp_out = jvp.flatten()
                    jvp_out.requires_grad_(True)
                    grad_outs[i] = jvp_out
            return tuple(grad_outs)

    return _TorchFunction, weight_vars, compile_args
Beispiel #18
0
    def __init__(self,
                 objective: Union[Objective, VectorObjective],
                 compile_args: Dict[str, Any] = None,
                 input_vars: Dict[str, Any] = None,
                 **kwargs):
        """
        Tensorflow layer that compiles the Objective (or VectorObjective) with the given compile arguments and/or
        input variables if there are any when initialized. When called, it will forward the input variables into the
        compiled objective (if there are any inputs needed) alongside the parameters and will return the output.
        The gradient values can also be returned.

        Parameters
        ----------
        objective
            Objective or VectorObjective to compile and run.
        compile_args
            dict of all the necessary information to compile the objective
        input_vars
            List of variables that will be inputs
        """
        super(TFLayer, self).__init__(**kwargs)

        # Currently, the optimizers in tf.keras.optimizers don't support float64. For now, all values will be cast to
        # float32 to accommodate this, but in the future, whenever it is supported, this can be changed with
        # set_cast_type()
        self._cast_type = tf.float32

        self.objective = objective
        # Store the objective and vectorize it if necessary
        if isinstance(objective, tuple) or isinstance(objective, list):
            for i, elem in enumerate(objective):
                if not isinstance(elem, Objective):
                    raise TequilaMLException(
                        "Element {} in {} is not a Tequila Objective: {}"
                        "".format(i, type(objective), elem))
            objective = vectorize(list_assignment(objective))

        elif isinstance(objective, Objective) or isinstance(
                objective, VectorObjective):
            objective = vectorize(list_assignment(objective))
        else:
            raise TequilaMLException(
                "Objective must be a Tequila Objective, VectorObjective "
                "or list/tuple of Objectives. Received a {}".format(
                    type(objective)))
        self.objective = objective

        # Compile the objective, prepare the gradients and whatever else that may be necessary
        self.comped_objective, self.compile_args, self.input_vars, self.weight_vars, self.i_grads, self.w_grads, \
        self.first, self.second = preamble(objective, compile_args, input_vars)

        # VARIABLES
        # These variables will hold 1D tensors which each will store the values in the order found by self.input_vars
        # for the variable in self.input_variable, and in the order found by self.weight_vars for the variable in
        # self.weight_variable

        # If there are inputs, prepare an input tensor as a trainable variable
        # NOTE: if the user specifies values for the inputs, they will be assigned in the set_input_values()
        if self.input_vars:
            initializer = tf.constant_initializer(
                np.random.uniform(low=0.,
                                  high=2 * np.pi,
                                  size=len(self.input_vars)))
            self.input_variable = self.add_weight(name="input_tensor_variable",
                                                  shape=(len(self.input_vars)),
                                                  dtype=self._cast_type,
                                                  initializer=initializer,
                                                  trainable=True)
        else:
            self.input_variable = None

        # If there are weight variables, prepare a params tensor as a trainable variable
        if self.weight_vars:
            # Initialize the variable tensor that will hold the weights/parameters/angles
            initializer = tf.constant_initializer(
                np.random.uniform(low=0.,
                                  high=2 * np.pi,
                                  size=len(self.weight_vars)))
            self.weight_variable = self.add_weight(
                name="params_tensor_variable",
                shape=(len(self.weight_vars)),
                dtype=self._cast_type,
                initializer=initializer,
                trainable=True)

            # If the user specified initial values for the parameters, use them
            if compile_args is not None and compile_args[
                    "initial_values"] is not None:
                # Assign them in the order given by self.second
                toVariable = [self.second[i] for i in self.second
                              ]  # Variable names in the correct order
                self.weight_variable.assign([
                    compile_args["initial_values"][val] for val in toVariable
                ])
        else:
            self.weight_variable = None

        # Store extra useful information
        self._input_len = 0
        if input_vars:
            self._input_len = len(self.input_vars)
        self._params_len = len(list(self.weight_vars))

        self.samples = None
        if self.compile_args is not None:
            self.samples = self.compile_args["samples"]