def test_linear_rotations_mutability(self): """Test the mutability of the linear rotations circuit.""" linear_rotation = LinearPauliRotations() with self.subTest(msg='missing number of state qubits'): with self.assertRaises(AttributeError): # no state qubits set print(linear_rotation.draw()) with self.subTest( msg='default setup, just setting number of state qubits'): linear_rotation.num_state_qubits = 2 self.assertFunctionIsCorrect(linear_rotation, lambda x: x / 2) with self.subTest(msg='setting non-default values'): linear_rotation.slope = -2.3 * 2 linear_rotation.offset = 1 * 2 self.assertFunctionIsCorrect(linear_rotation, lambda x: 1 - 2.3 * x) with self.subTest(msg='changing all values'): linear_rotation.num_state_qubits = 4 linear_rotation.slope = 0.2 * 2 linear_rotation.offset = 0.1 * 2 self.assertFunctionIsCorrect(linear_rotation, lambda x: 0.1 + 0.2 * x)
def test_linear_function(self, num_state_qubits, slope, offset): """Test the linear rotation arithmetic circuit.""" def linear(x): return offset + slope * x linear_rotation = LinearPauliRotations(num_state_qubits, slope * 2, offset * 2) self.assertFunctionIsCorrect(linear_rotation, linear)
def __init__( self, n_normal: int, normal_max_value: float, p_zeros: Union[List[float], np.ndarray], rhos: Union[List[float], np.ndarray], ) -> None: """ Args: n_normal: Number of qubits to represent the latent normal random variable Z normal_max_value: Min/max value to truncate the latent normal random variable Z p_zeros: Standard default probabilities for each asset rhos: Sensitivities of default probability of assets with respect to latent variable Z """ self.n_normal = n_normal self.normal_max_value = normal_max_value self.p_zeros = p_zeros self.rhos = rhos num_qubits = n_normal + len(p_zeros) # get normal (inverse) CDF and pdf (these names are from the paper, therefore ignore # pylint) def F(x): # pylint: disable=invalid-name return norm.cdf(x) def F_inv(x): # pylint: disable=invalid-name return norm.ppf(x) def f(x): # pylint: disable=invalid-name return norm.pdf(x) # create linear rotations for conditional defaults slopes = [] offsets = [] for rho, p_zero in zip(rhos, p_zeros): psi = F_inv(p_zero) / np.sqrt(1 - rho) # compute slope / offset slope = -np.sqrt(rho) / np.sqrt(1 - rho) 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) offsets += [offset] slopes += [slope] # create normal distribution normal_distribution = NormalDistribution( n_normal, 0, 1, bounds=(-normal_max_value, normal_max_value), ) # build circuit inner = QuantumCircuit(num_qubits, name="P(X)") inner.append(normal_distribution.to_gate(), list(range(n_normal))) for k, (slope, offset) in enumerate(zip(slopes, offsets)): lry = LinearPauliRotations(n_normal, slope, offset) qubits = list(range(n_normal)) + [n_normal + k] inner.append(lry.to_gate(), qubits) super().__init__(num_qubits, name="P(X)") self.append(inner.to_gate(), inner.qubits)
def build(self, qc, q, q_ancillas=None, params=None): self._normal.build(qc, q, q_ancillas) for k in range(self.K): lry = LinearPauliRotations(self.n_normal, self._slopes[k], self._offsets[k]) qc.append(lry.to_instruction(), self.i_normal + [self.i_ps[k]])