Cast a new model from this model. The new model might have a restricted
        wavelength range, different channels, larger sampling/binning, and/or a
        lower spectral resolution.

def cast(self, new_model_name, new_channels, output_dir=None, clobber=False,
        **kwargs):
        """
        Cast a new model from this model. The new model might have a restricted
        wavelength range, different channels, larger sampling/binning, and/or a
        lower spectral resolution.
        """

        output_dir = output_dir if output_dir is not None else os.getcwd()
        output_prefix = os.path.join(output_dir, new_model_name)

        # Validation.
        if not isinstance(new_channels, dict):
            raise TypeError("channels must be a dictionary object")

        for name, descr in new_channels.iteritems():
            if not isinstance(descr, (tuple, list, np.ndarray)) \
            or len(descr) != 2:
                raise TypeError("channel dictionary values must be a "\
                    "(wavelength_array, spectral_resolution)")

            new_wavelengths, spectral_resolution = descr
            if not isinstance(new_wavelengths, (list, np.ndarray)):
                raise TypeError("wavelengths for the new channel {} must be an "
                    "array".format(name))
            if 2 > len(new_wavelengths):
                raise ValueError("wavelength array for the new channel {0} only"
                    " contains {1} points".format(name, len(new_wavelengths)))
            if 0 > spectral_resolution:
                raise ValueError("spectral resolution for the new channel {} "
                    "must be a positive value".format(name))

        # Clobber.
        if not clobber:
            # Check to make sure the output files won't exist already.
            output_suffixes = (".yaml", ".pkl", "-wavelengths.memmap",
                "-intensities.memmap")
            for path in [output_prefix + suffix for suffix in output_suffixes]:
                if os.path.exists(path):
                    raise IOError("output filename {} already exists"\
                        .format(path))

        # Sort the proposed channel names from blue to red wavelengths.
        channel_names = []
        min_wavelengths = []
        for name, (wavelengths, resolution) in new_channels.iteritems():
            channel_names.append(name)
            min_wavelengths.append(np.min(wavelengths))

        channel_names = np.array(channel_names)[np.argsort(min_wavelengths)]

        # Get the wavelengths.
        channel_wavelengths = []
        wavelength_range = [self.wavelengths.min(), self.wavelengths.max()]
        for name in channel_names:

            new_wavelength = np.sort(new_channels[name][0])
            i = np.searchsorted(new_wavelength, wavelength_range)
            channel_wavelengths.append(new_wavelength[i[0]:i[1]])

            if i[0] != 0 or i[1] != len(new_wavelength):
                logger.warn("Clipping requested wavelength range in channel {0}"
                    " from {1:.0f}-{2:.0f} A to {3:.0f}-{4:.0f} A because the "
                    "region requested was not covered by the original model."\
                    .format(name, new_wavelength[0], new_wavelength[-1],
                        new_wavelength[i[0]], new_wavelength[i[1]]))

        channel_wavelengths = [new_channels[n][0] for n in channel_names]
        channel_sizes = [len(_) for _ in channel_wavelengths]
        num_pixels = sum(channel_sizes)

        # Find out which of the new channels overlap with the existing channels.
        channel_matches = {}
        for name, wavelengths in zip(channel_names, channel_wavelengths):

            overlapping_old_channels = self._overlapping_channels(wavelengths)

            # if there are no overlaps, do we have a problem?
            if len(overlapping_old_channels) == 0:
                raise ValueError("no channels overlapping")

            elif len(overlapping_old_channels) > 1:
                raise ValueError("too many channels overlapping!")

            channel_matches[name] = overlapping_old_channels[0]

        # Create the memory-mapped wavelength file.
        wavelengths_memmap = np.memmap(output_prefix + "-wavelengths.memmap",
            dtype="float32", mode="w+", shape=(num_pixels, ))
        wavelengths_memmap[:] = np.hstack(channel_wavelengths)
        wavelengths_memmap.flush()
        del wavelengths_memmap

        # Write the new configuration to file.
        with open(output_prefix + ".yaml", "w") as fp:
            header = "\n".join([
                "# Model created on {0} from previous model with hash {1}"\
                    .format(strftime("%Y-%m-%d %H:%M:%S"), self.hash),
                "# Grid parameters: {0}".format(
                    ", ".join(self.grid_points.dtype.names)),
                "# Channel names: {0}".format(
                    ", ".join(channel_names))
                ])
            configuration = self._configuration.copy()
            configuration.update({
                "model_grid": {
                    "grid_points": output_prefix + ".pkl",
                    "wavelengths": output_prefix + "-wavelengths.memmap",
                    "intensities": output_prefix + "-intensities.memmap"
                }
            })
            fp.write(header + "\n" + yaml.safe_dump(configuration, stream=None, 
                allow_unicode=True, default_flow_style=False))

        # Create the pickled model file, with meta data.
        meta = self.meta.copy()
        meta.update({
            "channel_names": channel_names,
            "channel_sizes": channel_sizes,
            "channel_resolutions": [new_channels[n][1] for n in channel_names]
        })
        with open(output_prefix + ".pkl", "wb") as fp:
            pickle.dump((self.grid_points, meta), fp, -1)
            
        # Create the rebinning matrices.
        convolutions = []
        wavelength_indices = []
        fast_binning \
            = self._configuration.get("settings", {}).get("fast_binning", False)
        for name in channel_names:
            new_wavelengths, spectral_resolution = new_channels[name]

            indices = np.clip(self.wavelengths.searchsorted(
                [new_wavelengths.min(), new_wavelengths.max()]) + [0, 1],
                0, self.wavelengths.size - 1)
            wavelength_indices.append(indices)
            old_wavelengths = self.wavelengths[indices[0]:indices[1]]
            logger.debug("Casting {0} channel from [{1:.0f}, {2:.0f}] to "\
                "[{3:.0f}, {4:.0f}]".format(name,
                    old_wavelengths[0], old_wavelengths[-1],
                    new_wavelengths[0], new_wavelengths[-1]))

            if fast_binning:
                if spectral_resolution is None \
                or not np.isfinite(spectral_resolution):
                    convolutions.append(lambda nw, ow, of: \
                        np.interp(nw, ow, of, left=np.nan, right=np.nan))

                else:
                    print("OK")
                    logger.debug("Using fast binning with spectral resolution")
                    R_scale = 2.3548200450309493 * new_wavelengths.mean()**2/ np.diff(new_wavelengths).mean()
                    convolutions.append(lambda nw, ow, of: \
                        np.interp(nw, ow,
                            gaussian_filter1d(of, R_scale/spectral_resolution**2),
                            left=np.nan, right=np.nan))

            else:
                if spectral_resolution is None \
                or not np.isfinite(spectral_resolution):
                    convolutions.append(lambda *_: _[2] * specutils.sample.resample(
                        old_wavelengths, new_wavelengths))
                else:
                    convolutions.append(lambda *_: _[2] * \
                        specutils.sample.resample_and_convolve(old_wavelengths,
                            new_wavelengths, new_resolution=spectral_resolution))
            
        # Load the intensities.
        intensities = np.memmap(
            self._configuration["model_grid"]["intensities"],
            shape=(self.grid_points.size, self.wavelengths.size),
            mode="r", dtype="float32")

        # Create a new intensities grid.
        cast_intensities = np.memmap(
            output_prefix + "-intensities.memmap",
            shape=(self.grid_points.size, num_pixels),
            mode="w+", dtype="float32")

        n = self.grid_points.size
        increment = int(n / 100)
        progressbar = kwargs.pop("__progressbar", False)
        if progressbar:
            print("Casting {} model:".format(new_model_name))

        for i in xrange(n):
            if progressbar:
                if (i % increment == 0):
                    sys.stdout.write("\r[{done}{not_done}] "
                        "{percent:3.0f}%".format(done="=" * int(i / increment),
                        not_done=" " * int((n - i)/ increment),
                        percent=100. * i/n))
                    sys.stdout.flush()
            else:
                logger.debug("Recasting point {0}/{1}".format(i, n))

            for j, (size, convolution, indices) \
            in enumerate(zip(channel_sizes, convolutions, wavelength_indices)):

                new_wavelengths, _ = new_channels[channel_names[j]]
                old_wl_idxs = np.clip(self.wavelengths.searchsorted(
                    [new_wavelengths.min(), new_wavelengths.max()]) + [0, 1],
                    0, self.wavelengths.size - 1)
                old_wavelengths = self.wavelengths[old_wl_idxs[0]:old_wl_idxs[1]]

                old_fluxes = np.copy(intensities[i, indices[0]:indices[1]])

                idx = sum(channel_sizes[:j])
                cast_intensities[i, idx:idx + size] = convolution(new_wavelengths, old_wavelengths, old_fluxes)

        """
        for j, (size, matrix, indices) \
        in enumerate(zip(channel_sizes, convolutions, wavelength_indices)):
            idx = sum(channel_sizes[:j])
            cast_intensities[:, idx:idx + size] = \
                intensities[:, indices[0]:indices[1]] * matrix
        """

        cast_intensities.flush()
        del cast_intensities
        if progressbar:
            print("\r")

        return True