def build(self, qc, q, q_ancillas=None, params=None):

        # get parameters
        i_state = self.i_state
        i_target = self.i_target

        # apply comparators and controlled linear rotations
        for i, bp in enumerate(self.breakpoints):

            if i == 0 and self.contains_zero_breakpoint:

                # apply rotation
                lin_r = LinR(self.mapped_slopes[i], self.mapped_offsets[i],
                             self.num_state_qubits, basis=self.basis,
                             i_state=i_state, i_target=i_target)
                lin_r.build(qc, q)

            elif self.contains_zero_breakpoint:

                # apply comparator
                comp = Comparator(self.num_state_qubits, bp)
                q_ = [q[i] for i in range(self.num_state_qubits)]  # map register to list
                q_ = q_ + [q_ancillas[i - 1]]  # add ancilla as compare qubit
                # take remaining ancillas as ancilla register (list)
                q_ancillas_ = [q_ancillas[j] for j in range(i, len(q_ancillas))]
                comp.build(qc, q_, q_ancillas_)

                # apply controlled rotation
                lin_r = LinR(self.mapped_slopes[i], self.mapped_offsets[i],
                             self.num_state_qubits, basis=self.basis,
                             i_state=i_state, i_target=i_target)
                lin_r.build_controlled(qc, q, q_ancillas[i - 1], use_basis_gates=False)

                # uncompute comparator
                comp.build_inverse(qc, q_, q_ancillas_)

            else:

                # apply comparator
                comp = Comparator(self.num_state_qubits, bp)
                q_ = [q[i] for i in range(self.num_state_qubits)]  # map register to list
                q_ = q_ + [q_ancillas[i]]  # add ancilla as compare qubit
                # take remaining ancillas as ancilla register (list)
                q_ancillas_ = [q_ancillas[j] for j in range(i + 1, len(q_ancillas))]
                comp.build(qc, q_, q_ancillas_)

                # apply controlled rotation
                lin_r = LinR(self.mapped_slopes[i],
                             self.mapped_offsets[i], self.num_state_qubits, basis=self.basis,
                             i_state=i_state, i_target=i_target)
                lin_r.build_controlled(qc, q, q_ancillas[i], use_basis_gates=False)

                # uncompute comparator
                comp.build_inverse(qc, q_, q_ancillas_)
Example #2
0
    def __init__(self, n_normal, normal_max_value, p_zeros, rhos, i_normal=None, i_ps=None):
        """
        Constructor.

        The Gaussian Conditional Independence Model for Credit Risk
        Reference: https://arxiv.org/abs/1412.1183

        Args:
            n_normal (int): number of qubits to represent the latent normal random variable Z
            normal_max_value (float): min/max value to truncate the latent normal random variable Z
            p_zeros (list or array): standard default probabilities for each asset
            rhos (list or array): sensitivities of default probability of assets with respect to latent variable Z
            i_normal (list or array): indices of qubits to represent normal variable
            i_ps (list or array): indices of qubits to represent asset defaults
        """
        self.n_normal = n_normal
        self.normal_max_value = normal_max_value
        self.p_zeros = p_zeros
        self.rhos = rhos
        self.K = len(p_zeros)
        num_qubits = [n_normal] + [1]*self.K

        # set and store indices
        if i_normal is not None:
            self.i_normal = i_normal
        else:
            self.i_normal = range(n_normal)

        if i_ps is not None:
            self.i_ps = i_ps
        else:
            self.i_ps = range(n_normal, n_normal + self.K)

        # get normal (inverse) CDF and pdf
        def F(x): return norm.cdf(x)
        def F_inv(x): return norm.ppf(x)
        def f(x): return norm.pdf(x)

        # set low/high values
        low = [-normal_max_value] + [0]*self.K
        high = [normal_max_value] + [1]*self.K

        # call super constructor
        super().__init__(num_qubits, low=low, high=high)

        # create normal distribution
        self._normal = NormalDistribution(n_normal, 0, 1, -normal_max_value, normal_max_value)

        # create linear rotations for conditional defaults
        self._slopes = np.zeros(self.K)
        self._offsets = np.zeros(self.K)
        self._rotations = []
        for k in range(self.K):

            psi = F_inv(p_zeros[k]) / np.sqrt(1 - rhos[k])

            # compute slope / offset
            slope = -np.sqrt(rhos[k]) / np.sqrt(1 - rhos[k])
            slope *= f(psi) / np.sqrt(1 - F(psi)) / np.sqrt(F(psi))
            offset = 2*np.arcsin(np.sqrt(F(psi)))

            # adjust for integer to normal range mapping
            offset += slope * (-normal_max_value)
            slope *= 2*normal_max_value / (2**n_normal - 1)

            self._offsets[k] = offset
            self._slopes[k] = slope

            lry = LinearRotation(slope, offset, n_normal, i_state=self.i_normal, i_target=self.i_ps[k])
            self._rotations += [lry]