def test_n_mean(self, adj, n_mean, dim, threshold, embedding): """Test that n_mean returns the expected number of photons or clicks when using an identity W, so that we expect the mean number of photons to be equal to the input value of n_mean""" params = np.zeros(dim) gbs = train.VGBS(adj, n_mean, embedding, threshold) assert np.allclose(gbs.n_mean(params), n_mean)
def test_intermediate_cost(self, dim, n_mean, simple_embedding): """Test that the cost function evaluates as expected on non-initial parameters. This is done by comparing the cost function calculated using train.Stochastic with a manual calculation. The manual calculation involves sampling from VGBS with the non-initial params and averaging the cost function over the result.""" n_samples = 10000 objectives = np.linspace(0.5, 1.5, dim) h = self.h_setup(objectives) A = np.eye(dim) vgbs = train.VGBS(A, n_mean, simple_embedding, threshold=False) n_mean_by_mode = [n_mean / dim] * dim samples = self.identity_sampler(n_mean_by_mode, n_samples=n_samples) vgbs.add_A_init_samples(samples) params = np.linspace(0, 1 / dim, dim) cost_fn = train.Stochastic(h, vgbs) cost = cost_fn(params, n_samples=n_samples) # We need to generate new samples and then calculate the cost with respect to them new_n_mean_by_mode = vgbs.mean_photons_by_mode(params) new_samples = self.identity_sampler(new_n_mean_by_mode, n_samples=n_samples) expected_cost = np.mean(np.sum((new_samples - objectives) ** 2, axis=1)) assert np.allclose(cost, expected_cost, rtol=0.1)
def test_add_A_init_samples_bad_shape(self, adj, n_mean, dim, embedding): """Test that add_A_init_samples raises a ValueError when input samples of incorrect shape, i.e. of dim + 1 modes""" gbs = train.VGBS(adj, n_mean, embedding, True) s = np.ones((2, dim + 1)) with pytest.raises(ValueError, match="Must input samples of shape"): gbs.add_A_init_samples(s)
def test_W(self, adj, n_mean, params, embedding): """Test that the W method correctly gives the diagonal matrix of square root embedded parameters""" gbs = train.VGBS(adj, n_mean, embedding, True) W = gbs.W(params) assert np.allclose(np.diag(W)**2, embedding(params)) # check that diagonal squared
def test_add_A_init_samples_already_there(self, adj, n_mean, dim, embedding): """Test that add_A_init_samples correctly adds more samples when some are already there""" gbs = train.VGBS(adj, n_mean, embedding, True) gbs.A_init_samples = np.ones((2, dim)) gbs.add_A_init_samples(np.zeros((2, dim))) assert gbs.A_init_samples.shape == (4, dim) assert np.allclose(gbs.A_init_samples[:2], np.ones((2, dim))) assert np.allclose(gbs.A_init_samples[2:3], np.zeros((2, dim)))
def test_photons_clicks_comparison(self, n_mean, dim, adj, embedding): """Test that compares mean_photons_by_mode and mean_clicks_by_mode. We expect elements of n_mean_vec_photon to always be larger than n_mean_vec_click and also for elements of n_mean_vec_click to not exceed one.""" params = np.zeros(dim) gbs = train.VGBS(adj, n_mean, embedding, False) n_mean_vec_photon = gbs.mean_photons_by_mode(params) n_mean_vec_click = gbs.mean_clicks_by_mode(params) assert (n_mean_vec_click <= 1).all() assert (n_mean_vec_photon >= n_mean_vec_click).all()
def test_VGBS_integration(adj, params, n_mean, threshold, dim, embedding): """Integration test for the class ``train.VGBS``. We access the output adjacency matrix, mean photon number, and samples to check that they have the expected shape.""" n_samples = 3 gbs = train.VGBS(adj, n_mean, embedding, threshold) A_prime = gbs.A(params) n_mean_prime = gbs.n_mean(params) samples = gbs.generate_samples(A_prime, n_samples) assert A_prime.shape == (dim, dim) assert isinstance(n_mean_prime, float) assert samples.shape == (n_samples, dim)
def test_prob_sample_vacuum(self, n_mean, dim, threshold, embedding): """Test if prob_sample returns the correct probability of the vacuum, which can be calculated directly as the prefactor in the GBS distribution.""" adj = np.ones((dim, dim)) params = np.zeros(dim) gbs = train.VGBS(adj, n_mean, embedding, threshold) sample = np.zeros(dim) p = gbs.prob_sample(params, sample) O = train.param._Omat(gbs.A(params)) scale = np.sqrt(np.linalg.det(np.identity(2 * dim) - O)) assert np.allclose(scale, p)
def test_get_A_init_samples_none_there(self, adj, n_mean, monkeypatch, dim, embedding): """Test if get_A_init_samples generates the required samples when none are present in A_init_samples. To speed up sampling, we monkeypatch torontonian_sample_state to always return a numpy array of ones.""" gbs = train.VGBS(adj, n_mean, embedding, True) with monkeypatch.context() as m: m.setattr( thewalrus.samples, "torontonian_sample_state", lambda *args, **kwargs: np.ones((args[1], dim)), ) samples = gbs.get_A_init_samples(1000) assert np.allclose(samples, np.ones((1000, dim)))
def test_mean_clicks_by_mode(self, n_mean, dim, embedding): """Test that mean_clicks_by_mode is correct when given a simple fully connected adjacency matrix and an identity W. We expect each mode to have the same mean click number and for that to add up to n_mean.""" adj = np.ones((dim, dim)) params = np.zeros(dim) gbs = train.VGBS(adj, n_mean, embedding, True) n_mean_vec = gbs.mean_clicks_by_mode(params) assert np.allclose(np.sum(n_mean_vec), n_mean) # check that the vector sums to n_mean assert np.allclose(n_mean_vec - n_mean_vec[0], np.zeros(dim)) # check that the vector is # constant assert np.allclose(n_mean_vec[0], n_mean / dim) # check that elements have correct values
def test_prob_sample_different(self, n_mean, dim, embedding): """Test if prob_sample returns different probabilities for the same sample when using threshold and pnr modes.""" adj = np.ones((dim, dim)) params = np.zeros(dim) gbs = train.VGBS(adj, n_mean, embedding, True) sample = np.array([1, 1] + [0] * (dim - 2)) p1 = gbs.prob_sample(params, sample) gbs.threshold = False p2 = gbs.prob_sample(params, sample) assert not np.allclose(p1, p2)
def test_get_A_init_samples_already_there(self, adj, n_mean, monkeypatch, dim, embedding): """Test if get_A_init_samples generates the required samples when some are already present in A_init_samples. We pre-load 200 samples of all zeros into VGBS and then request 1000 samples back. The sampling is monkeypatched to always return samples of ones, so that we hence expect 200 zero samples then 800 ones samples.""" gbs = train.VGBS(adj, n_mean, embedding, True, np.zeros((200, dim))) with monkeypatch.context() as m: m.setattr( thewalrus.samples, "torontonian_sample_state", lambda *args, **kwargs: np.ones((args[1], dim)), ) samples = gbs.get_A_init_samples(1000) assert np.allclose(samples[:200], np.zeros((200, dim))) assert np.allclose(samples[200:], np.ones((800, dim)))
def test_gradient(self, dim, n_mean, simple_embedding): """Test that the gradient evaluates as expected when compared to a value calculated by hand. Consider the problem with respect to a single mode. We want to calculate E((s - x) ** 2) with s the number of photons in the mode, x the element of the fixed vector, and with the expectation value calculated with respect to the (twice) negative binomial distribution. We know that E(s) = 2 * r * (1 - q) / q and Var(s) = 4 * (1 - q) * r / q ** 2 in terms of the r and q parameters of the negative binomial distribution. Using q = 1 / (1 + n_mean) with n_mean the mean number of photons in that mode and r = 0.5, we can calculate E((s - x) ** 2) = 3 * n_mean ** 2 + 2 * (1 - x) * n_mean + x ** 2, This can be differentiated to give the derivative: d/dx E((s - x) ** 2) = 6 * n_mean + 2 * (1 - x). """ n_samples = 20000 # We need a lot of shots due to the high variance in the distribution objectives = np.linspace(0.5, 1.5, dim) h = self.h_setup(objectives) A = np.eye(dim) vgbs = train.VGBS(A, n_mean, simple_embedding, threshold=False) n_mean_by_mode = [n_mean / dim] * dim samples = self.identity_sampler(n_mean_by_mode, n_samples=n_samples) vgbs.add_A_init_samples(samples) params = np.linspace(0, 1 / dim, dim) cost_fn = train.Stochastic(h, vgbs) # We want to calculate dcost_by_dn as this is available analytically, where n is the mean # photon number. The following calculates this using the chain rule: dcost_by_dtheta = cost_fn.grad(params, n_samples=n_samples) dtheta_by_dw = 1 / np.diag(simple_embedding.jacobian(params)) A_diag = np.diag(vgbs.A(params)) A_init_diag = np.diag(vgbs.A_init) # Differentiate Eq. (8) of https://arxiv.org/abs/2004.04770 and invert for the next line dw_by_dn = (1 - A_diag**2) ** 2 / (2 * A_diag * A_init_diag) # Now use the chain rule dcost_by_dn = dcost_by_dtheta * dtheta_by_dw * dw_by_dn n_mean_by_mode = vgbs.mean_photons_by_mode(params) dcost_by_dn_expected = 6 * n_mean_by_mode + 2 * (1 - objectives) assert np.allclose(dcost_by_dn, dcost_by_dn_expected, 0.5)
def test_initial_cost(self, dim, n_mean, simple_embedding): """Test that the cost function evaluates as expected on initial parameters of all zeros""" n_samples = 1000 objectives = np.linspace(0.5, 1.5, dim) h = self.h_setup(objectives) A = np.eye(dim) vgbs = train.VGBS(A, n_mean, simple_embedding, threshold=False) n_mean_by_mode = [n_mean / dim] * dim samples = self.identity_sampler(n_mean_by_mode, n_samples=n_samples) vgbs.add_A_init_samples(samples) params = np.zeros(dim) cost_fn = train.Stochastic(h, vgbs) cost = cost_fn(params, n_samples=n_samples) # We can directly calculate the cost with respect to the samples expected_cost = np.mean(np.sum((samples - objectives) ** 2, axis=1)) assert np.allclose(cost, expected_cost)
def test_generate_samples(self, adj, n_mean, monkeypatch, embedding): """Test that generate_samples correctly dispatches between torontonian and hafnian sampling based upon whether threshold=True or threshold=False. This is done by monkeypatching torontonian_sample_state and hafnian_sample_state so that they simply return 0 and 1, respectively, instead of calculating samples. We then check that the returned samples are 0 in threshold mode and 1 when not in threshold mode.""" gbs = train.VGBS(adj, n_mean, embedding, True) with monkeypatch.context() as m: m.setattr(thewalrus.samples, "torontonian_sample_state", lambda *args, **kwargs: 0) s_threshold = gbs.generate_samples(gbs.A_init, 10) gbs.threshold = False with monkeypatch.context() as m: m.setattr(thewalrus.samples, "hafnian_sample_state", lambda *args, **kwargs: 1) s_pnr = gbs.generate_samples(gbs.A_init, 10) assert s_threshold == 0 assert s_pnr == 1
def test_add_A_init_samples_none_there(self, adj, n_mean, dim, embedding): """Test that add_A_init_samples correctly adds samples""" gbs = train.VGBS(adj, n_mean, embedding, True) s = np.ones((2, dim)) gbs.add_A_init_samples(s) assert np.allclose(gbs.A_init_samples, s)
def vgbs(A, n_mean, embedding, threshold): """VGBS for the fully connected adjacency matrix using the exponential embedding""" return train.VGBS(A, n_mean, embedding, threshold)
def test_get_A_init_samples_lots_there(self, adj, n_mean, dim, embedding): """Test if get_A_init_samples returns a portion of the pre-generated samples if ``n_samples`` is less than the number of samples stored.""" gbs = train.VGBS(adj, n_mean, embedding, True, np.zeros((1000, dim))) samples = gbs.get_A_init_samples(200) assert samples.shape == (200, dim)