def update_discretisation_scheme(self, signal, period): """ Given an existing set of interior knots, re-optimize the knots to the new optimal value, for the given signal. signal : 2-by-n float array Signal to discretise. signal[0] is the independent (time-like) variable. signal[1] is the dependent (voltage / position / etc.)-like variable. period : float > 0 Time taken for the signal to complete a single oscillation. Stores the result of an optimization procedure over the knot sets, to minimize fitting error, starting from the current knots. """ def loss(interior_knots): # Training error under current knots spline = splines.PeriodicSpline(np.sort(interior_knots)) coeffs = spline.fit(signal[0], signal[1], period) residuals = signal[1] - spline(signal[0], coeffs, period) return np.linalg.norm(residuals)**2 bounds = scipy.optimize.Bounds(0, 1) opti = scipy.optimize.minimize(loss, self.mesh, bounds=bounds) self.mesh = np.sort(opti.x) self._spline = splines.PeriodicSpline(self.mesh) print("Proposed knots: \n", self.mesh) print(opti, "\n") return self.mesh
def _initialise_discretisation_scheme(self, signal, period, n_tries=10): """ Given some periodic data, find the set of interior knots that provide a best-possible periodic splines model to the data (in the least-squares sense). This is done by starting with a randomly distributed set of knots, then attempting a numerical optimization on the knot set, to maximise goodness-of-fit. To avoid local minima, this procedure is repeated numerous times, with the best knots being recorded. signal : 2-by-n float array Signal to discretise. signal[0] is the independent (time-like) variable. signal[1] is the dependent (voltage / position / etc.)-like variable. period : float > 0 Time taken for the signal to complete a single oscillation. n_tries : int > 0 Number of times to restart the optimisation, to avoid local minima. More is better, but slower. Returns an interior knot vector that produces the lowest-residual splines fit to the provided data. """ def loss(interior_knots): # Training error under current knots spline = splines.PeriodicSpline(np.sort(interior_knots)) coeffs = spline.fit(signal[0], signal[1], period) residuals = signal[1] - spline(signal[0], coeffs, period) return np.linalg.norm(residuals)**2 # Uniform distribution for initial knots initial_knots = scipy.stats.uniform().rvs((n_tries, self.dsize)) # Keep re-optimizing; store best result best_loss = np.inf best_knots = None bounds = scipy.optimize.Bounds(0, 1) for k in initial_knots: opti = scipy.optimize.minimize(loss, k, bounds=bounds) print("Proposed knots: \n", np.sort(opti.x)) print(opti, "\n") if opti.fun < best_loss: best_loss = opti.fun best_knots = opti.x self.mesh = np.sort(best_knots) self._spline = splines.PeriodicSpline(self.mesh) return self.mesh
def __init__(self, mesh_size, order=3): """ BSpline collocation doesn't divide the data into subintervals. Instead, it maintains a single subinterval over the entire BVP domain [0,1]. The subinterval mesh is taken as a knot list for the spline functions. mesh_size therefore specifies the number of BSpline basis functions, and order specifies the order of the splines, similarly to with polynomial collocation. Note that BSpline collocation is only implemented for third-order (cubic) BSplines, so choosing an order!=3 will raise a NotImplementedError. Also, for third-order periodic splines, we require at least 4 BSpline functions, so choosing mesh_size<4 will raise a ValueError. """ if mesh_size < 4: raise ValueError("Must have at least 4 cubic BSpline functions") if order != 3: raise NotImplementedError( "BSpline collocation is only implemented for third-order BSplines" ) super().__init__(1, mesh_size) self.splines = splines.PeriodicSpline(self.full_mesh[1:-1])
def loss(interior_knots): # Training error under current knots spline = splines.PeriodicSpline(np.sort(interior_knots)) coeffs = spline.fit(signal[0], signal[1], period) residuals = signal[1] - spline(signal[0], coeffs, period) return np.linalg.norm(residuals)**2
def __init__(self, dsize): self.dsize = dsize interior_knots = np.linspace(0, 1, dsize + 2)[1:-1] self._spline = splines.PeriodicSpline(interior_knots)