def test_fast_random_choice_output(): """Test that the fast random choice outputs are accurate.""" n_sample = 10000 # few samples weights = np.array([0.1, 0.9]) ret = np.array([fast_random_choice(weights) for _ in range(n_sample)]) assert 0.08 < sum(ret == 0) / n_sample < 0.12 assert 0.88 < sum(ret == 1) / n_sample < 0.92 # many samples weights = np.array([*[0.02] * 20, *[0.3] * 2]) ret = np.array([fast_random_choice(weights) for _ in range(n_sample)]) assert 0.005 < sum(ret == 0) / n_sample < 0.04 assert 0.28 < sum(ret == 20) / n_sample < 0.32
def generate_valid_proposal( t: int, m: np.ndarray, p: np.ndarray, model_prior: RV, parameter_priors: List[Distribution], model_perturbation_kernel: ModelPerturbationKernel, transitions: List[Transition]): """Sample a parameter for a model. Parameters ---------- t: Population index to generate for. m: Indices of alive models. p: Probabilities of alive models. model_prior: The model prior. parameter_priors: The parameter priors. model_perturbation_kernel: The model perturbation kernel. transitions: The transitions, one per model. Returns ------- (m_ss, theta_ss): Model, parameter. """ # first generation if t == 0: # sample from prior m_ss = int(model_prior.rvs()) theta_ss = parameter_priors[m_ss].rvs() return m_ss, theta_ss # later generation # counter n_sample, n_sample_soft_limit = 0, 1000 # sample until the prior density is positive while True: if len(m) > 1: index = fast_random_choice(p) m_s = m[index] m_ss = model_perturbation_kernel.rvs(m_s) # theta_s is None if the population m_ss has died out. # This can happen since the model_perturbation_kernel # can return a model nr which has died out. if m_ss not in m: continue else: # only one model m_ss = m[0] theta_ss = Parameter(**transitions[m_ss].rvs().to_dict()) # check if positive under prior if (model_prior.pmf(m_ss) * parameter_priors[m_ss].pdf(theta_ss) > 0): return m_ss, theta_ss # unhealthy sampling detection n_sample += 1 if n_sample == n_sample_soft_limit: logger.warning( "Unusually many (model, parameter) samples have prior " "density zero. The transition might be inappropriate.")
def test_fast_random_choice(): """Check that `fast_random_choice` delivers the promised benefit.""" nrep = 100000 ws = np.random.uniform(size=(nrep, 10)) ws /= ws.sum(axis=1, keepdims=True) np.random.seed(0) start = time() for w in ws: np.random.choice(len(w), p=w) time_numpy = time() - start print(f"Time numpy: {time_numpy}") np.random.seed(0) start = time() for w in ws: fast_random_choice(w) time_fast = time() - start print(f"Time fast_random_choice: {time_fast}") assert time_fast < time_numpy
def test_fast_random_choice_basic(): """Test the fast random choice function for various inputs.""" # run with many values (method 1) weights = np.random.uniform(0, 1, size=10000) weights /= weights.sum() fast_random_choice(weights) # run with few values (method 2) weights = np.random.uniform(0, 1, size=5) weights /= weights.sum() fast_random_choice(weights) # run with a single value fast_random_choice(np.array([1]))
def test_fast_random_choice_errors(): """Test the fast random choice function for invalid inputs.""" with pytest.raises(ValueError): fast_random_choice(np.array([])) # non-normalized weights for many values weights = np.random.uniform(0, 1, size=1000) weights /= weights.sum() / 2 with pytest.raises(ValueError): fast_random_choice(weights) # non-normalized weights for few values with pytest.raises(ValueError): # does not always raise, but will sometimes for _ in range(100): weights = np.random.uniform(0, 1, size=5) weights /= 5 fast_random_choice(weights)