예제 #1
0
def main():
    m = SimpleMacroModel()
    prior = UniformDistribution([[0, 1], [0, 1]])
    u = SMCUpdater(m, 1000, prior)
    modelparams = prior.sample()
    expparams = np.array([(12.0,)], dtype=m.expparams_dtype)
    datum = m.simulate_experiment(modelparams, expparams)
    print datum
    u.update(datum, expparams)
    print u.est_mean()
    print m.call_count
예제 #2
0
def main():
    m = SimpleMacroModel()
    prior = UniformDistribution([[0, 1], [0, 1]])
    u = SMCUpdater(m, 1000, prior)
    modelparams = prior.sample()
    expparams = np.array([(12.0, )], dtype=m.expparams_dtype)
    datum = m.simulate_experiment(modelparams, expparams)
    print datum
    u.update(datum, expparams)
    print u.est_mean()
    print m.call_count
예제 #3
0
def perf_test(
        model, n_particles, prior,
        n_exp, heuristic_class,
        true_model=None, true_prior=None
    ):
    """
    Runs a trial of using SMC to estimate the parameters of a model, given a
    number of particles, a prior distribution and an experiment design
    heuristic.

    :param qinfer.Model model: Model whose parameters are to
        be estimated.
    :param int n_particles: Number of SMC particles to use.
    :param qinfer.Distribution prior: Prior to use in selecting
        SMC particles.
    :param int n_exp: Number of experimental data points to draw from the
        model.
    :param qinfer.Heuristic heuristic_class: Constructor function
        for the experiment design heuristic to be used.
    :param qinfer.Model true_model: Model to be used in
        generating experimental data. If ``None``, assumed to be ``model``.
    :param qinfer.Distribution true_prior: Prior to be used in
        selecting the true model parameters. If ``None``, assumed to be
        ``prior``.
    :rtype np.ndarray: See :ref:`perf_testing_struct` for more details on 
        the type returned by this function.
    :return: A record array of performance metrics, indexed by the number
        of experiments performed.
    """

    if true_model is None:
        true_model = model

    if true_prior is None:
        true_prior = prior

    true_mps = true_prior.sample()

    performance = np.zeros((n_exp,), dtype=PERFORMANCE_DTYPE)

    updater = SMCUpdater(model, n_particles, prior)
    heuristic = heuristic_class(updater)

    for idx_exp in xrange(n_exp):
        expparams = heuristic()
        datum = true_model.simulate_experiment(true_mps, expparams)

        with timing() as t:
            updater.update(datum, expparams)

        delta = updater.est_mean() - true_mps

        performance[idx_exp]['elapsed_time'] = t.delta_t
        performance[idx_exp]['loss'] = np.dot(model.Q, delta**2)
        performance[idx_exp]['resample_count'] = updater.resample_count

    return performance
예제 #4
0
def do_update(model, n_particles, prior, outcomes, expparams, return_all, resampler=None):
    updater = SMCUpdater(model, n_particles, prior,
        resampler=resampler
    )
    updater.batch_update(outcomes, expparams, resample_interval=1)

    mean = updater.est_mean()
    cov = updater.est_covariance_mtx()

    if model.n_modelparams == 1:
        mean = mean[0]
        cov = cov[0, 0]

    if not return_all:
        return mean, cov
    else:
        return mean, cov, {
            'updater': updater
        }
예제 #5
0
def do_update(model, n_particles, prior, outcomes, expparams, return_all, resampler=None):
    updater = SMCUpdater(model, n_particles, prior,
        resampler=resampler
    )
    updater.batch_update(outcomes, expparams, resample_interval=1)

    mean = updater.est_mean()
    cov = updater.est_covariance_mtx()

    if model.n_modelparams == 1:
        mean = mean[0]
        cov = cov[0, 0]

    if not return_all:
        return mean, cov
    else:
        return mean, cov, {
            'updater': updater
        }
class TestSMCUpdater(DerandomizedTestCase):
    # True model parameter for test
    MODELPARAMS = np.array([
        1,
    ])
    TEST_EXPPARAMS = np.linspace(1., 10., 100, dtype=np.float)
    PRIOR = UniformDistribution([[0, 2]])
    N_PARTICLES = 10000

    TEST_TARGET_COV = np.array([[0.01]])

    def setUp(self):

        super(TestSMCUpdater, self).setUp()
        self.precession_model = SimplePrecessionModel()
        self.num_precession_model = NumericalSimplePrecessionModel()
        self.expparams = TestSMCUpdater.TEST_EXPPARAMS.reshape(-1, 1)
        self.outcomes = self.precession_model.simulate_experiment(
            TestSMCUpdater.MODELPARAMS,
            TestSMCUpdater.TEST_EXPPARAMS,
            repeat=1).reshape(-1, 1)

        self.updater = SMCUpdater(self.precession_model,
                                  TestSMCUpdater.N_PARTICLES,
                                  TestSMCUpdater.PRIOR)
        self.updater_bayes = SMCUpdaterBCRB(self.precession_model,
                                            TestSMCUpdater.N_PARTICLES,
                                            TestSMCUpdater.PRIOR,
                                            adaptive=True)
        self.num_updater = SMCUpdater(self.num_precession_model,
                                      TestSMCUpdater.N_PARTICLES,
                                      TestSMCUpdater.PRIOR)
        self.num_updater_bayes = SMCUpdaterBCRB(self.num_precession_model,
                                                TestSMCUpdater.N_PARTICLES,
                                                TestSMCUpdater.PRIOR,
                                                adaptive=True)

    def test_smc_fitting(self):
        """
		Checks that the fitters converge on true value on simple precession_model. Is a stochastic
		test but I ran 100 times and there were no fails, with these parameters.
		"""

        self.updater.batch_update(self.outcomes, self.expparams)
        self.updater_bayes.batch_update(self.outcomes, self.expparams)
        self.num_updater.batch_update(self.outcomes, self.expparams)
        self.num_updater_bayes.batch_update(self.outcomes, self.expparams)

        #Assert that models have learned true model parameters from data
        #test means
        assert_almost_equal(self.updater.est_mean(),
                            TestSMCUpdater.MODELPARAMS, 2)
        assert_almost_equal(self.updater_bayes.est_mean(),
                            TestSMCUpdater.MODELPARAMS, 2)
        assert_almost_equal(self.num_updater.est_mean(),
                            TestSMCUpdater.MODELPARAMS, 2)
        assert_almost_equal(self.num_updater_bayes.est_mean(),
                            TestSMCUpdater.MODELPARAMS, 2)

        #Assert that covariances have been reduced below thresholds
        #test covs
        assert_array_less(self.updater.est_covariance_mtx(),
                          TestSMCUpdater.TEST_TARGET_COV)
        assert_array_less(self.updater_bayes.est_covariance_mtx(),
                          TestSMCUpdater.TEST_TARGET_COV)
        assert_array_less(self.num_updater.est_covariance_mtx(),
                          TestSMCUpdater.TEST_TARGET_COV)
        assert_array_less(self.num_updater_bayes.est_covariance_mtx(),
                          TestSMCUpdater.TEST_TARGET_COV)

    def test_bim(self):
        """
		Checks that the fitters converge on true value on simple precession_model. Is a stochastic
		test but I ran 100 times and there were no fails, with these parameters.
		"""
        bim_currents = []
        num_bim_currents = []
        bim_adaptives = []
        num_bim_adaptives = []

        #track bims throughout experiments
        for i in range(self.outcomes.shape[0]):
            self.updater_bayes.update(self.outcomes[i], self.expparams[i])
            self.num_updater_bayes.update(self.outcomes[i], self.expparams[i])

            bim_currents.append(self.updater_bayes.current_bim)
            num_bim_currents.append(self.num_updater_bayes.current_bim)
            bim_adaptives.append(self.updater_bayes.adaptive_bim)
            num_bim_adaptives.append(self.num_updater_bayes.adaptive_bim)

        bim_currents = np.array(bim_currents)
        num_bim_currents = np.array(num_bim_currents)
        bim_adaptives = np.array(bim_adaptives)
        num_bim_adaptives = np.array(num_bim_adaptives)

        #compare numerical and analytical bims
        assert_almost_equal(bim_currents, num_bim_currents, 2)
        assert_almost_equal(bim_adaptives, num_bim_adaptives, 2)

        #verify that array copying of properties is working
        assert not np.all(bim_currents == bim_currents[0, ...])
        assert not np.all(num_bim_currents == num_bim_currents[0, ...])
        assert not np.all(bim_adaptives == bim_adaptives[0, ...])
        assert not np.all(num_bim_adaptives == num_bim_adaptives[0, ...])

        #verify that BCRB is approximately reached
        assert_almost_equal(self.updater_bayes.est_covariance_mtx(),
                            np.linalg.inv(self.updater_bayes.current_bim), 2)
        assert_almost_equal(self.updater_bayes.est_covariance_mtx(),
                            np.linalg.inv(self.updater_bayes.adaptive_bim), 2)
        assert_almost_equal(self.num_updater_bayes.est_covariance_mtx(),
                            np.linalg.inv(self.updater_bayes.current_bim), 2)
        assert_almost_equal(self.num_updater_bayes.est_covariance_mtx(),
                            np.linalg.inv(self.updater_bayes.adaptive_bim), 2)
예제 #7
0
        mps_buf.release()
        eps_buf.release()
        dest_buf.release()

        # Now we concatenate over outcomes.
        return FiniteOutcomeModel.pr0_to_likelihood_array(outcomes, pr0)


## SCRIPT ######################################################################

if __name__ == "__main__":
    # NOTE: This is now redundant with the perf_testing module.

    simple_model = SimplePrecessionModel()

    for model in [AcceleratedPrecessionModel(), SimplePrecessionModel()]:

        true = np.random.random(1)
        updater = SMCUpdater(model, 100000, UniformDistribution([0, 1]))

        tic = time.time()

        for idx_exp in range(200):
            if not (idx_exp % 20):
                print(idx_exp)
            expparams = np.array([(9 / 8)**idx_exp])
            updater.update(simple_model.simulate_experiment(true, expparams),
                           expparams)

        print(model, updater.est_mean(), true, time.time() - tic)
예제 #8
0
    
    
#NMR EXPERIMENT*************************************************    
#USE this instead of simualate when doing experiments in NMR 
#    outcome=np.array([[[float(raw_input('Enter obtained Mz: '))]]])
#    dummy=float(raw_input('waiting for Mz'))
#    Mz_value=LF.lorentzfit(str(idx_trials+2)+'_spectrum.txt')
#    outcome=np.array([[[Mz_value/abs(Mo_norm)]]])

    #Run SMC and update the posterior distribution
    updater.update(outcome,expparams,check_for_resample=True)
 
 
 
#STORE DATA******************************************
    data[idx_trials]['est_mean'] = updater.est_mean()
    data[idx_trials]['sim_outcome'] = outcome
    data[idx_trials]['expparams'] = expparams
    data[idx_trials]['covariance'] = updater.est_covariance_mtx()
    save_exp.writelines(str(expparams)+'\n')
    save_mean.write(str(updater.est_mean())+'\n')
    save_out.write(str(outcome)+'\n')
    save_cov.write(str(updater.est_covariance_mtx())+'\n')
    

# PLOT *******************************************  
#plotting particles and weights 
    particles = updater.particle_locations
    weights = updater.particle_weights
    
    a=np.var(np.multiply(particles[:,0],weights))
예제 #9
0
    outcome = sim_outcome
    
    
# NMR EXPERIMENT*************************************************
# USE this instead when doing experiments in NMR
#    outcome=np.array([[[float(raw_input('Enter obtained Mz: '))]]])
#    dummy=float(raw_input('waiting for Mz'))
#    Mz_value=LF.lorentzfit(str(idx_trials+2)+'_spectrum.txt')
#    outcome=np.array([[[Mz_value/abs(Mo_norm)]]])

    # Run SMC and update the posterior distribution
    updater.update(outcome, expparams)
 

# STORE DATA******************************************
    data[idx_trials]['est_mean'] = updater.est_mean()
    data[idx_trials]['sim_outcome'] = outcome
    data[idx_trials]['expparams'] = expparams
   

# PLOT *******************************************  
# plotting particles and weights
    particles = updater.particle_locations
    weights = updater.particle_weights
    fig = plt.figure()

    plt.axvline(updater.est_mean(), linestyle='--', c='blue', linewidth=2)
    plt.axvline(true_model, linestyle='--', c='red', linewidth=2)
    plt.scatter(
            particles[:, 0], weights*10,
            s=50*(1+(weights-1/N_particles)*N_particles)
예제 #10
0
        # Copy the buffer back from the GPU and free memory there.
        cl.enqueue_copy(self._queue, pr0, dest_buf)
        mps_buf.release()
        eps_buf.release()
        dest_buf.release()
        
        # Now we concatenate over outcomes.
        return FiniteOutcomeModel.pr0_to_likelihood_array(outcomes, pr0)

## SCRIPT ######################################################################

if __name__ == "__main__":
    # NOTE: This is now redundant with the perf_testing module.

    simple_model = SimplePrecessionModel()

    for model in [AcceleratedPrecessionModel(), SimplePrecessionModel()]:
        
        true = np.random.random(1)
        updater = SMCUpdater(model, 100000, UniformDistribution([0, 1]))
        
        tic = time.time()
        
        for idx_exp in range(200):
            if not (idx_exp % 20):
                print(idx_exp)
            expparams = np.array([(9 / 8) ** idx_exp])
            updater.update(simple_model.simulate_experiment(true, expparams), expparams)
            
        print(model, updater.est_mean(), true, time.time() - tic)
예제 #11
0
def perf_test(model,
              n_particles,
              prior,
              n_exp,
              heuristic_class,
              true_model=None,
              true_prior=None,
              true_mps=None,
              extra_updater_args=None):
    """
    Runs a trial of using SMC to estimate the parameters of a model, given a
    number of particles, a prior distribution and an experiment design
    heuristic.

    :param qinfer.Model model: Model whose parameters are to
        be estimated.
    :param int n_particles: Number of SMC particles to use.
    :param qinfer.Distribution prior: Prior to use in selecting
        SMC particles.
    :param int n_exp: Number of experimental data points to draw from the
        model.
    :param qinfer.Heuristic heuristic_class: Constructor function
        for the experiment design heuristic to be used.
    :param qinfer.Model true_model: Model to be used in
        generating experimental data. If ``None``, assumed to be ``model``.
        Note that if the true and estimation models have different numbers
        of parameters, the loss will be calculated by aligning the
        respective model vectors "at the right," analogously to the
        convention used by NumPy broadcasting.
    :param qinfer.Distribution true_prior: Prior to be used in
        selecting the true model parameters. If ``None``, assumed to be
        ``prior``.
    :param numpy.ndarray true_mps: The true model parameters. If ``None``,
        it will be sampled from ``true_prior``. Note that as this function
        runs exactly one trial, only one model parameter vector may be passed.
        In particular, this requires that ``len(true_mps.shape) == 1``. 
    :param dict extra_updater_args: Extra keyword arguments for the updater,
        such as resampling and zero-weight policies.
    :rtype np.ndarray: See :ref:`perf_testing_struct` for more details on 
        the type returned by this function.
    :return: A record array of performance metrics, indexed by the number
        of experiments performed.
    """

    if true_model is None:
        true_model = model

    if true_prior is None:
        true_prior = prior

    if true_mps is None:
        true_mps = true_prior.sample()

    if extra_updater_args is None:
        extra_updater_args = {}

    n_min_modelparams = min(model.n_modelparams, true_model.n_modelparams)

    dtype, is_scalar_exp = actual_dtype(model, true_model)
    performance = np.zeros((n_exp, ), dtype=dtype)

    updater = SMCUpdater(model, n_particles, prior, **extra_updater_args)
    heuristic = heuristic_class(updater)

    for idx_exp in range(n_exp):
        # Set inside the loop to handle the case where the
        # true model is time-dependent as well as the estimation model.
        performance[idx_exp]['true'] = true_mps

        expparams = heuristic()
        datum = true_model.simulate_experiment(true_mps, expparams)

        with timing() as t:
            updater.update(datum, expparams)

        # Update the true model.
        true_mps = true_model.update_timestep(promote_dims_left(true_mps, 2),
                                              expparams)[:, :, 0]

        est_mean = updater.est_mean()
        delta = np.subtract(*shorten_right(est_mean, true_mps))
        loss = np.dot(delta**2, model.Q[-n_min_modelparams:])

        performance[idx_exp]['elapsed_time'] = t.delta_t
        performance[idx_exp]['loss'] = loss
        performance[idx_exp]['resample_count'] = updater.resample_count
        performance[idx_exp]['outcome'] = datum
        performance[idx_exp]['est'] = est_mean
        if is_scalar_exp:
            performance[idx_exp]['experiment'] = expparams
        else:
            for param_name in [param[0] for param in model.expparams_dtype]:
                performance[idx_exp][param_name] = expparams[param_name]

    return performance
예제 #12
0
def perf_test(
        model, n_particles, prior, n_exp, heuristic_class,
        true_model=None, true_prior=None, true_mps=None,
        extra_updater_args=None
    ):
    """
    Runs a trial of using SMC to estimate the parameters of a model, given a
    number of particles, a prior distribution and an experiment design
    heuristic.

    :param qinfer.Model model: Model whose parameters are to
        be estimated.
    :param int n_particles: Number of SMC particles to use.
    :param qinfer.Distribution prior: Prior to use in selecting
        SMC particles.
    :param int n_exp: Number of experimental data points to draw from the
        model.
    :param qinfer.Heuristic heuristic_class: Constructor function
        for the experiment design heuristic to be used.
    :param qinfer.Model true_model: Model to be used in
        generating experimental data. If ``None``, assumed to be ``model``.
    :param qinfer.Distribution true_prior: Prior to be used in
        selecting the true model parameters. If ``None``, assumed to be
        ``prior``.
    :param np.ndarray true_mps: The true model parameters. If ``None``,
        it will be sampled from ``true_prior``. Note that the performance
        record can only handle one outcome and therefore ONLY ONE TRUE MODEL.
        An error will occur if ``true_mps.shape[0] > 1`` returns ``True``.
    :param dict extra_updater_args: Extra keyword arguments for the updater,
        such as resampling and zero-weight policies.
    :rtype np.ndarray: See :ref:`perf_testing_struct` for more details on 
        the type returned by this function.
    :return: A record array of performance metrics, indexed by the number
        of experiments performed.
    """

    if true_model is None:
        true_model = model

    if true_prior is None:
        true_prior = prior

    if true_mps is None:
        true_mps = true_prior.sample()

    if extra_updater_args is None:
        extra_updater_args = {}

    dtype, is_scalar_exp = actual_dtype(model)
    performance = np.zeros((n_exp,), dtype=dtype)

    updater = SMCUpdater(model, n_particles, prior, **extra_updater_args)
    heuristic = heuristic_class(updater)

    performance['true'] = true_mps

    for idx_exp in xrange(n_exp):
        expparams = heuristic()
        datum = true_model.simulate_experiment(true_mps, expparams)

        with timing() as t:
            updater.update(datum, expparams)

        est_mean = updater.est_mean()
        delta = est_mean - true_mps
        loss = np.dot(delta**2, model.Q)

        performance[idx_exp]['elapsed_time'] = t.delta_t
        performance[idx_exp]['loss'] = loss
        performance[idx_exp]['resample_count'] = updater.resample_count
        performance[idx_exp]['outcome'] = datum
        performance[idx_exp]['est'] = est_mean
        if is_scalar_exp:
            performance[idx_exp]['experiment'] = expparams
        else:
            for param_name in [param[0] for param in model.expparams_dtype]:
                performance[idx_exp][param_name] = expparams[param_name]

    return performance
예제 #13
0
class TestSMCUpdater(DerandomizedTestCase):
	# True model parameter for test
	MODELPARAMS = np.array([1,])
	TEST_EXPPARAMS = np.linspace(1.,10.,100,dtype=np.float)
	PRIOR = UniformDistribution([[0,2]])
	N_PARTICLES = 10000

	TEST_TARGET_COV = np.array([[0.01]])

	def setUp(self):

		super(TestSMCUpdater,self).setUp()
		self.precession_model = SimplePrecessionModel()
		self.num_precession_model = NumericalSimplePrecessionModel() 
		self.expparams = TestSMCUpdater.TEST_EXPPARAMS.reshape(-1,1)
		self.outcomes = self.precession_model.simulate_experiment(TestSMCUpdater.MODELPARAMS,
				TestSMCUpdater.TEST_EXPPARAMS,repeat=1 ).reshape(-1,1)

		self.updater = SMCUpdater(self.precession_model,
				TestSMCUpdater.N_PARTICLES,TestSMCUpdater.PRIOR)
		self.updater_bayes = SMCUpdaterBCRB(self.precession_model,
				TestSMCUpdater.N_PARTICLES,TestSMCUpdater.PRIOR,adaptive=True)
		self.num_updater = SMCUpdater(self.num_precession_model,
				TestSMCUpdater.N_PARTICLES,TestSMCUpdater.PRIOR)
		self.num_updater_bayes = SMCUpdaterBCRB(self.num_precession_model,
				TestSMCUpdater.N_PARTICLES,TestSMCUpdater.PRIOR,adaptive=True)


	def test_smc_fitting(self):
		"""
		Checks that the fitters converge on true value on simple precession_model. Is a stochastic
		test but I ran 100 times and there were no fails, with these parameters.
		"""

		self.updater.batch_update(self.outcomes,self.expparams)
		self.updater_bayes.batch_update(self.outcomes,self.expparams)
		self.num_updater.batch_update(self.outcomes,self.expparams)
		self.num_updater_bayes.batch_update(self.outcomes,self.expparams)

		#Assert that models have learned true model parameters from data 
		#test means
		assert_almost_equal(self.updater.est_mean(),TestSMCUpdater.MODELPARAMS,2)
		assert_almost_equal(self.updater_bayes.est_mean(),TestSMCUpdater.MODELPARAMS,2)
		assert_almost_equal(self.num_updater.est_mean(),TestSMCUpdater.MODELPARAMS,2)
		assert_almost_equal(self.num_updater_bayes.est_mean(),TestSMCUpdater.MODELPARAMS,2)


		#Assert that covariances have been reduced below thresholds
		#test covs 
		assert_array_less(self.updater.est_covariance_mtx(),TestSMCUpdater.TEST_TARGET_COV)
		assert_array_less(self.updater_bayes.est_covariance_mtx(),TestSMCUpdater.TEST_TARGET_COV)
		assert_array_less(self.num_updater.est_covariance_mtx(),TestSMCUpdater.TEST_TARGET_COV)
		assert_array_less(self.num_updater_bayes.est_covariance_mtx(),TestSMCUpdater.TEST_TARGET_COV)

	def test_bim(self):
		"""
		Checks that the fitters converge on true value on simple precession_model. Is a stochastic
		test but I ran 100 times and there were no fails, with these parameters.
		"""
		bim_currents = []
		num_bim_currents = []
		bim_adaptives = []
		num_bim_adaptives = []

		#track bims throughout experiments
		for i in range(self.outcomes.shape[0]):			
			self.updater_bayes.update(self.outcomes[i],self.expparams[i])
			self.num_updater_bayes.update(self.outcomes[i],self.expparams[i])

			bim_currents.append(self.updater_bayes.current_bim)
			num_bim_currents.append(self.num_updater_bayes.current_bim)
			bim_adaptives.append(self.updater_bayes.adaptive_bim)
			num_bim_adaptives.append(self.num_updater_bayes.adaptive_bim)

		bim_currents = np.array(bim_currents)
		num_bim_currents = np.array(num_bim_currents)
		bim_adaptives = np.array(bim_adaptives)
		num_bim_adaptives = np.array(num_bim_adaptives)

		#compare numerical and analytical bims 
		assert_almost_equal(bim_currents,num_bim_currents,2)
		assert_almost_equal(bim_adaptives,num_bim_adaptives,2)

		#verify that array copying of properties is working
		assert not np.all(bim_currents == bim_currents[0,...])
		assert not np.all(num_bim_currents == num_bim_currents[0,...])
		assert not np.all(bim_adaptives == bim_adaptives[0,...])
		assert not np.all(num_bim_adaptives == num_bim_adaptives[0,...])


		#verify that BCRB is approximately reached 
		assert_almost_equal(self.updater_bayes.est_covariance_mtx(),np.linalg.inv(self.updater_bayes.current_bim),2)
		assert_almost_equal(self.updater_bayes.est_covariance_mtx(),np.linalg.inv(self.updater_bayes.adaptive_bim),2)
		assert_almost_equal(self.num_updater_bayes.est_covariance_mtx(),np.linalg.inv(self.updater_bayes.current_bim),2)
		assert_almost_equal(self.num_updater_bayes.est_covariance_mtx(),np.linalg.inv(self.updater_bayes.adaptive_bim),2)
예제 #14
0
def perf_test(
        model, n_particles, prior, n_exp, heuristic_class,
        true_model=None, true_prior=None, true_mps=None,
        extra_updater_args=None
    ):
    """
    Runs a trial of using SMC to estimate the parameters of a model, given a
    number of particles, a prior distribution and an experiment design
    heuristic.

    :param qinfer.Model model: Model whose parameters are to
        be estimated.
    :param int n_particles: Number of SMC particles to use.
    :param qinfer.Distribution prior: Prior to use in selecting
        SMC particles.
    :param int n_exp: Number of experimental data points to draw from the
        model.
    :param qinfer.Heuristic heuristic_class: Constructor function
        for the experiment design heuristic to be used.
    :param qinfer.Model true_model: Model to be used in
        generating experimental data. If ``None``, assumed to be ``model``.
        Note that if the true and estimation models have different numbers
        of parameters, the loss will be calculated by aligning the
        respective model vectors "at the right," analogously to the
        convention used by NumPy broadcasting.
    :param qinfer.Distribution true_prior: Prior to be used in
        selecting the true model parameters. If ``None``, assumed to be
        ``prior``.
    :param numpy.ndarray true_mps: The true model parameters. If ``None``,
        it will be sampled from ``true_prior``. Note that as this function
        runs exactly one trial, only one model parameter vector may be passed.
        In particular, this requires that ``len(true_mps.shape) == 1``. 
    :param dict extra_updater_args: Extra keyword arguments for the updater,
        such as resampling and zero-weight policies.
    :rtype np.ndarray: See :ref:`perf_testing_struct` for more details on 
        the type returned by this function.
    :return: A record array of performance metrics, indexed by the number
        of experiments performed.
    """

    if true_model is None:
        true_model = model

    if true_prior is None:
        true_prior = prior

    if true_mps is None:
        true_mps = true_prior.sample()

    if extra_updater_args is None:
        extra_updater_args = {}

    n_min_modelparams = min(model.n_modelparams, true_model.n_modelparams)

    dtype, is_scalar_exp = actual_dtype(model, true_model)
    performance = np.zeros((n_exp,), dtype=dtype)

    updater = SMCUpdater(model, n_particles, prior, **extra_updater_args)
    heuristic = heuristic_class(updater)

    for idx_exp in range(n_exp):
        # Set inside the loop to handle the case where the
        # true model is time-dependent as well as the estimation model.
        performance[idx_exp]['true'] = true_mps

        expparams = heuristic()
        datum = true_model.simulate_experiment(true_mps, expparams)

        with timing() as t:
            updater.update(datum, expparams)

        # Update the true model.
        true_mps = true_model.update_timestep(
            promote_dims_left(true_mps, 2), expparams
        )[:, :, 0]

        est_mean = updater.est_mean()
        delta = np.subtract(*shorten_right(est_mean, true_mps))
        loss = np.dot(delta**2, model.Q[-n_min_modelparams:])

        performance[idx_exp]['elapsed_time'] = t.delta_t
        performance[idx_exp]['loss'] = loss
        performance[idx_exp]['resample_count'] = updater.resample_count
        performance[idx_exp]['outcome'] = datum
        performance[idx_exp]['est'] = est_mean
        if is_scalar_exp:
            performance[idx_exp]['experiment'] = expparams
        else:
            for param_name in [param[0] for param in model.expparams_dtype]:
                performance[idx_exp][param_name] = expparams[param_name]

    return performance