def __init__(self): self._t = pints.vector([1, 2, 3]) self._v = pints.vector([-1, 2, 3])
def tell(self, reply): """ See :meth:`pints.SingleChainMCMC.tell()`. """ # Check if we had a proposal if not self._ready_for_tell: raise RuntimeError('Tell called before proposal was set.') self._ready_for_tell = False # Unpack reply fx, log_gradient = reply # Check reply, copy gradient fx = float(fx) log_gradient = pints.vector(log_gradient) assert(log_gradient.shape == (self._n_parameters, )) # First point? if self._current is None: if not np.isfinite(fx): raise ValueError( 'Initial point for MCMC must have finite log_pdf.') # Accept self._current = self._proposed self._current_log_pdf = fx self._current_gradient = log_gradient # Increase iteration count self._iterations += 1 # Clear proposal self._proposed = None # Return first point for chain return self._current # Calculate alpha proposed_gradient = log_gradient self._backward_mu = self._proposed + ( 0.5 * self._epsilon**2 * proposed_gradient) self._backward_q = scipy.stats.multivariate_normal.logpdf( self._current, self._backward_mu, self._epsilon**2 * (np.diag(np.ones(self._n_parameters)))) alpha = fx + self._backward_q - ( self._current_log_pdf + self._forward_q) # Check if the proposed point can be accepted accepted = 0 if np.isfinite(fx): u = np.log(np.random.uniform(0, 1)) if alpha > u: accepted = 1 self._current = self._proposed self._current_log_pdf = fx self._current_gradient = log_gradient # Clear proposal self._proposed = None # Update acceptance rate (only used for output!) self._acceptance = ((self._iterations * self._acceptance + accepted) / (self._iterations + 1)) # Increase iteration count self._iterations += 1 # Return new point for chain return self._current
def tell(self, reply): """ See :meth:`pints.SingleChainMCMC.tell()`. """ if not self._ready_for_tell: raise RuntimeError('Tell called before proposal was set.') self._ready_for_tell = False # Unpack reply energy, gradient = reply # Check reply, copy gradient energy = float(energy) gradient = pints.vector(gradient) assert gradient.shape == (self._n_parameters, ) # Energy = -log_pdf, so flip both signs! energy = -energy gradient = -gradient # Very first call if self._current is None: # Check first point is somewhere sensible if not np.isfinite(energy): raise ValueError( 'Initial point for MCMC must have finite logpdf.') # Set current sample, energy, and gradient self._current = self._x0 self._current_energy = energy self._current_gradient = gradient # Increase iteration count self._mcmc_iteration += 1 # Mark current as read-only, so it can be safely returned. # Gradient won't be returned (only -gradient, so no need. self._current.setflags(write=False) # Return first point in chain return (self._current, (-self._current_energy, -self._current_gradient), False) # Set gradient of current leapfrog position self._gradient = gradient # Update the leapfrog iteration count self._frog_iteration += 1 # Not the last iteration? Then perform a leapfrog step and return if self._frog_iteration < self._n_frog_iterations: self._momentum -= self._scaled_epsilon * self._gradient # Return None to indicate there is no new sample for the chain return None # Final leapfrog iteration: only do half a step self._momentum -= self._scaled_epsilon * self._gradient * 0.5 # Before starting accept/reject procedure, check if the leapfrog # procedure has led to a finite momentum and logpdf. If not, reject. accept = 0 if np.isfinite(energy) and np.all(np.isfinite(self._momentum)): # Evaluate potential and kinetic energies at start and end of # leapfrog trajectory current_U = self._current_energy current_K = np.sum(self._current_momentum**2 / 2) proposed_U = energy proposed_K = np.sum(self._momentum**2 / 2) # Check for divergent iterations by testing whether the # Hamiltonian difference is above a threshold div = proposed_U + proposed_K - (self._current_energy + current_K) if np.abs(div) > self._hamiltonian_threshold: # pragma: no cover self._divergent = np.append(self._divergent, self._mcmc_iteration) self._momentum = self._position = self._gradient = None self._frog_iteration = 0 # Update MCMC iteration count self._mcmc_iteration += 1 # Update acceptance rate (only used for output!) self._mcmc_acceptance = ( (self._mcmc_iteration * self._mcmc_acceptance + accept) / (self._mcmc_iteration + 1)) # Return current state return (self._current, (-self._current_energy, -self._current_gradient), False) # Accept/reject else: r = np.exp(current_U - proposed_U + current_K - proposed_K) if np.random.uniform(0, 1) < r: accept = 1 self._current = self._position self._current_energy = energy self._current_gradient = gradient # Mark current as read-only, so it can be safely returned. # Gradient won't be returned (only -gradient, so no need. self._current.setflags(write=False) # Reset leapfrog mechanism self._momentum = self._position = self._gradient = None self._frog_iteration = 0 # Update MCMC iteration count self._mcmc_iteration += 1 # Update acceptance rate (only used for output!) self._mcmc_acceptance = ( (self._mcmc_iteration * self._mcmc_acceptance + accept) / (self._mcmc_iteration + 1)) # Return current position as next sample in the chain return (self._current, (-self._current_energy, -self._current_gradient), accept > 0)
def surface(points, values, boundaries=None, markers='+', figsize=None): """ Takes irregularly spaced points and function evaluations in a two-dimensional parameter space and creates a coloured surface plot using a voronoi diagram. Returns a ``matplotlib`` figure object and axes handle. Parameters ---------- points A list of (two-dimensional) points in parameter space. values The values (e.g. error measure evaluations) corresponding to these points. boundaries An optional :class:`pints.RectangularBoundaries` object to restrict the area shown. If set to ``None`` boundaries will be determined from the given ``points``. markers An optional string indicating the matplotlib markers to use to plot the ``points``. Set to ``None`` to hide. figsize An optional tuple ``(width, height)`` that will be passed to matplotlib when creating the figure. If set to ``None`` matplotlib will use its default figure size. """ import matplotlib.pyplot as plt from matplotlib.collections import PolyCollection # Check points and values points = pints.matrix2d(points) n, d = points.shape if d != 2: raise ValueError('Only two-dimensional parameters are supported.') values = pints.vector(values) if len(values) != n: raise ValueError( 'The number of values must match the number of points.') # Extract x and y points x = points[:, 0] y = points[:, 1] del (points) # Check boundaries if boundaries is None: xmin, xmax = np.min(x), np.max(x) r = 0.1 * (xmax - xmin) xmin -= r xmax += r ymin, ymax = np.min(y), np.max(y) r = 0.1 * (ymax - ymin) ymin -= r ymax += r else: if boundaries.n_parameters() != 2: raise ValueError('The boundaries must be two-dimensional.') xmin, ymin = boundaries.lower() xmax, ymax = boundaries.upper() # Get voronoi regions (and filter points and evaluations) xlim = xmin, xmax ylim = ymin, ymax x, y, values, regions = _voronoi_regions(x, y, values, xlim, ylim) # Create figure and axes figure, axes = plt.subplots(figsize=figsize) axes.set_xlim(xmin, xmax) axes.set_ylim(ymin, ymax) # Add coloured voronoi regions c = PolyCollection(regions, array=values, edgecolors='none', cmap='viridis_r') axes.add_collection(c) # Add markers if markers: axes.plot(x, y, markers, color='#ffffff', alpha=0.75) # Add colorbar figure.colorbar(c, ax=axes) return figure, axes
def test_vector(self): # Test correct use with 1d arrays x = np.array([1, 2, 3]) v = pints.vector(x) x = np.array([1]) v = pints.vector(x) x = np.array([]) v = pints.vector(x) # Test correct use with higher dimensional arrays x = np.array([1, 2, 3]) w = pints.vector(x) x = x.reshape((3, 1, 1, 1, 1)) v = pints.vector(x) self.assertTrue(np.all(w == v)) x = x.reshape((1, 3, 1, 1, 1)) v = pints.vector(x) self.assertTrue(np.all(w == v)) x = x.reshape((1, 1, 1, 1, 3)) v = pints.vector(x) self.assertTrue(np.all(w == v)) # Test correct use with lists x = [1, 2, 3] v = pints.vector(x) self.assertTrue(np.all(w == v)) x = [4] v = pints.vector(x) x = [] v = pints.vector(x) # Test incorrect use with higher dimensional arrays x = np.array([4, 5, 6, 3, 2, 4]) x = x.reshape((2, 3)) self.assertRaises(ValueError, pints.vector, x) x = x.reshape((3, 2)) self.assertRaises(ValueError, pints.vector, x) x = x.reshape((6, 1)) v = pints.vector(x) # Test correct use with scalar x = 5 v = pints.vector(x) self.assertEqual(len(v), 1) self.assertEqual(v[0], 5) # Test read-only def assign(): v[0] = 10 self.assertRaises(ValueError, assign)
def function_between_points(f, point_1, point_2, padding=0.25, evaluations=20): """ Creates and returns a plot of a function between two points in parameter space. Returns a ``matplotlib`` figure object and axes handle. Parameters ---------- f A :class:`pints.LogPDF` or :class:`pints.ErrorMeasure` to plot. point_1 The first point in parameter space. The method will find a line from ``point_1`` to ``point_2`` and plot ``f`` at several points along it. point_2 The second point. padding Specifies the amount of padding around the line segment ``[point_1, point_2]`` that will be shown in the plot. evaluations The number of evaluation along the line in parameter space. """ import matplotlib.pyplot as plt import numpy as np import pints # Check function and get n_parameters if not (isinstance(f, pints.LogPDF) or isinstance(f, pints.ErrorMeasure)): raise ValueError( 'Given function must be pints.LogPDF or pints.ErrorMeasure.') n_param = f.n_parameters() # Check points point_1 = pints.vector(point_1) point_2 = pints.vector(point_2) if not (len(point_1) == len(point_2) == n_param): raise ValueError('Both points must have the same number of parameters' + ' as the given function.') # Check padding padding = float(padding) if padding < 0: raise ValueError('Padding cannot be negative.') # Check evaluation evaluations = int(evaluations) if evaluations < 3: raise ValueError('The number of evaluations must be 3 or greater.') # Figure setting fig, axes = plt.subplots(1, 1, figsize=(6, 4)) axes.set_xlabel('Point 1 to point 2') axes.set_ylabel('Function') # Generate some x-values near the given parameters s = np.linspace(-padding, 1 + padding, evaluations) # Direction r = point_2 - point_1 # Calculate function with other parameters fixed x = [point_1 + sj * r for sj in s] y = pints.evaluate(f, x, parallel=False) # Plot axes.plot(s, y, color='green') axes.axvline(0, color='#1f77b4', label='Point 1') axes.axvline(1, color='#7f7f7f', label='Point 2') axes.legend() return fig, axes
def to_model(self, q): """ See :meth:`Transformation.to_model()`. """ q = pints.vector(q) return np.exp(q)
def log_jacobian_det(self, q): """ See :meth:`Transformation.log_jacobian_det()`. """ q = pints.vector(q) return np.sum(q)
def log_jacobian_det_S1(self, q): """ See :meth:`Transformation.log_jacobian_det_S1()`. """ q = pints.vector(q) logjacdet = self.log_jacobian_det(q) dlogjacdet = np.ones(self._n_parameters) return logjacdet, dlogjacdet
boundaries, method=pints.CMAES) sampler = pints.AdaptiveCovarianceMCMC(found_parameters) samplers.append(sampler) pickle.dump((samplers, log_posteriors), open(pickle_file, 'wb')) else: samplers, log_posteriors = pickle.load(open(pickle_file, 'rb')) print('using starting points:') for i, (sampler, log_posterior) in enumerate(zip(samplers, log_posteriors)): print('\t', sampler._x0) sampled_true_parameters[:, i] = sampler._x0[:-1] if not use_cmaes: sampler._x0 = pints.vector(x0) plt.clf() times = log_posterior._log_likelihood._problem._times values = log_posterior._log_likelihood._problem._values sim_values = log_posterior._log_likelihood._problem.evaluate( sampled_true_parameters[:, i]) E_0, T_0, L_0, I_0 = model._calculate_characteristic_values() plt.plot(T_0 * times, I_0 * values * 1e6, label='experiment') plt.plot(T_0 * times, I_0 * sim_values * 1e6, label='simulation') plt.xlabel(r'$t$ (s)') plt.ylabel(r'$I_{tot}$ ($\mu A$)') plt.legend() filename = filenames[i] print('plotting fit for ', filename) plt.savefig('fit%s.pdf' % filename)
def to_model(self, q): """ See :meth:`Transformation.to_model()`. """ return pints.vector(q)
def __init__(self, bad_value=float('inf')): super(BadMiniProblem, self).__init__() self._v = pints.vector([bad_value, 2, -3])
def __init__(self): super(BigMiniProblem, self).__init__() self._t = pints.vector([1, 2, 3, 4, 5, 6]) self._v = pints.vector([-1, 2, 3, 4, 5, -6])
def __init__(self): self._t = pints.vector([1, 2, 3]) self._v = pints.matrix2d( np.array([[-1, 2, 3], [-1, 2, 3]]).swapaxes(0, 1))
def x_best(self): """ See: :meth:`pints.Optimiser.x_best()`. """ return pints.vector(self._xs[0] if self._running else self._x0)
def jacobian(self, q): """ See :meth:`Transformation.jacobian()`. """ q = pints.vector(q) diag = (self._b - self._a) / (np.exp(q) * (1. + np.exp(-q))**2) return np.diag(diag)
def tell(self, reply): """ See :meth:`pints.SingleChainMCMC.tell()`. """ # Check ask/tell pattern if not self._ready_for_tell: raise RuntimeError('Tell called before proposal was set.') self._ready_for_tell = False # Unpack reply fx, grad = reply # Check reply, copy gradient fx = float(fx) grad = pints.vector(grad) assert grad.shape == (self._n_parameters, ) # Very first call if self._current is None: # Check first point is somewhere sensible if not np.isfinite(fx): raise ValueError( 'Initial point for MCMC must have finite logpdf.') # Set current sample, log pdf of current sample and initialise # proposed sample for next iteration self._current = np.array(self._x0, copy=True) self._proposed = np.array(self._current, copy=True) # Sample height of the slice log_y for next iteration e = np.random.exponential(1) self._current_log_y = fx - e # Return first point in chain, which is x0 # Note: `grad` is not stored in this iteration, so can return return np.copy(self._current), (fx, grad), True # Acceptance check if fx >= self._current_log_y: # The accepted sample becomes the new current sample self._current = np.copy(self._proposed) # Sample new log_y used to define the next slice e = np.random.exponential(1) self._current_log_y = fx - e # Reset parameters self._J = np.zeros((self._n_parameters, 0), float) self._k = 0 self._c_bar = 0 # Return accepted sample # Note: `grad` is not stored in this iteration, so can return return np.copy(self._current), (fx, grad), True # If proposal is reject, shrink rank of the next proposal distribution # by adding new orthonormal column to ``J``. This will represent a new # direction in which the conditional covariance of the proposal # distribution will be 0. else: # If J has less non-zero columns than``number of # parameters - 1``, shrink rank by adding new orthonormal # column if self._J.shape[1] < self._n_parameters - 1: # Gradient projection g_star = self._p(self._J, grad) # To prevent meaningless adaptations, we only perform this # operation when the angle between the gradient and its # projection into the nullspace of J is less than 60 degrees. if np.dot(g_star.transpose(), grad) > ( .5 * np.linalg.norm(g_star) * np.linalg.norm(grad)): new_column = np.array(g_star / np.linalg.norm(g_star)) self._J = np.column_stack([self._J, new_column]) return None
def log_jacobian_det(self, q): """ See :meth:`Transformation.log_jacobian_det()`. """ q = pints.vector(q) s = self._softplus(-q) return np.sum(np.log(self._b - self._a) - 2. * s - q)
def jacobian(self, q): """ See :meth:`Transformation.jacobian()`. """ q = pints.vector(q) return np.diag(np.exp(q))
def log_jacobian_det_S1(self, q): """ See :meth:`Transformation.log_jacobian_det_S1()`. """ q = pints.vector(q) logjacdet = self.log_jacobian_det(q) dlogjacdet = 2. * np.exp(-q) * expit(q) - 1. return logjacdet, dlogjacdet
def function(f, x, lower=None, upper=None, evaluations=20): """ Creates 1d plots of a :class:`LogPDF` or a :class:`ErrorMeasure` around a point `x` (i.e. a 1-dimensional plot in each direction). Returns a ``matplotlib`` figure object and axes handle. Parameters ---------- f A :class:`pints.LogPDF` or :class:`pints.ErrorMeasure` to plot. x A point in the function's input space. lower Optional lower bounds for each parameter, used to specify the lower bounds of the plot. upper Optional upper bounds for each parameter, used to specify the upper bounds of the plot. evaluations The number of evaluations to use in each plot. """ import matplotlib.pyplot as plt # Check function and get n_parameters if not (isinstance(f, pints.LogPDF) or isinstance(f, pints.ErrorMeasure)): raise ValueError( 'Given function must be pints.LogPDF or pints.ErrorMeasure.') n_param = f.n_parameters() # Check point x = pints.vector(x) if len(x) != n_param: raise ValueError( 'Given point `x` must have same number of parameters as function.') # Check boundaries if lower is None: # Guess boundaries based on point x lower = x * 0.95 lower[lower == 0] = -1 else: lower = pints.vector(lower) if len(lower) != n_param: raise ValueError('Lower bounds must have same number of' + ' parameters as function.') if upper is None: # Guess boundaries based on point x upper = x * 1.05 upper[upper == 0] = 1 else: upper = pints.vector(upper) if len(upper) != n_param: raise ValueError('Upper bounds must have same number of' + ' parameters as function.') # Check number of evaluations evaluations = int(evaluations) if evaluations < 1: raise ValueError('Number of evaluations must be greater than zero.') # Create points to plot xs = np.tile(x, (n_param * evaluations, 1)) for j in range(n_param): i1 = j * evaluations i2 = i1 + evaluations xs[i1:i2, j] = np.linspace(lower[j], upper[j], evaluations) # Evaluate points fs = pints.evaluate(f, xs, parallel=False) # Create figure fig, axes = plt.subplots(n_param, 1, figsize=(6, 2 * n_param)) if n_param == 1: axes = np.asarray([axes], dtype=object) for j, p in enumerate(x): i1 = j * evaluations i2 = i1 + evaluations axes[j].plot(xs[i1:i2, j], fs[i1:i2], c='green', label='Function') axes[j].axvline(p, c='blue', label='Value') axes[j].set_xlabel('Parameter ' + str(1 + j)) axes[j].legend() plt.tight_layout() return fig, axes
def to_model(self, q): """ See :meth:`Transformation.to_model()`. """ q = pints.vector(q) return (self._b - self._a) * expit(q) + self._a
def xbest(self): """ See: :meth:`pints.Optimiser.xbest()`. """ if not self._running: return pints.vector(self._x0) return pints.vector(self._xs[0])
def to_search(self, p): p = pints.vector(p) """ See :meth:`Transformation.to_search()`. """ return np.log(p - self._a) - np.log(self._b - p)
def set_initial_position(self, x0=None, sigma0=None): """ Updates the initial position and standard deviation used by this optimiser. Arguments: ``x0=None`` An optional starting point for searches in the parameter space. This value may be used directly (for example as the initial position of a particle in :class:`PSO`) or indirectly (for example as the center of a distribution in :class:`XNES`). ``sigma0=None`` An optional initial standard deviation around ``x0``. Can be specified either as a scalar value (one standard deviation for all coordinates) or as an array with one entry per dimension. Not all methods will use this information. """ # Check initial solution if x0 is None: # Use value in middle of search space if self._boundaries is None: self._x0 = np.zeros(self._dimension) else: self._x0 = self._boundaries.center() self._x0.setflags(write=False) else: # Check given value self._x0 = pints.vector(x0) if len(self._x0) != self._dimension: raise ValueError( 'Initial position must have same dimension as function.') if self._boundaries is not None: if not self._boundaries.check(self._x0): raise ValueError( 'Initial position must lie within given boundaries.') # Check initial standard deviation if sigma0 is None: if self._boundaries: # Use boundaries to guess self._sigma0 = (1 / 6.0) * self._boundaries.range() else: # Use initial position to guess at parameter scaling self._sigma0 = (1 / 3.0) * np.abs(self._x0) # But add 1 for any initial value that's zero self._sigma0 += (self._sigma0 == 0) self._sigma0.setflags(write=False) elif np.isscalar(sigma0): # Single number given, convert to vector sigma0 = float(sigma0) if sigma0 <= 0: raise ValueError( 'Initial standard deviation must be greater than zero.') self._sigma0 = np.ones(self._dimension) * sigma0 self._sigma0.setflags(write=False) else: # Vector given self._sigma0 = pints.vector(sigma0) if len(self._sigma0) != self._dimension: raise ValueError( 'Initial standard deviation must be None, scalar, or have' ' same dimension as function.') if np.any(self._sigma0 <= 0): raise ValueError( 'Initial standard deviations must be greater than zero.')
def __init__(self, scalings): self.s = pints.vector(scalings) self.inv_s = 1. / self.s self._n_parameters = len(self.s)
def __init__( self, function, x0, sigma0=None, boundaries=None, transformation=None, method=None): # Convert x0 to vector # This converts e.g. (1, 7) shapes to (7, ), giving users a bit more # freedom with the exact shape passed in. For example, to allow the # output of LogPrior.sample(1) to be passed in. x0 = pints.vector(x0) # Check dimension of x0 against function if function.n_parameters() != len(x0): raise ValueError( 'Starting point must have same dimension as function to' ' optimise.') # Check if minimising or maximising self._minimising = not isinstance(function, pints.LogPDF) # Apply a transformation (if given). From this point onward the # optimiser will see only the transformed search space and will know # nothing about the model parameter space. if transformation is not None: # Convert error measure or log pdf if self._minimising: function = transformation.convert_error_measure(function) else: function = transformation.convert_log_pdf(function) # Convert initial position x0 = transformation.to_search(x0) # Convert sigma0, if provided if sigma0 is not None: sigma0 = transformation.convert_standard_deviation(sigma0, x0) if boundaries: boundaries = transformation.convert_boundaries(boundaries) # Store transformation for later detransformation: if using a # transformation, any parameters logged to the filesystem or printed to # screen should be detransformed first! self._transformation = transformation # Store function if self._minimising: self._function = function else: self._function = pints.ProbabilityBasedError(function) del function # Create optimiser if method is None: method = pints.CMAES elif not issubclass(method, pints.Optimiser): raise ValueError('Method must be subclass of pints.Optimiser.') self._optimiser = method(x0, sigma0, boundaries) # Check if sensitivities are required self._needs_sensitivities = self._optimiser.needs_sensitivities() # Track optimiser's f_best or f_guessed self._use_f_guessed = None self.set_f_guessed_tracking() # Logging self._log_to_screen = True self._log_filename = None self._log_csv = False self.set_log_interval() # Parallelisation self._parallel = False self._n_workers = 1 self.set_parallel() # User callback self._callback = None # :meth:`run` can only be called once self._has_run = False # # Stopping criteria # # Maximum iterations self._max_iterations = None self.set_max_iterations() # Maximum unchanged iterations self._unchanged_max_iterations = None # n_iter w/o change until stop self._unchanged_threshold = 1 # smallest significant f change self.set_max_unchanged_iterations() # Maximum evaluations self._max_evaluations = None # Threshold value self._threshold = None # Post-run statistics self._evaluations = None self._iterations = None self._time = None
def to_search(self, p): """ See :meth:`Transformation.to_search()`. """ p = pints.vector(p) return self.s * p
def __init__(self, function, x0, sigma0=None, boundaries=None, method=None): # Convert x0 to vector # This converts e.g. (1, 7) shapes to (7, ), giving users a bit more # freedom with the exact shape passed in. For example, to allow the # output of LogPrior.sample(1) to be passed in. x0 = pints.vector(x0) # Check dimension of x0 against function if function.n_parameters() != len(x0): raise ValueError( 'Starting point must have same dimension as function to' ' optimise.') # Check if minimising or maximising self._minimising = not isinstance(function, pints.LogPDF) # Store function if self._minimising: self._function = function else: self._function = pints.ProbabilityBasedError(function) del (function) # Create optimiser if method is None: method = pints.CMAES elif not issubclass(method, pints.Optimiser): raise ValueError('Method must be subclass of pints.Optimiser.') self._optimiser = method(x0, sigma0, boundaries) # Check if sensitivities are required self._needs_sensitivities = self._optimiser.needs_sensitivities() # Logging self._log_to_screen = True self._log_filename = None self._log_csv = False self.set_log_interval() # Parallelisation self._parallel = False self._n_workers = 1 self.set_parallel() # # Stopping criteria # # Maximum iterations self._max_iterations = None self.set_max_iterations() # Maximum unchanged iterations self._max_unchanged_iterations = None self._min_significant_change = 1 self.set_max_unchanged_iterations() # Threshold value self._threshold = None # Post-run statistics self._evaluations = None self._iterations = None self._time = None
def __init__(self, c=[0, 0]): self._c = pints.vector(c) self._n = len(self._c)