def test_leapfrog_reversible(): n = 3 np.random.seed(42) start, model, _ = models.non_normal(n) size = sum(start[n.name].size for n in model.value_vars) scaling = floatX(np.random.rand(size)) class HMC(BaseHMC): def _hamiltonian_step(self, *args, **kwargs): pass step = HMC(vars=model.value_vars, model=model, scaling=scaling) step.integrator._logp_dlogp_func.set_extra_values({}) astart = DictToArrayBijection.map(start) p = RaveledVars(floatX(step.potential.random()), astart.point_map_info) q = RaveledVars(floatX(np.random.randn(size)), astart.point_map_info) start = step.integrator.compute_state(p, q) for epsilon in [0.01, 0.1]: for n_steps in [1, 2, 3, 4, 20]: state = start for _ in range(n_steps): state = step.integrator.step(epsilon, state) for _ in range(n_steps): state = step.integrator.step(-epsilon, state) npt.assert_allclose(state.q.data, start.q.data, rtol=1e-5) npt.assert_allclose(state.p.data, start.p.data, rtol=1e-5)
def _step(self, epsilon, state): axpy = linalg.blas.get_blas_funcs("axpy", dtype=self._dtype) pot = self._potential q_new = state.q.data.copy() p_new = state.p.data.copy() v_new = np.empty_like(q_new) q_new_grad = np.empty_like(q_new) dt = 0.5 * epsilon # p is already stored in p_new # p_new = p + dt * q_grad axpy(state.q_grad, p_new, a=dt) pot.velocity(p_new, out=v_new) # q is already stored in q_new # q_new = q + epsilon * v_new axpy(v_new, q_new, a=epsilon) p_new = RaveledVars(p_new, state.p.point_map_info) q_new = RaveledVars(q_new, state.q.point_map_info) logp = self._logp_dlogp_func(q_new, grad_out=q_new_grad) # p_new = p_new + dt * q_new_grad axpy(q_new_grad, p_new.data, a=dt) kinetic = pot.velocity_energy(p_new.data, v_new) energy = kinetic - logp return State(q_new, p_new, v_new, q_new_grad, energy, logp)
def astep(self, q0: RaveledVars, logp) -> Tuple[RaveledVars, List[Dict[str, Any]]]: logp_q0 = logp(q0) point_map_info = q0.point_map_info q0 = q0.data # Convert adaptive_scale_factor to a jump probability p_jump = 1.0 - 0.5**self.scaling rand_array = nr.random(q0.shape) q = np.copy(q0) # Locations where switches occur, according to p_jump switch_locs = rand_array < p_jump q[switch_locs] = True - q[switch_locs] logp_q = logp(RaveledVars(q, point_map_info)) accept = logp_q - logp_q0 q_new, accepted = metrop_select(accept, q, q0) self.accepted += accepted stats = { "tune": self.tune, "accept": np.exp(accept), "p_jump": p_jump, } q_new = RaveledVars(q_new, point_map_info) return q_new, [stats]
def astep(self, q0: RaveledVars) -> Tuple[RaveledVars, List[Dict[str, Any]]]: point_map_info = q0.point_map_info q0 = q0.data if not self.steps_until_tune and self.tune: # Tune scaling parameter self.scaling = tune(self.scaling, self.accepted_sum / float(self.tune_interval)) # Reset counter self.steps_until_tune = self.tune_interval self.accepted_sum[:] = 0 delta = self.proposal_dist() * self.scaling if self.any_discrete: if self.all_discrete: delta = np.round(delta, 0).astype("int64") q0 = q0.astype("int64") q = (q0 + delta).astype("int64") else: delta[self.discrete] = np.round(delta[self.discrete], 0) q = q0 + delta else: q = floatX(q0 + delta) if self.elemwise_update: q_temp = q0.copy() # Shuffle order of updates (probably we don't need to do this in every step) np.random.shuffle(self.enum_dims) for i in self.enum_dims: q_temp[i] = q[i] accept_rate_i = self.delta_logp(q_temp, q0) q_temp_, accepted_i = metrop_select(accept_rate_i, q_temp, q0) q_temp[i] = q_temp_[i] self.accept_rate_iter[i] = accept_rate_i self.accepted_iter[i] = accepted_i self.accepted_sum[i] += accepted_i q = q_temp else: accept_rate = self.delta_logp(q, q0) q, accepted = metrop_select(accept_rate, q, q0) self.accept_rate_iter = accept_rate self.accepted_iter = accepted self.accepted_sum += accepted self.steps_until_tune -= 1 stats = { "tune": self.tune, "scaling": np.mean(self.scaling), "accept": np.mean(np.exp(self.accept_rate_iter)), "accepted": np.mean(self.accepted_iter), } return RaveledVars(q, point_map_info), [stats]
def astep(self, q0, logp): q0_val = q0.data self.w = np.resize(self.w, len(q0_val)) # this is a repmat q = np.copy(q0_val) # TODO: find out if we need this ql = np.copy(q0_val) # l for left boundary qr = np.copy(q0_val) # r for right boudary for i in range(len(q0_val)): # uniformly sample from 0 to p(q), but in log space q_ra = RaveledVars(q, q0.point_map_info) y = logp(q_ra) - nr.standard_exponential() ql[i] = q[i] - nr.uniform(0, self.w[i]) qr[i] = q[i] + self.w[i] # Stepping out procedure cnt = 0 while y <= logp( RaveledVars(ql, q0.point_map_info) ): # changed lt to leq for locally uniform posteriors ql[i] -= self.w[i] cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) cnt = 0 while y <= logp(RaveledVars(qr, q0.point_map_info)): qr[i] += self.w[i] cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) cnt = 0 q[i] = nr.uniform(ql[i], qr[i]) while logp( q_ra ) < y: # Changed leq to lt, to accomodate for locally flat posteriors # Sample uniformly from slice if q[i] > q0_val[i]: qr[i] = q[i] elif q[i] < q0_val[i]: ql[i] = q[i] q[i] = nr.uniform(ql[i], qr[i]) cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) if ( self.tune ): # I was under impression from MacKays lectures that slice width can be tuned without # breaking markovianness. Can we do it regardless of self.tune?(@madanh) self.w[i] = self.w[i] * (self.n_tunes / (self.n_tunes + 1)) + ( qr[i] - ql[i]) / (self.n_tunes + 1) # same as before # unobvious and important: return qr and ql to the same point qr[i] = q[i] ql[i] = q[i] if self.tune: self.n_tunes += 1 return q
def step(self, point): for name, shared_var in self.shared.items(): shared_var.set_value(point[name]) q = DictToArrayBijection.map( {v.name: point[v.name] for v in self.vars}) step_res = self.astep(q) if self.generates_stats: apoint, stats = step_res else: apoint = step_res if not isinstance(apoint, RaveledVars): # We assume that the mapping has stayed the same apoint = RaveledVars(apoint, q.point_map_info) new_point = DictToArrayBijection.rmap(apoint, start_point=point) if self.generates_stats: return new_point, stats return new_point
def step(self, point: PointType): partial_funcs_and_point = [ DictToArrayBijection.mapf(x, start_point=point) for x in self.fs ] if self.allvars: partial_funcs_and_point.append(point) apoint = DictToArrayBijection.map( {v.name: point[v.name] for v in self.vars}) step_res = self.astep(apoint, *partial_funcs_and_point) if self.generates_stats: apoint_new, stats = step_res else: apoint_new = step_res if not isinstance(apoint_new, RaveledVars): # We assume that the mapping has stayed the same apoint_new = RaveledVars(apoint_new, apoint.point_map_info) point_new = DictToArrayBijection.rmap(apoint_new, start_point=point) if self.generates_stats: return point_new, stats return point_new
def astep(self, q0: RaveledVars) -> Tuple[RaveledVars, List[Dict[str, Any]]]: point_map_info = q0.point_map_info q0 = q0.data if not self.steps_until_tune and self.tune: # Tune scaling parameter self.scaling = tune(self.scaling, self.accepted / float(self.tune_interval)) # Reset counter self.steps_until_tune = self.tune_interval self.accepted = 0 delta = self.proposal_dist() * self.scaling if self.any_discrete: if self.all_discrete: delta = np.round(delta, 0).astype("int64") q0 = q0.astype("int64") q = (q0 + delta).astype("int64") else: delta[self.discrete] = np.round(delta[self.discrete], 0) q = q0 + delta else: q = floatX(q0 + delta) accept = self.delta_logp(q, q0) q_new, accepted = metrop_select(accept, q, q0) self.accepted += accepted self.steps_until_tune -= 1 stats = { "tune": self.tune, "scaling": self.scaling, "accept": np.exp(accept), "accepted": accepted, } q_new = RaveledVars(q_new, point_map_info) return q_new, [stats]
def astep(self, q0: RaveledVars) -> Tuple[RaveledVars, List[Dict[str, Any]]]: point_map_info = q0.point_map_info q0 = q0.data # same tuning scheme as DEMetropolis if not self.steps_until_tune and self.tune: if self.tune_target == "scaling": self.scaling = tune(self.scaling, self.accepted / float(self.tune_interval)) elif self.tune_target == "lambda": self.lamb = tune(self.lamb, self.accepted / float(self.tune_interval)) # Reset counter self.steps_until_tune = self.tune_interval self.accepted = 0 epsilon = self.proposal_dist() * self.scaling it = len(self._history) # use the DE-MCMC-Z proposal scheme as soon as the history has 2 entries if it > 1: # differential evolution proposal # select two other chains iz1 = np.random.randint(it) iz2 = np.random.randint(it) while iz2 == iz1: iz2 = np.random.randint(it) z1 = self._history[iz1] z2 = self._history[iz2] # propose a jump q = floatX(q0 + self.lamb * (z1 - z2) + epsilon) else: # propose just with noise in the first 2 iterations q = floatX(q0 + epsilon) accept = self.delta_logp(q, q0) q_new, accepted = metrop_select(accept, q, q0) self.accepted += accepted self._history.append(q_new) self.steps_until_tune -= 1 stats = { "tune": self.tune, "scaling": self.scaling, "lambda": self.lamb, "accept": np.exp(accept), "accepted": accepted, } q_new = RaveledVars(q_new, point_map_info) return q_new, [stats]
def astep_unif(self, q0: RaveledVars, logp) -> RaveledVars: point_map_info = q0.point_map_info q0 = q0.data dimcats = self.dimcats if self.shuffle_dims: nr.shuffle(dimcats) q = RaveledVars(np.copy(q0), point_map_info) logp_curr = logp(q) for dim, k in dimcats: curr_val, q.data[dim] = q.data[dim], sample_except(k, q.data[dim]) logp_prop = logp(q) q.data[dim], accepted = metrop_select(logp_prop - logp_curr, q.data[dim], curr_val) if accepted: logp_curr = logp_prop return q
def astep(self, q0: RaveledVars, logp: Callable[[RaveledVars], np.ndarray]) -> RaveledVars: order = self.order if self.shuffle_dims: nr.shuffle(order) q = RaveledVars(np.copy(q0.data), q0.point_map_info) logp_curr = logp(q) for idx in order: # No need to do metropolis update if the same value is proposed, # as you will get the same value regardless of accepted or reject if nr.rand() < self.transit_p: curr_val, q.data[idx] = q.data[idx], True - q.data[idx] logp_prop = logp(q) q.data[idx], accepted = metrop_select(logp_prop - logp_curr, q.data[idx], curr_val) if accepted: logp_curr = logp_prop return q
def test_grad(self): self.f_grad.set_extra_values({"extra1": 5}) size = self.val1_.size + self.val2_.size array = RaveledVars( np.ones(size, dtype=self.f_grad.dtype), ( ("val1", self.val1_.shape, self.val1_.dtype), ("val2", self.val2_.shape, self.val2_.dtype), ), ) val, grad = self.f_grad(array) assert val == 21 npt.assert_allclose(grad, [5, 5, 5, 1, 1, 1, 1, 1, 1])
def astep_prop(self, q0: RaveledVars, logp) -> RaveledVars: point_map_info = q0.point_map_info q0 = q0.data dimcats = self.dimcats if self.shuffle_dims: nr.shuffle(dimcats) q = RaveledVars(np.copy(q0), point_map_info) logp_curr = logp(q) for dim, k in dimcats: logp_curr = self.metropolis_proportional(q, logp, logp_curr, dim, k) return q
def astep(self, q0: RaveledVars) -> Tuple[RaveledVars, List[Dict[str, Any]]]: point_map_info = q0.point_map_info q0 = q0.data if not self.steps_until_tune and self.tune: if self.tune == "scaling": self.scaling = tune(self.scaling, self.accepted / float(self.tune_interval)) elif self.tune == "lambda": self.lamb = tune(self.lamb, self.accepted / float(self.tune_interval)) # Reset counter self.steps_until_tune = self.tune_interval self.accepted = 0 epsilon = self.proposal_dist() * self.scaling # differential evolution proposal # select two other chains ir1, ir2 = np.random.choice(self.other_chains, 2, replace=False) r1 = DictToArrayBijection.map(self.population[ir1]) r2 = DictToArrayBijection.map(self.population[ir2]) # propose a jump q = floatX(q0 + self.lamb * (r1.data - r2.data) + epsilon) accept = self.delta_logp(q, q0) q_new, accepted = metrop_select(accept, q, q0) self.accepted += accepted self.steps_until_tune -= 1 stats = { "tune": self.tune, "scaling": self.scaling, "lambda": self.lamb, "accept": np.exp(accept), "accepted": accepted, } q_new = RaveledVars(q_new, point_map_info) return q_new, [stats]
def dlogp_func(x): return DictToArrayBijection.mapf(model.fastdlogp_nojac(vars))( RaveledVars(x, x0.point_map_info))
def astep(self, q: RaveledVars) -> Tuple[RaveledVars, List[Dict[str, Any]]]: point_map_info = q.point_map_info sum_trees_output = q.data variable_inclusion = np.zeros(self.num_variates, dtype="int") if self.idx == self.m: self.idx = 0 for tree_id in range(self.idx, self.idx + self.batch): if tree_id >= self.m: break # Generate an initial set of SMC particles # at the end of the algorithm we return one of these particles as the new tree particles = self.init_particles(tree_id) # Compute the sum of trees without the tree we are attempting to replace self.sum_trees_output_noi = sum_trees_output - particles[0].tree.predict_output() self.idx += 1 # The old tree is not growing so we update the weights only once. self.update_weight(particles[0]) for t in range(self.max_stages): # Sample each particle (try to grow each tree), except for the first one. for p in particles[1:]: tree_grew = p.sample_tree_sequential( self.ssv, self.available_predictors, self.prior_prob_leaf_node, self.X, self.missing_data, sum_trees_output, self.mean, self.linear_fit, self.m, self.normal, self.mu_std, self.response, ) if tree_grew: self.update_weight(p) # Normalize weights W_t, normalized_weights = self.normalize(particles) # Resample all but first particle re_n_w = normalized_weights[1:] / normalized_weights[1:].sum() new_indices = np.random.choice(self.indices, size=self.len_indices, p=re_n_w) particles[1:] = particles[new_indices] # Set the new weights for p in particles: p.log_weight = W_t # Check if particles can keep growing, otherwise stop iterating non_available_nodes_for_expansion = [] for p in particles[1:]: if p.expansion_nodes: non_available_nodes_for_expansion.append(0) if all(non_available_nodes_for_expansion): break # Get the new tree and update new_particle = np.random.choice(particles, p=normalized_weights) new_tree = new_particle.tree new_particle.log_weight = new_particle.old_likelihood_logp - self.log_num_particles self.all_particles[tree_id] = new_particle sum_trees_output = self.sum_trees_output_noi + new_tree.predict_output() if self.tune: for index in new_particle.used_variates: self.split_prior[index] += 1 self.ssv = SampleSplittingVariable(self.split_prior) else: self.batch = max(1, int(self.m * 0.2)) self.iter += 1 self.sum_trees.append(new_tree) if not self.iter % self.m: # XXX update the all_trees variable in BARTRV to be used in the rng_fn method # this fails for chains > 1 as the variable is not shared between proccesses self.bart.all_trees.append(self.sum_trees) self.sum_trees = [] for index in new_particle.used_variates: variable_inclusion[index] += 1 stats = {"variable_inclusion": variable_inclusion} sum_trees_output = RaveledVars(sum_trees_output, point_map_info) return sum_trees_output, [stats]
def find_MAP(start=None, vars=None, method="L-BFGS-B", return_raw=False, include_transformed=True, progressbar=True, maxeval=5000, model=None, *args, seed: Optional[int] = None, **kwargs): """Finds the local maximum a posteriori point given a model. `find_MAP` should not be used to initialize the NUTS sampler. Simply call ``pymc.sample()`` and it will automatically initialize NUTS in a better way. Parameters ---------- start: `dict` of parameter values (Defaults to `model.initial_point`) vars: list List of variables to optimize and set to optimum (Defaults to all continuous). method: string or callable Optimization algorithm (Defaults to 'L-BFGS-B' unless discrete variables are specified in `vars`, then `Powell` which will perform better). For instructions on use of a callable, refer to SciPy's documentation of `optimize.minimize`. return_raw: bool Whether to return the full output of scipy.optimize.minimize (Defaults to `False`) include_transformed: bool, optional defaults to True Flag for reporting automatically transformed variables in addition to original variables. progressbar: bool, optional defaults to True Whether or not to display a progress bar in the command line. maxeval: int, optional, defaults to 5000 The maximum number of times the posterior distribution is evaluated. model: Model (optional if in `with` context) *args, **kwargs Extra args passed to scipy.optimize.minimize Notes ----- Older code examples used `find_MAP` to initialize the NUTS sampler, but this is not an effective way of choosing starting values for sampling. As a result, we have greatly enhanced the initialization of NUTS and wrapped it inside ``pymc.sample()`` and you should thus avoid this method. """ model = modelcontext(model) if vars is None: vars = model.cont_vars if not vars: raise ValueError("Model has no unobserved continuous variables.") vars = inputvars(vars) disc_vars = list(typefilter(vars, discrete_types)) allinmodel(vars, model) ipfn = make_initial_point_fn( model=model, jitter_rvs={}, return_transformed=True, overrides=start, ) if seed is None: seed = model.rng_seeder.randint(2**30, dtype=np.int64) start = ipfn(seed) model.check_start_vals(start) x0 = DictToArrayBijection.map(start) # TODO: If the mapping is fixed, we can simply create graphs for the # mapping and avoid all this bijection overhead def logp_func(x): return DictToArrayBijection.mapf(model.fastlogp_nojac)(RaveledVars( x, x0.point_map_info)) try: # This might be needed for calls to `dlogp_func` # start_map_info = tuple((v.name, v.shape, v.dtype) for v in vars) def dlogp_func(x): return DictToArrayBijection.mapf(model.fastdlogp_nojac(vars))( RaveledVars(x, x0.point_map_info)) compute_gradient = True except (AttributeError, NotImplementedError, tg.NullTypeGradError): compute_gradient = False if disc_vars or not compute_gradient: pm._log.warning( "Warning: gradient not available." + "(E.g. vars contains discrete variables). MAP " + "estimates may not be accurate for the default " + "parameters. Defaulting to non-gradient minimization " + "'Powell'.") method = "Powell" if compute_gradient: cost_func = CostFuncWrapper(maxeval, progressbar, logp_func, dlogp_func) else: cost_func = CostFuncWrapper(maxeval, progressbar, logp_func) try: opt_result = minimize(cost_func, x0.data, method=method, jac=compute_gradient, *args, **kwargs) mx0 = opt_result["x"] # r -> opt_result except (KeyboardInterrupt, StopIteration) as e: mx0, opt_result = cost_func.previous_x, None if isinstance(e, StopIteration): pm._log.info(e) finally: last_v = cost_func.n_eval if progressbar: assert isinstance(cost_func.progress, ProgressBar) cost_func.progress.total = last_v cost_func.progress.update(last_v) print(file=sys.stdout) mx0 = RaveledVars(mx0, x0.point_map_info) vars = get_default_varnames(model.unobserved_value_vars, include_transformed) mx = { var.name: value for var, value in zip( vars, model.fastfn(vars)(DictToArrayBijection.rmap(mx0))) } if return_raw: return mx, opt_result else: return mx
def astep(self, q0): """Perform a single HMC iteration.""" perf_start = time.perf_counter() process_start = time.process_time() p0 = self.potential.random() p0 = RaveledVars(p0, q0.point_map_info) start = self.integrator.compute_state(q0, p0) if not np.isfinite(start.energy): model = self._model check_test_point = model.point_logps() error_logp = check_test_point.loc[ (np.abs(check_test_point) >= 1e20) | np.isnan(check_test_point) ] self.potential.raise_ok(q0.point_map_info) message_energy = ( "Bad initial energy, check any log probabilities that " "are inf or -inf, nan or very small:\n{}".format(error_logp.to_string()) ) warning = SamplerWarning( WarningType.BAD_ENERGY, message_energy, "critical", self.iter_count, ) self._warnings.append(warning) raise SamplingError("Bad initial energy") adapt_step = self.tune and self.adapt_step_size step_size = self.step_adapt.current(adapt_step) self.step_size = step_size if self._step_rand is not None: step_size = self._step_rand(step_size) hmc_step = self._hamiltonian_step(start, p0.data, step_size) perf_end = time.perf_counter() process_end = time.process_time() self.step_adapt.update(hmc_step.accept_stat, adapt_step) self.potential.update(hmc_step.end.q, hmc_step.end.q_grad, self.tune) if hmc_step.divergence_info: info = hmc_step.divergence_info point = None point_dest = None info_store = None if self.tune: kind = WarningType.TUNING_DIVERGENCE else: kind = WarningType.DIVERGENCE self._num_divs_sample += 1 # We don't want to fill up all memory with divergence info if self._num_divs_sample < 100 and info.state is not None: point = DictToArrayBijection.rmap(info.state.q) if self._num_divs_sample < 100 and info.state_div is not None: point = DictToArrayBijection.rmap(info.state_div.q) if self._num_divs_sample < 100: info_store = info warning = SamplerWarning( kind, info.message, "debug", self.iter_count, info.exec_info, divergence_point_source=point, divergence_point_dest=point_dest, divergence_info=info_store, ) self._warnings.append(warning) self.iter_count += 1 if not self.tune: self._samples_after_tune += 1 stats = { "tune": self.tune, "diverging": bool(hmc_step.divergence_info), "perf_counter_diff": perf_end - perf_start, "process_time_diff": process_end - process_start, "perf_counter_start": perf_start, } stats.update(hmc_step.stats) stats.update(self.step_adapt.stats()) return hmc_step.end.q, [stats]
def astep(self, q0, logp): q0_val = q0.data self.w = np.resize(self.w, len(q0_val)) # this is a repmat q = np.copy(q0_val) ql = np.copy(q0_val) # l for left boundary qr = np.copy(q0_val) # r for right boudary # The points are not copied, so it's fine to update them inplace in the # loop below q_ra = RaveledVars(q, q0.point_map_info) ql_ra = RaveledVars(ql, q0.point_map_info) qr_ra = RaveledVars(qr, q0.point_map_info) for i, wi in enumerate(self.w): # uniformly sample from 0 to p(q), but in log space y = logp(q_ra) - nr.standard_exponential() # Create initial interval ql[i] = q[i] - nr.uniform() * wi # q[i] + r * w qr[i] = ql[i] + wi # Equivalent to q[i] + (1-r) * w # Stepping out procedure cnt = 0 while y <= logp( ql_ra ): # changed lt to leq for locally uniform posteriors ql[i] -= wi cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) cnt = 0 while y <= logp(qr_ra): qr[i] += wi cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) cnt = 0 q[i] = nr.uniform(ql[i], qr[i]) while y > logp( q_ra ): # Changed leq to lt, to accomodate for locally flat posteriors # Sample uniformly from slice if q[i] > q0_val[i]: qr[i] = q[i] elif q[i] < q0_val[i]: ql[i] = q[i] q[i] = nr.uniform(ql[i], qr[i]) cnt += 1 if cnt > self.iter_limit: raise RuntimeError(LOOP_ERR_MSG % self.iter_limit) if self.tune: # I was under impression from MacKays lectures that slice width can be tuned without # breaking markovianness. Can we do it regardless of self.tune?(@madanh) self.w[i] = wi * (self.n_tunes / (self.n_tunes + 1)) + ( qr[i] - ql[i]) / (self.n_tunes + 1) # Set qr and ql to the accepted points (they matter for subsequent iterations) qr[i] = ql[i] = q[i] if self.tune: self.n_tunes += 1 return q