docs: def optimise(self, data, initial_theta=None, full_output=False, **kwargs):
          """
          Optimise the model parameters, given the data.
          """
  
          data = self._format_data(data)
  
          if initial_theta is None:
              initial_theta = self.estimate(data)
  
          # Which parameters will be optimised, and which will be fixed?
          matched_channels, missing_channels, ignore_parameters \
              = self._match_channels_to_data(data)
          #parameters = set(self.parameters).difference(ignore_parameters)
          parameters = [p for p in self.parameters if p not in ignore_parameters]
  
          # What model wavelength ranges will be required?
          wavelengths_required = []
          for channel, spectrum in zip(matched_channels, data):
              if channel is None: continue
              z = initial_theta.get("z",
                  initial_theta.get("z_{}".format(channel), 0))
              wavelengths_required.append(
                  [spectrum.disp[0] * (1 - z), spectrum.disp[-1] * (1 - z)])
  
          # Create the spectrum approximator/interpolator.
          closest_point = [initial_theta[p] for p in self.grid_points.dtype.names]
          subset_bounds = self._initialise_approximator(
              closest_point=closest_point, 
              wavelengths_required=wavelengths_required, **kwargs)
          
          # Get the optimisation keyword arguments.
          op_kwargs = self._configuration.get("optimise", {}).copy()
          op_kwargs.update(kwargs)
  
          # Get fixed keywords.
          fixed = op_kwargs.pop("fixed", {})
          if fixed:
              # Remove non-parameters from the 'fixed' keywords.
              keys = set(fixed).intersection(parameters)
              # If the 'fixed' value is provided, use that. Otherwise if it is
              # None then use the initial_theta value.
              fixed = dict(zip(keys, 
                  [(fixed[k], initial_theta.get(k, None))[fixed[k] is None] \
                      for k in keys]))
  
              logger.info("Fixing keyword arguments (these will not be optimised)"
                  ": {}".format(fixed))
  
          # Remove fixed parameters from the parameters to be optimised
          #parameters = list(set(parameters).difference(fixed))
          parameters = [p for p in parameters if p not in fixed]
  
          # Translate input bounds.
          nbs = (None, None) # No boundaries.
          input_bounds = op_kwargs.pop("bounds", {})
          op_kwargs["bounds"] = [input_bounds.get(p, subset_bounds.get(p, nbs)) \
              for p in parameters]
          
          # Apply data masks now so we don't have to do it on the fly.
          masked_data, pixels_affected = self._apply_data_mask(data)
  
          # Prepare the convolution functions.
          self._create_convolution_functions(matched_channels, data, parameters,
              fixed_parameters=fixed)
  
          logger.info("Optimising parameters: {0}".format(", ".join(parameters)))
          logger.info("Optimisation keywords: {0}".format(op_kwargs))
  
          # Create the objective function.
          debug = kwargs.get("debug", False)
          def nlp(theta):
              # Apply fixed keywords
              t_ = theta.copy()
              p_ = [] + parameters
              for parameter, value in fixed.iteritems():
                  p_.append(parameter)
                  t_ = np.append(theta, value)
  
              return -inference.ln_probability(t_, p_, self, data, debug,
                  matched_channels=matched_channels)
  
          # Do the optimisation.
          p0 = np.array([initial_theta[p] for p in parameters])
  
          x_opt = op.minimise(nlp, p0, **op_kwargs)
  
          # Put the result into a usable form.
          x_opt_theta = OrderedDict(zip(parameters, x_opt))
          x_opt_theta.update(fixed)
  
          if full_output:
              # Create model fluxes and calculate some metric.
              chi_sq, dof, model_fluxes = self._chi_sq(x_opt_theta, data)
  
  
              # Remove any prepared convolution functions.
              self._destroy_convolution_functions()
      
              return (x_opt_theta, chi_sq, dof, model_fluxes)
  
          # Remove any prepared convolution functions.
          self._destroy_convolution_functions()
  
          return x_opt_theta