text:
"""sedmodel.py - classes and methods for storing parameters and predicting
observed spectra and photometry from them, given a Source object.
"""

import numpy as np
import os

from numpy.polynomial.chebyshev import chebval, chebvander
from scipy.interpolate import splrep, BSpline
from scipy.stats import multivariate_normal as mvn

from sedpy.observate import getSED

from .parameters import ProspectorParams
from ..sources.constants import to_cgs_at_10pc as to_cgs
from ..sources.constants import cosmo, lightspeed, ckms, jansky_cgs
from ..utils.smoothing import smoothspec

__all__ = ["SpecModel", "PolySpecModel", "SplineSpecModel",
           "LineSpecModel", "AGNSpecModel",
           "SedModel", "PolySedModel", "PolyFitModel"]

class SpecModel(ProspectorParams):

    """A subclass of :py:class:`ProspectorParams` that passes the models
    through to an ``sps`` object and returns spectra and photometry, including
    optional spectroscopic calibration, and sky emission.

    This class performs most of the conversion from intrinsic model spectrum to
    observed quantities, and additionally can compute MAP emission line values
    and penalties for marginalization over emission line amplitudes.
    """

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)
        self.init_eline_info()

    def _available_parameters(self):
        new_pars = [("sigma_smooth", ""),
                    ("marginalize_elines", ""),
                    ("elines_to_fit", ""),
                    ("elines_to_fix", ""),
                    ("elines_to_ignore", ""),
                    ("eline_delta_zred", ""),
                    ("eline_sigma", ""),
                    ("use_eline_priors", ""),
                    ("eline_prior_width", "")]
        relevant_pars = [("mass", ""),
                         ("lumdist", ""),
                         ("zred", ""),
                         ("nebemlineinspec", ""),
                         ("add_neb_emission")]

        return new_pars

    def predict(self, theta, obs=None, sps=None, sigma_spec=None, **extras):
        """Given a ``theta`` vector, generate a spectrum, photometry, and any
        extras (e.g. stellar mass), including any calibration effects.

        :param theta:
            ndarray of parameter values, of shape ``(ndim,)``

        :param obs:
            An observation dictionary, containing the output wavelength array,
            the photometric filter lists, and the observed fluxes and
            uncertainties thereon.  Assumed to be the result of
            :py:func:`utils.obsutils.rectify_obs`

        :param sps:
            An `sps` object to be used in the model generation.  It must have
            the :py:func:`get_galaxy_spectrum` method defined.

        :param sigma_spec: (optional)
            The covariance matrix for the spectral noise. It is only used for
            emission line marginalization.

        :returns spec:
            The model spectrum for these parameters, at the wavelengths
            specified by ``obs['wavelength']``, including multiplication by the
            calibration vector.  Units of maggies

        :returns phot:
            The model photometry for these parameters, for the filters
            specified in ``obs['filters']``.  Units of maggies.

        :returns extras:
            Any extra aspects of the model that are returned.  Typically this
            will be `mfrac` the ratio of the surviving stellar mass to the
            stellar mass formed.
        """
        # generate and cache model spectrum and info
        self.set_parameters(theta)
        self._wave, self._spec, self._mfrac = sps.get_galaxy_spectrum(**self.params)
        self._zred = self.params.get('zred', 0)
        self._eline_wave, self._eline_lum = sps.get_galaxy_elines()

        # Flux normalize
        self._norm_spec = self._spec * self.flux_norm()

        # generate spectrum and photometry for likelihood
        # predict_spec should be called before predict_phot
        # because in principle it can modify the emission line parameters
        # and also needs some things done in 'cache_eline_parameters`
        # especially _ewave_obs and _use_elines
        spec = self.predict_spec(obs, sigma_spec=sigma_spec)
        phot = self.predict_phot(obs.get('filters', None))

        return spec, phot, self._mfrac

    def predict_spec(self, obs, sigma_spec=None, **extras):
        """Generate a prediction for the observed spectrum.  This method assumes
        that the parameters have been set and that the following attributes are
        present and correct

          + ``_wave`` - The SPS restframe wavelength array
          + ``_zred`` - Redshift
          + ``_norm_spec`` - Observed frame spectral fluxes, in units of maggies
          + ``_eline_wave`` and ``_eline_lum`` - emission line parameters from the SPS model

        It generates the following attributes

          + ``_outwave`` - Wavelength grid (observed frame)
          + ``_speccal`` - Calibration vector
          + ``_sed`` - Intrinsic spectrum (before cilbration vector applied but including emission lines)

        And the following attributes are generated if nebular lines are added

          + ``_fix_eline_spec`` - emission line spectrum for fixed lines, intrinsic units
          + ``_fix_eline_spec`` - emission line spectrum for fitted lines, with
            spectroscopic calibration factor included.

        Numerous quantities related to the emission lines are also cached (see
        ``cache_eline_parameters()`` and ``fit_el()`` for details.)

        :param obs:
            An observation dictionary, containing the output wavelength array,
            the photometric filter lists, and the observed fluxes and
            uncertainties thereon.  Assumed to be the result of
            :py:meth:`utils.obsutils.rectify_obs`

        :param sigma_spec: (optional)
            The covariance matrix for the spectral noise. It is only used for
            emission line marginalization.

        :returns spec:
            The prediction for the observed frame spectral flux these
            parameters, at the wavelengths specified by ``obs['wavelength']``,
            including multiplication by the calibration vector.
            ndarray of shape ``(nwave,)`` in units of maggies.
        """
        # redshift wavelength
        obs_wave = self.observed_wave(self._wave, do_wavecal=False)
        self._outwave = obs.get('wavelength', obs_wave)
        if self._outwave is None:
            self._outwave = obs_wave

        # --- cache eline parameters ---
        self.cache_eline_parameters(obs)

        # --- smooth and put on output wavelength grid ---
        smooth_spec = self.smoothspec(obs_wave, self._norm_spec)

        # --- add fixed lines if necessary ---
        emask = self._fix_eline_pixelmask
        if emask.any():
            inds = self._fix_eline & self._valid_eline
            espec = self.predict_eline_spec(line_indices=inds,
                                            wave=self._outwave[emask])
            self._fix_eline_spec = espec
            smooth_spec[emask] += self._fix_eline_spec.sum(axis=1)

        # --- calibration ---
        self._speccal = self.spec_calibration(obs=obs, spec=smooth_spec, **extras)
        calibrated_spec = smooth_spec * self._speccal

        # --- fit and add lines if necessary ---
        emask = self._fit_eline_pixelmask
        if emask.any():
            self._fit_eline_spec = self.fit_el(obs, calibrated_spec, sigma_spec)
            calibrated_spec[emask] += self._fit_eline_spec.sum(axis=1)

        # --- cache intrinsic spectrum ---
        self._sed = calibrated_spec / self._speccal

        return calibrated_spec

    def predict_phot(self, filters):
        """Generate a prediction for the observed photometry.  This method assumes
        that the parameters have been set and that the following attributes are
        present and correct:
          + ``_wave`` - The SPS restframe wavelength array
          + ``_zred`` - Redshift
          + ``_norm_spec`` - Observed frame spectral fluxes, in units of maggies.
          + ``_ewave_obs`` and ``_eline_lum`` - emission line parameters from
            the SPS model

        :param filters:
            Instance of :py:class:`sedpy.observate.FilterSet` or list of
            :py:class:`sedpy.observate.Filter` objects. If there is no
            photometry, ``None`` should be supplied.

        :returns phot:
            Observed frame photometry of the model SED through the given filters.
            ndarray of shape ``(len(filters),)``, in units of maggies.
            If ``filters`` is None, this returns 0.0
        """
        if filters is None:
            return 0.0

        # generate photometry w/o emission lines
        obs_wave = self.observed_wave(self._wave, do_wavecal=False)
        flambda = self._norm_spec * lightspeed / obs_wave**2 * (3631*jansky_cgs)
        phot = 10**(-0.4 * np.atleast_1d(getSED(obs_wave, flambda, filters)))
        # TODO: below is faster for sedpy > 0.2.0
        #phot = np.atleast_1d(getSED(obs_wave, flambda, filters, linear_flux=True))

        # generate emission-line photometry
        if (self._want_lines & self._need_lines):
            phot += self.nebline_photometry(filters)

        return phot

    def flux_norm(self):
        """Compute the scaling required to go from Lsun/Hz/Msun to maggies.
        Note this includes the (1+z) factor required for flux densities.

        :returns norm: (float)
            The normalization factor, scalar float.
        """
        # distance factor
        if (self._zred == 0) | ('lumdist' in self.params):
            lumdist = self.params.get('lumdist', 1e-5)
        else:
            lumdist = cosmo.luminosity_distance(self._zred).to('Mpc').value
        dfactor = (lumdist * 1e5)**2
        # Mass normalization
        mass = np.sum(self.params.get('mass', 1.0))
        # units
        unit_conversion = to_cgs / (3631*jansky_cgs) * (1 + self._zred)

        return mass * unit_conversion / dfactor

    def init_eline_info(self, eline_file='emlines_info.dat'):

        # get the emission line info
        try:
            SPS_HOME = os.getenv('SPS_HOME')
            info = np.genfromtxt(os.path.join(SPS_HOME, 'data', eline_file),
                                 dtype=[('wave', 'f8'), ('name', '<U20')],
                                 delimiter=',')
            self.emline_info = info
            self._use_eline = np.ones(len(info), dtype=bool)
        except(OSError, KeyError, ValueError) as e:
            print("Could not read and cache emission line info from $SPS_HOME/data/emlines_info.dat")
            self.emline_info = e

    @property
    def _need_lines(self):
        return (not (bool(self.params.get("nebemlineinspec", True))))

    @property
    def _want_lines(self):
        return bool(self.params.get('add_neb_emission', False))

    def nebline_photometry(self, filters, elams=None, elums=None):
        """Compute the emission line contribution to photometry.  This requires
        several cached attributes:
          + ``_ewave_obs``
          + ``_eline_lum``

        :param filters:
            Instance of :py:class:`sedpy.observate.FilterSet` or list of
            :py:class:`sedpy.observate.Filter` objects

        :param elams: (optional)
            The emission line wavelength in angstroms.  If not supplied uses the
            cached ``_ewave_obs`` attribute.

        :param elums: (optional)
            The emission line flux in erg/s/cm^2.  If not supplied uses  the
            cached ``_eline_lum`` attribute and applies appropriate distance
            dimming and unit conversion.

        :returns nebflux:
            The flux of the emission line through the filters, in units of
            maggies. ndarray of shape ``(len(filters),)``
        """
        if (elams is None) or (elums is None):
            elams = self._ewave_obs[self._use_eline]
            # We have to remove the extra (1+z) since this is flux, not a flux density
            # Also we convert to cgs
            self.line_norm = self.flux_norm() / (1 + self._zred) * (3631*jansky_cgs)
            elums = self._eline_lum[self._use_eline] * self.line_norm

        # loop over filters
        flux = np.zeros(len(filters))
        try:
            # TODO: Since in this case filters are on a grid, there should be a
            # faster way to look up the transmission than the later loop
            flist = filters.filters
        except(AttributeError):
            flist = filters
        for i, filt in enumerate(flist):
            # calculate transmission at line wavelengths
            trans = np.interp(elams, filt.wavelength, filt.transmission,
                              left=0., right=0.)
            # include all lines where transmission is non-zero
            idx = (trans > 0)
            if True in idx:
                flux[i] = (trans[idx]*elams[idx]*elums[idx]).sum() / filt.ab_zero_counts

        return flux

    def cache_eline_parameters(self, obs, nsigma=5, forcelines=False):
        """ This computes and caches a number of quantities that are relevant
        for predicting the emission lines, and computing the MAP values thereof,
        including
          + ``_ewave_obs`` - Observed frame wavelengths (AA) of all emission lines.
          + ``_eline_sigma_kms`` - Dispersion (in km/s) of all the emission lines
          + ``_fit_eline`` - If fitting and marginalizing over emission lines,
            this stores a boolean mask of the lines to actually fit. Only lines
            that are within ``nsigma`` of an observed wavelength points are
            included.
          + ``_fix_eline`` - this stores a boolean mask of the lines that are
            to be added with the cloudy amplitudes Only lines that are within
            ``nsigma`` of an observed wavelength point are included.
          + ``_fit_eline_pixelmask`` - A mask of the `_outwave` vector that
            indicates which pixels to use in the emission line fitting.
            Only pixels within ``nsigma`` of an emission line are used.
          + ``_fix_eline_pixelmask`` - A mask of the `_outwave` vector that
            indicates which pixels to use in the fixed emission line prediction.

        Can be subclassed to add more sophistication
        redshift - first looks for ``eline_delta_zred``, and defaults to ``zred``
        sigma - first looks for ``eline_sigma``, defaults to 100 km/s

        :param nsigma: (float, optional, default: 5.)
            Number of sigma from a line center to use for defining which lines
            to fit and useful spectral elements for the fitting.  float.
        """

        # observed wavelengths
        eline_z = self.params.get("eline_delta_zred", 0.0)
        self._ewave_obs = (1 + eline_z + self._zred) * self._eline_wave

        # masks for lines to be treated in various ways.
        # always run this becuase it's need for spec *and* phot if adding lines
        # by hand
        self.parse_elines()

        # exit gracefully if not adding lines.  We also exit if only fitting
        # photometry, for performance reasons
        hasspec = obs.get('spectrum', None) is not None
        if not (self._want_lines & self._need_lines & hasspec):
            self._fit_eline = None
            self._fit_eline_pixelmask = np.array([], dtype=bool)
            self._fix_eline_pixelmask = np.array([], dtype=bool)
            self._fix_eline = None
            return

        # observed linewidths
        nline = self._ewave_obs.shape[0]
        self._eline_sigma_kms = np.atleast_1d(self.params.get('eline_sigma', 100.0))
        # what is this wierd construction for?
        self._eline_sigma_kms = (self._eline_sigma_kms[None] * np.ones(nline)).squeeze()
        #self._eline_sigma_lambda = eline_sigma_kms * self._ewave_obs / ckms

        # --- get valid lines ---
        # fixed and fit lines specified by user, but remove any lines which do
        # not have an observed pixel within 5sigma of their center
        # This part has to go in every call
        linewidth = nsigma * self._ewave_obs / ckms * self._eline_sigma_kms
        pixel_mask = (np.abs(self._outwave - self._ewave_obs[:, None]) < linewidth[:, None])
        self._valid_eline = pixel_mask.any(axis=1) & self._use_eline

        # --- wavelengths corresponding to valid lines ---
        # within N sigma of the central wavelength
        self._fit_eline_pixelmask = pixel_mask[self._valid_eline & self._fit_eline, :].any(axis=0)
        self._fix_eline_pixelmask = pixel_mask[self._valid_eline & self._fix_eline, :].any(axis=0)
        # --- lines to fit ---
        self._elines_to_fit = self._fit_eline & self._valid_eline

    def parse_elines(self):
        """Create mask arrays to identify the lines that should be fit and the
        lines that should be fixed based on the content of `params["elines_to_fix"]`
        and `params["elines_to_fit"]`

        This can probably be cached just once unless you want to change between
        separate fits.
        """

        all_lines = self.emline_info['name']

        if self.params.get('marginalize_elines', False):
            # if marginalizing, default to fitting all lines
            # unless some are explicitly fixed
            lnames_to_fit = self.params.get('elines_to_fit', all_lines)
            lnames_to_fix = self.params.get('elines_to_fix', np.array([]))
            self._fit_eline = np.isin(all_lines, lnames_to_fit) & ~np.isin(all_lines, lnames_to_fix)
        else:
            self._fit_eline = np.zeros(len(all_lines), dtype=bool)

        self._fix_eline = ~self._fit_eline

        if self.params.get("elines_to_ignore", []):
            self._use_eline = ~np.isin(self.emline_info["name"],
                                       self.params["elines_to_ignore"])

    def fit_el(self, obs, calibrated_spec, sigma_spec=None):
        """Compute the maximum likelihood and, optionally, MAP emission line
        amplitudes for lines that fall within the observed spectral range. Also
        compute and cache the analytic penalty to log-likelihood from
        marginalizing over the emission line amplitudes.  This is cached as
        ``_ln_eline_penalty``.  The emission line amplitudes (in maggies) at
        `_eline_lums` are updated to the ML values for the fitted lines.

        :param obs:
            A dictionary containing the ``'spectrum'`` and ``'unc'`` keys that
            are observed fluxes and uncertainties, both ndarrays of shape
            ``(n_wave,)``

        :param calibrated_spec:
            The predicted (so far) observer-frame spectrum in the same units as
            the observed spectrum, ndarray of shape ``(n_wave,)``  Should
            include pixed lines but not lines to be fit

        :param sigma_spec:
            Spectral covariance matrix, if using a non-trivial noise model.

        :returns el:
            The maximum likelihood emission line flux densities.
            ndarray of shape ``(n_wave_neb, n_fitted_lines)`` where
            ``n_wave_neb`` is the number of wavelength elements within
            ``nsigma`` of a line, and ``n_fitted_lines`` is the number of lines
            that fall within ``nsigma`` of a wavelength pixel.  Units are same
            as ``calibrated_spec``
        """
        # ensure we have no emission lines in spectrum
        # and we definitely want them.
        assert bool(self.params['nebemlineinspec']) is False
        assert bool(self.params['add_neb_emission']) is True

        # generate Gaussians on appropriate wavelength grid
        idx = self._elines_to_fit
        emask = self._fit_eline_pixelmask
        nebwave = self._outwave[emask]
        eline_gaussians = self.get_eline_gaussians(lineidx=idx, wave=nebwave)

        # generate residuals
        delta = obs['spectrum'][emask] - calibrated_spec[emask]

        # generate line amplitudes in observed flux units
        units_factor = self.flux_norm() / (1 + self._zred)
        calib_factor = np.interp(self._ewave_obs[idx], nebwave, self._speccal[emask])
        linecal = units_factor * calib_factor
        alpha_breve = self._eline_lum[idx] * linecal

        # FIXME: nebopt: be careful with inverses
        # generate inverse of sigma_spec
        if sigma_spec is None:
            sigma_spec = obs["unc"]**2
        sigma_spec = sigma_spec[emask]
        if sigma_spec.ndim == 2:
            sigma_inv = np.linalg.pinv(sigma_spec)
        else:
            sigma_inv = np.diag(1. / sigma_spec)

        # calculate ML emission line amplitudes and covariance matrix
        # FIXME: nebopt: do this with a solve
        sigma_alpha_hat = np.linalg.pinv(np.dot(eline_gaussians.T, np.dot(sigma_inv, eline_gaussians)))
        alpha_hat = np.dot(sigma_alpha_hat, np.dot(eline_gaussians.T, np.dot(sigma_inv, delta)))

        # generate likelihood penalty term (and MAP amplitudes)
        # FIXME: Cache line amplitude covariance matrices?
        if self.params.get('use_eline_prior', False):
            # Incorporate gaussian priors on the amplitudes
            sigma_alpha_breve = np.diag((self.params['eline_prior_width'] * np.abs(alpha_breve)))**2
            M = np.linalg.pinv(sigma_alpha_hat + sigma_alpha_breve)
            alpha_bar = (np.dot(sigma_alpha_breve, np.dot(M, alpha_hat)) +
                         np.dot(sigma_alpha_hat, np.dot(M, alpha_breve)))
            sigma_alpha_bar = np.dot(sigma_alpha_hat, np.dot(M, sigma_alpha_breve))
            # FIXME: nebopt: can we avoid a scipy call here
            K = ln_mvn(alpha_hat, mean=alpha_breve, cov=sigma_alpha_breve+sigma_alpha_hat) - \
                ln_mvn(alpha_hat, mean=alpha_hat, cov=sigma_alpha_hat)
        else:
            # simply use the ML values and associated marginaliztion penalty
            alpha_bar = alpha_hat
            K = ln_mvn(alpha_hat, mean=alpha_hat, cov=sigma_alpha_hat)

        # Cache the ln-penalty
        self._ln_eline_penalty = K

        # Store fitted emission line luminosities in physical units
        self._eline_lum[idx] = alpha_bar / linecal

        # return the maximum-likelihood line spectrum in observed units
        return alpha_hat * eline_gaussians

    def predict_eline_spec(self, line_indices=slice(None), wave=None):
        """Compute a complete model emission line spectrum. This should only
        be run after calling predict(), as it accesses cached information.
        Relatively slow, useful for display purposes

        :param line_indices: optional
            If given, this should give the indices of the lines to predict.

        :param wave: (optional, default: ``None``)
            The wavelength ndarray on which to compute the emission line spectrum.
            If not supplied, the ``_outwave`` vector is used.

        :returns eline_spec:
            An (n_line, n_wave) ndarray, units of Lsun/Hz intrinsic (no
            calibration vector applied)
        """
        gaussians = self.get_eline_gaussians(lineidx=line_indices, wave=wave)
        elums = self._eline_lum[line_indices] * self.flux_norm() / (1 + self._zred)
        return elums * gaussians

    def get_eline_gaussians(self, lineidx=slice(None), wave=None):
        """Generate a set of unit normals with centers and widths given by the
        previously cached emission line observed-frame wavelengths and emission
        line widths.

        :param lineidx: (optional)
            A boolean array or integer array used to subscript the cached
            lines.  Gaussian vectors will only be constructed for the lines
            thus subscripted.

        :param wave: (optional)
            The wavelength array (in Angstroms) used to construct the gaussian
            vectors. If not given, the cached `_outwave` array will be used.

        :returns gaussians:
            The unit gaussians for each line, in units Lsun/Hz.
            ndarray of shape (n_wave, n_line)
        """
        if wave is None:
            warr = self._outwave
        else:
            warr = wave

        # generate gaussians
        mu = np.atleast_2d(self._ewave_obs[lineidx])
        sigma = np.atleast_2d(self._eline_sigma_kms[lineidx])
        dv = ckms * (warr[:, None]/mu - 1)
        dv_dnu = ckms * warr[:, None]**2 / (lightspeed * mu)

        eline_gaussians = 1. / (sigma * np.sqrt(np.pi * 2)) * np.exp(-dv**2 / (2 * sigma**2))
        eline_gaussians *= dv_dnu

        # outside of the wavelengths defined by the spectrum? (why this dependence?)
        # FIXME what is this?
        eline_gaussians /= -np.trapz(eline_gaussians, 3e18/warr[:, None], axis=0)

        return eline_gaussians

    def smoothspec(self, wave, spec):
        """Smooth the spectrum.  See :py:func:`prospect.utils.smoothing.smoothspec`
        for details.
        """
        sigma = self.params.get("sigma_smooth", 100)
        outspec = smoothspec(wave, spec, sigma, outwave=self._outwave, **self.params)

        return outspec

    def observed_wave(self, wave, do_wavecal=False):
        """Convert the restframe wavelngth grid to the observed frame wavelength
        grid, optionally including wavelength calibration adjustments.  Requires
        that the ``_zred`` attribute is already set.

        :param wave:
            The wavelength array
        """
        # FIXME: missing wavelength calibration code
        if do_wavecal:
            raise NotImplementedError
        a = 1 + self._zred
        return wave * a

    def wave_to_x(self, wavelength=None, mask=slice(None), **extras):
        """Map unmasked wavelengths to the interval -1, 1
              masked wavelengths may have x>1, x<-1
        """
        x = wavelength - (wavelength[mask]).min()
        x = 2.0 * (x / (x[mask]).max()) - 1.0
        return x

    def spec_calibration(self, **kwargs):
        return np.ones_like(self._outwave)

    def absolute_rest_maggies(self, filters):
        """Return absolute rest-frame maggies (=10**(-0.4*M)) of the last
        computed spectrum.

        Parameters
        ----------
        filters : list of ``sedpy.observate.Filter()`` instances
            The filters through which you wish to compute the absolute mags

        Returns
        -------
        maggies : ndarray of shape (nbands,)
            The absolute restframe maggies of the model through the supplied
            filters, including emission lines.  Convert to absolute rest-frame
            magnitudes as M = -2.5 * log10(maggies)
        """
        # --- convert spectrum ---
        ld = cosmo.luminosity_distance(self._zred).to("pc").value
        # convert to maggies if the source was at 10 parsec, accounting for the (1+z) applied during predict()
        fmaggies = self._norm_spec / (1 + self._zred) * (ld / 10)**2
        # convert to erg/s/cm^2/AA for sedpy and get absolute magnitudes
        flambda = fmaggies * lightspeed / self._wave**2 * (3631*jansky_cgs)
        abs_rest_maggies = 10**(-0.4 * np.atleast_1d(getSED(self._wave, flambda, filters)))
        # TODO: below is faster for sedpy > 0.2.0
        #abs_rest_maggies = np.atleast_1d(getSED(self._wave, flambda, filters, linear_flux=True))

        # add emission lines
        if bool(self.params.get('nebemlineinspec', False)) is False:
            eline_z = self.params.get("eline_delta_zred", 0.0)
            elams = (1 + eline_z) * self._eline_wave
            elums = self._eline_lum * self.flux_norm() / (1 + self._zred) * (3631*jansky_cgs) * (ld / 10)**2
            emaggies = self.nebline_photometry(filters, elams=elams, elums=elums)
            abs_rest_maggies += emaggies

        return abs_rest_maggies

    def mean_model(self, theta, obs, sps=None, sigma=None, **extras):
        """Legacy wrapper around predict()
        """
        return self.predict(theta, obs, sps=sps, sigma_spec=sigma, **extras)

class PolySpecModel(SpecModel):

    """This is a subclass of *SpecModel* that generates the multiplicative
    calibration vector at each model `predict` call as the maximum likelihood
    chebyshev polynomial describing the ratio between the observed and the model
    spectrum.
    """

    def _available_parameters(self):
        pars = [("polyorder", "order of the polynomial to fit"),
                ("poly_regularization", "vector of length `polyorder` providing regularization for each polynomial term")
                ]

        return pars

    def spec_calibration(self, theta=None, obs=None, spec=None, **kwargs):
        """Implements a Chebyshev polynomial calibration model. This uses
        least-squares to find the maximum-likelihood Chebyshev polynomial of a
        certain order describing the ratio of the observed spectrum to the model
        spectrum, conditional on all other parameters, using least squares. If
        emission lines are being marginalized out, they are excluded from the
        least-squares fit.

        :returns cal:
           A polynomial given by :math:`\sum_{m=0}^M a_{m} * T_m(x)`.
        """
        if theta is not None:
            self.set_parameters(theta)

        # norm = self.params.get('spec_norm', 1.0)
        polyopt = ((self.params.get('polyorder', 0) > 0) &
                   (obs.get('spectrum', None) is not None))
        if polyopt:
            order = self.params['polyorder']

            # generate mask
            # remove region around emission lines if doing analytical marginalization
            mask = obs.get('mask', np.ones_like(obs['wavelength'], dtype=bool)).copy()
            if self.params.get('marginalize_elines', False):
                mask[self._fit_eline_pixelmask] = 0

            # map unmasked wavelengths to the interval -1, 1
            # masked wavelengths may have x>1, x<-1
            x = self.wave_to_x(obs["wavelength"], mask)
            y = (obs['spectrum'] / spec)[mask] - 1.0
            yerr = (obs['unc'] / spec)[mask]
            yvar = yerr**2
            A = chebvander(x[mask], order)
            ATA = np.dot(A.T, A / yvar[:, None])
            reg = self.params.get('poly_regularization', 0.)
            if np.any(reg > 0):
                ATA += reg**2 * np.eye(order)
            ATAinv = np.linalg.inv(ATA)
            c = np.dot(ATAinv, np.dot(A.T, y / yvar))
            Afull = chebvander(x, order)
            poly = np.dot(Afull, c)
            self._poly_coeffs = c
        else:
            poly = np.zeros_like(self._outwave)

        return (1.0 + poly)

class SplineSpecModel(SpecModel):

    """This is a subclass of *SpecModel* that generates the multiplicative
    calibration vector at each model `predict` call as the maximum likelihood
    cubic spline describing the ratio between the observed and the model
    spectrum.
    """

    def _available_parameters(self):
        pars = [("spline_knot_wave", "vector of wavelengths for the location ot he spline knots"),
                ("spline_knot_spacing", "spacing between knots, in units of wavelength"),
                ("spline_knot_n", "number of interior knots between minimum and maximum unmasked wavelength")
                ]

        return pars

    def spec_calibration(self, theta=None, obs=None, spec=None, **kwargs):
        """Implements a spline calibration model.  This fits a cubic spline with
        determined knot locations to the ratio of the observed spectrum to the
        model spectrum.  If emission lines are being marginalized out, they are
        excluded from the least-squares fit.

        The knot locations must be specified as model parameters, either
        explicitly or via a number of knots or knot spacing (in angstroms)
        """
        if theta is not None:
            self.set_parameters(theta)

        splineopt = True
        if splineopt:
            mask, (wlo, whi) = self.obs_to_mask(obs)
            y = (obs['spectrum'] / spec)[mask] - 1.0
            yerr = (obs['unc'] / spec)[mask]  # HACK
            knots_x = self.make_knots(wlo, whi, as_x=True, **self.params)
            x = 2.0 * (obs["wavelength"] - wlo) / (whi - wlo) - 1.0
            tck = splrep(x[mask], y[mask], w=1/yerr[mask], k=3, task=-1, t=knots_x)
            self._spline_coeffs = tck
            spl = BSpline(*tck)
            spline = spl(x)
        else:
            spline = np.zeros_like(self._outwave)
        return (1.0 + spline)

    def make_knots(self, wlo, whi, as_x=True, **params):
        """
        """
        if "spline_knot_wave" in params:
            knots = np.squeeze(params["spline_knot_wave"])
        elif "spline_knot_spacing" in params:
            s = np.squeeze(params["spline_knot_spacing"])
            # we drop the start so knots are interior
            knots = np.arange(wlo, whi, s)[1:]
        elif "spline_knot_n" in params:
            n = np.squeeze(params["spline_knot_n"])
            # we need to drop the endpoints so knots are interior
            knots = np.linspace(wlo, whi, n)[1:-1]
        else:
            raise KeyError("No valid spline knot specification in self.params")

        if as_x:
            knots = 2.0 * (knots - wlo) / (whi - wlo) - 1.0

        return knots

    def obs_to_mask(self, obs):
        mask = obs.get('mask', np.ones_like(obs['wavelength'], dtype=bool)).copy()
        if self.params.get('marginalize_elines', False):
            mask[self._fit_eline_pixelmask] = 0
        w = obs["wavelength"]
        wrange = w[mask].min(), w[mask].max()
        return mask, wrange

class LineSpecModel(SpecModel):

    """This is a sublcass of SpecModel that predicts emission line fluxes
    instead of a full spectrum, useful when the continuum is not detected or is
    otherwise uninformative.
    """

    def _available_parameters(self):
        pars = [("linespec_scaling", "This float scales the predicted nebular "
                 "emission line luminosities, for example to accxount for a "
                 "(constant in wavelengtrh) slit loss"),
                ]

        return pars

    def predict_spec(self, obs, **extras):
        """Generate a prediction for the observed nebular line fluxes.  This method assumes
        that the model parameters have been set and that the following
        attributes are present and correct
          + ``_wave`` - The SPS restframe wavelength array
          + ``_zred`` - Redshift
          + ``_norm_spec`` - Observed frame spectral fluxes, in units of maggies
          + ``_eline_wave`` and ``_eline_lum`` - emission line parameters from the SPS model
        It generates the following attributes
          + ``_outwave`` - Wavelength grid (observed frame)
          + ``_speccal`` - Calibration vector

        Numerous quantities related to the emission lines are also cached (see
        ``cache_eline_parameters()`` and ``fit_el()`` for details) including
        ``_predicted_line_inds`` which is the indices of the line that are predicted.

        :param obs:
            An observation dictionary, containing the keys
            + ``"wavelength"`` - the observed frame wavelength of the lines.
            + ``"line_ind"`` - a set of indices identifying the observed lines in
            the fsps line array
            Assumed to be the result of :py:meth:`utils.obsutils.rectify_obs`

        :returns spec:
            The prediction for the observed frame nebular emission line flux these
            parameters, at the wavelengths specified by ``obs['wavelength']``,
            ndarray of shape ``(nwave,)`` in units of erg/s/cm^2.
        """
        obs_wave = self.observed_wave(self._eline_wave, do_wavecal=False)
        self._outwave = obs.get('wavelength', obs_wave)
        assert len(self._outwave) <= len(self.emline_info)

        # --- cache eline parameters ---
        self.cache_eline_parameters(obs)

        # find the indices of the observed emission lines
        #dw = np.abs(self._ewave_obs[:, None] - self._outwave[None, :])
        #self._predicted_line_inds = np.argmin(dw, axis=0)
        self._predicted_line_inds = obs.get("line_ind")
        self._speccal = 1.0

        self.line_norm = self.flux_norm() / (1 + self._zred) * (3631*jansky_cgs)
        self.line_norm *= self.params.get("linespec_scaling", 1.0)
        elums = self._eline_lum[self._predicted_line_inds] * self.line_norm

        return elums

    def nebline_photometry(self, filters, elams=None, elums=None):
        """Compute the emission line contribution to photometry.  This requires
        several cached attributes:
          + ``_ewave_obs``
          + ``_eline_lum``

        :param filters:
            Instance of :py:class:`sedpy.observate.FilterSet` or list of
            :py:class:`sedpy.observate.Filter` objects

        :param elams: (optional)
            The emission line wavelength in angstroms.  If not supplied uses the
            cached ``_ewave_obs`` attribute.

        :param elums: (optional)
            The emission line flux in erg/s/cm^2.  If not supplied uses  the
            cached ``_eline_lum`` attribute and applies appropriate distance
            dimming and unit conversion.

        :returns nebflux:
            The flux of the emission line through the filters, in units of
            maggies. ndarray of shape ``(len(filters),)``
        """
        if (elams is None) or (elums is None):
            elams = self._ewave_obs[self._use_eline]
            # We have to remove the extra (1+z) since this is flux, not a flux density
            # Also we convert to cgs
            self.line_norm = self.flux_norm() / (1 + self._zred) * (3631*jansky_cgs)
            elums = self._eline_lum[self._use_eline] * self.line_norm

        # loop over filters
        flux = np.zeros(len(filters))
        try:
            # TODO: Since in this case filters are on a grid, there should be a
            # faster way to look up the transmission than the later loop
            flist = filters.filters
        except(AttributeError):
            flist = filters
        for i, filt in enumerate(flist):
            # calculate transmission at line wavelengths
            trans = np.interp(elams, filt.wavelength, filt.transmission,
                              left=0., right=0.)
            # include all lines where transmission is non-zero
            idx = (trans > 0)
            if True in idx:
                flux[i] = (trans[idx]*elams[idx]*elums[idx]).sum() / filt.ab_zero_counts

        return flux

class AGNSpecModel(SpecModel):

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)
        self.init_aline_info()

    def _available_parameters(self):
        pars = [("agn_elum",
                 "This float scales the predicted AGN nebular emission line"
                 "luminosities, in units of Lsun(Hbeta)/Mformed"),
                ("agn_eline_sigma",
                 "This float gives the velocity dispersion of the AGN emission"
                 "lines in km/s")
                ]

        return pars

    def init_aline_info(self):
        """AGN line spectrum. Based on data as reported in Richardson et al.
        2014 (Table 3, the 'a42' dataset) and normalized to Hbeta.index=48 is Hbeta
        """
        ainds = np.array([31, 33, 34, 35, 37, 42, 43, 44, 48,
                          49, 50, 52, 56, 57, 58, 60, 61, 62,
                          63, 64, 65, 66, 68])
        afluxes = np.array([2.96, 0.06, 0.1 , 1.  , 0.2 , 0.25, 0.48, 0.13, 1.,
                            2.87, 8.53, 0.07, 0.02, 0.1 , 0.33, 0.09, 0.79, 2.86,
                            2.13, 0.03, 0.77, 0.65, 0.19])
        self._aline_lum = np.zeros(128)
        self._aline_lum[ainds] = afluxes

    def predict_spec(self, obs, sigma_spec=None, **extras):
        """Generate a prediction for the observed spectrum.  This method assumes
        that the parameters have been set and that the following attributes are
        present and correct

          + ``_wave`` - The SPS restframe wavelength array
          + ``_zred`` - Redshift
          + ``_norm_spec`` - Observed frame spectral fluxes, in units of maggies
          + ``_eline_wave`` and ``_eline_lum`` - emission line parameters from the SPS model

        It generates the following attributes

          + ``_outwave`` - Wavelength grid (observed frame)
          + ``_speccal`` - Calibration vector
          + ``_sed`` - Intrinsic spectrum (before cilbration vector applied but including emission lines)

        And the following attributes are generated if nebular lines are added

          + ``_fix_eline_spec`` - emission line spectrum for fixed lines, intrinsic units
          + ``_fix_eline_spec`` - emission line spectrum for fitted lines, with
            spectroscopic calibration factor included.

        Numerous quantities related to the emission lines are also cached (see
        ``cache_eline_parameters()`` and ``fit_el()`` for details.)

        :param obs:
            An observation dictionary, containing the output wavelength array,
            the photometric filter lists, and the observed fluxes and
            uncertainties thereon.  Assumed to be the result of
            :py:meth:`utils.obsutils.rectify_obs`

        :param sigma_spec: (optional)
            The covariance matrix for the spectral noise. It is only used for
            emission line marginalization.

        :returns spec:
            The prediction for the observed frame spectral flux these
            parameters, at the wavelengths specified by ``obs['wavelength']``,
            including multiplication by the calibration vector.
            ndarray of shape ``(nwave,)`` in units of maggies.
        """
        # redshift wavelength
        obs_wave = self.observed_wave(self._wave, do_wavecal=False)
        self._outwave = obs.get('wavelength', obs_wave)
        if self._outwave is None:
            self._outwave = obs_wave

        # --- cache eline parameters ---
        nsigma = 5 * np.max(self.params.get("agn_eline_sigma", 100.0) / self.params.get("eline_sigma", 100))
        self.cache_eline_parameters(obs, nsigma=nsigma)

        # --- smooth and put on output wavelength grid ---
        smooth_spec = self.smoothspec(obs_wave, self._norm_spec)

        # --- add fixed lines ---
        assert self.params["nebemlineinspec"] == False, "must add agn and nebular lines within prospector"
        assert self.params.get("marginalize_elines", False) == False, "Cannot fit lines when AGN lines included"
        emask = self._fix_eline_pixelmask
        if emask.any():
            # Add SF lines
            inds = self._fix_eline & self._valid_eline
            espec = self.predict_eline_spec(line_indices=inds,
                                            wave=self._outwave[emask])
            self._fix_eline_spec = espec
            smooth_spec[emask] += self._fix_eline_spec.sum(axis=1)

            # Add agn lines
            aspec = self.predict_aline_spec(line_indices=inds,
                                            wave=self._outwave[emask])
            self._agn_eline_spec = aspec
            smooth_spec[emask] += self._agn_eline_spec

        # --- calibration ---
        self._speccal = self.spec_calibration(obs=obs, spec=smooth_spec, **extras)
        calibrated_spec = smooth_spec * self._speccal

        # --- cache intrinsic spectrum ---
        self._sed = calibrated_spec / self._speccal

        return calibrated_spec

    def predict_phot(self, filters):
        """Generate a prediction for the observed photometry.  This method assumes
        that the parameters have been set and that the following attributes are
        present and correct:
          + ``_wave`` - The SPS restframe wavelength array
          + ``_zred`` - Redshift
          + ``_norm_spec`` - Observed frame spectral fluxes, in units of maggies.
          + ``_ewave_obs`` and ``_eline_lum`` - emission line parameters from
            the SPS model

        :param filters:
            Instance of :py:class:`sedpy.observate.FilterSet` or list of
            :py:class:`sedpy.observate.Filter` objects. If there is no
            photometry, ``None`` should be supplied.

        :returns phot:
            Observed frame photometry of the model SED through the given filters.
            ndarray of shape ``(len(filters),)``, in units of maggies.
            If ``filters`` is None, this returns 0.0
        """
        if filters is None:
            return 0.0

        # generate photometry w/o emission lines
        obs_wave = self.observed_wave(self._wave, do_wavecal=False)
        flambda = self._norm_spec * lightspeed / obs_wave**2 * (3631*jansky_cgs)
        phot = 10**(-0.4 * np.atleast_1d(getSED(obs_wave, flambda, filters)))
        # TODO: below is faster for sedpy > 0.2.0
        #phot = np.atleast_1d(getSED(obs_wave, flambda, filters, linear_flux=True))

        # generate emission-line photometry
        if (self._want_lines & self._need_lines):
            phot += self.nebline_photometry(filters)
            # Add agn lines to photometry
            # this could use _use_line
            anorm = self.params.get('agn_elum', 1.0) * self.flux_norm() / (1 + self._zred) * (3631*jansky_cgs)
            alums = self._aline_lum * anorm
            alams = self._ewave_obs
            phot += self.nebline_photometry(filters, alams, alums)

        return phot

    def predict_aline_spec(self, line_indices, wave):
        # HACK to change the AGN line widths.
        orig = self._eline_sigma_kms
        nline = self._ewave_obs.shape[0]
        self._eline_sigma_kms = np.atleast_1d(self.params.get('agn_eline_sigma', 100.0))
        self._eline_sigma_kms = (self._eline_sigma_kms[None] * np.ones(nline)).squeeze()
        #self._eline_sigma_kms *= np.ones(self._ewave_obs.shape[0])
        gaussians = self.get_eline_gaussians(lineidx=line_indices, wave=wave)
        self._eline_sigma_kms = orig

        anorm = self.params.get('agn_elum', 1.0) * self.flux_norm() / (1 + self._zred)
        alums = self._aline_lum[line_indices] * anorm
        aline_spec = (alums * gaussians).sum(axis=1)
        return aline_spec

class SedModel(ProspectorParams):

    """A subclass of :py:class:`ProspectorParams` that passes the models
    through to an ``sps`` object and returns spectra and photometry, including
    optional spectroscopic calibration and sky emission.
    """

    def predict(self, theta, obs=None, sps=None, **extras):
        """Given a ``theta`` vector, generate a spectrum, photometry, and any
        extras (e.g. stellar mass), including any calibration effects.

        :param theta:
            ndarray of parameter values, of shape ``(ndim,)``

        :param obs:
            An observation dictionary, containing the output wavelength array,
            the photometric filter lists, and the observed fluxes and
            uncertainties thereon.  Assumed to be the result of
            :py:func:`utils.obsutils.rectify_obs`

        :param sps:
            An `sps` object to be used in the model generation.  It must have
            the :py:func:`get_spectrum` method defined.

        :param sigma_spec: (optional, unused)
            The covariance matrix for the spectral noise. It is only used for
            emission line marginalization.

        :returns spec:
            The model spectrum for these parameters, at the wavelengths
            specified by ``obs['wavelength']``, including multiplication by the
            calibration vector.  Units of maggies

        :returns phot:
            The model photometry for these parameters, for the filters
            specified in ``obs['filters']``.  Units of maggies.

        :returns extras:
            Any extra aspects of the model that are returned.  Typically this
            will be `mfrac` the ratio of the surviving stellar mass to the
            stellar mass formed.
        """
        s, p, x = self.sed(theta, obs, sps=sps, **extras)
        self._speccal = self.spec_calibration(obs=obs, **extras)
        if obs.get('logify_spectrum', False):
            s = np.log(s) + np.log(self._speccal)
        else:
            s *= self._speccal
        return s, p, x

    def sed(self, theta, obs=None, sps=None, **kwargs):
        """Given a vector of parameters ``theta``, generate a spectrum, photometry,
        and any extras (e.g. surviving mass fraction), ***not** including any
        instrument calibration effects.  The intrinsic spectrum thus produced is
        cached in `_spec` attribute

        :param theta:
            ndarray of parameter values.

        :param obs:
            An observation dictionary, containing the output wavelength array,
            the photometric filter lists, and the observed fluxes and
            uncertainties thereon.  Assumed to be the result of
            :py:func:`utils.obsutils.rectify_obs`

        :param sps:
            An `sps` object to be used in the model generation.  It must have
            the :py:func:`get_spectrum` method defined.

        :returns spec:
            The model spectrum for these parameters, at the wavelengths
            specified by ``obs['wavelength']``.  Default units are maggies, and
            the calibration vector is **not** applied.

        :returns phot:
            The model photometry for these parameters, for the filters
            specified in ``obs['filters']``. Units are maggies.

        :returns extras:
            Any extra aspects of the model that are returned.  Typically this
            will be `mfrac` the ratio of the surviving stellar mass to the
            steallr mass formed.
        """
        self.set_parameters(theta)
        spec, phot, extras = sps.get_spectrum(outwave=obs['wavelength'],
                                              filters=obs['filters'],
                                              component=obs.get('component', -1),
                                              lnwavegrid=obs.get('lnwavegrid', None),
                                              **self.params)

        spec *= obs.get('normalization_guess', 1.0)
        # Remove negative fluxes.
        try:
            tiny = 1.0 / len(spec) * spec[spec > 0].min()
            spec[spec < tiny] = tiny
        except:
            pass
        spec = (spec + self.sky(obs))
        self._spec = spec.copy()
        return spec, phot, extras

    def sky(self, obs):
        """Model for the *additive* sky emission/absorption"""
        return 0.

    def spec_calibration(self, theta=None, obs=None, **kwargs):
        """Implements an overall scaling of the spectrum, given by the
        parameter ``'spec_norm'``

        :returns cal: (float)
          A scalar multiplicative factor that gives the ratio between the true
          spectrum and the observed spectrum
        """
        if theta is not None:
            self.set_parameters(theta)

        return 1.0 * self.params.get('spec_norm', 1.0)

    def wave_to_x(self, wavelength=None, mask=slice(None), **extras):
        """Map unmasked wavelengths to the interval (-1, 1). Masked wavelengths may have x>1, x<-1

        :param wavelength:
            The input wavelengths.  ndarray of shape ``(nwave,)``

        :param mask: optional
            The mask.  slice or boolean array with ``True`` for unmasked elements.
            The interval (-1, 1) will be defined only by unmasked wavelength points

        :returns x:
            The wavelength vector, remapped to the interval (-1, 1).
            ndarray of same shape as  ``wavelength``
        """
        x = wavelength - (wavelength[mask]).min()
        x = 2.0 * (x / (x[mask]).max()) - 1.0
        return x

    def mean_model(self, theta, obs, sps=None, sigma_spec=None, **extras):
        """Legacy wrapper around predict()
        """
        return self.predict(theta, obs, sps=sps, sigma=sigma_spec, **extras)

class PolySedModel(SedModel):

    """This is a subclass of SedModel that replaces the calibration vector with
    the maximum likelihood chebyshev polynomial describing the difference
    between the observed and the model spectrum.
    """

    def spec_calibration(self, theta=None, obs=None, **kwargs):
        """Implements a Chebyshev polynomial calibration model. This uses
        least-squares to find the maximum-likelihood Chebyshev polynomial of a
        certain order describing the ratio of the observed spectrum to the
        model spectrum, conditional on all other parameters, using least
        squares.  The first coefficient is always set to 1, as the overall
        normalization is controlled by ``spec_norm``.

        :returns cal:
           A polynomial given by 'spec_norm' * (1 + \sum_{m=1}^M a_{m} * T_m(x)).
        """
        if theta is not None:
            self.set_parameters(theta)

        norm = self.params.get('spec_norm', 1.0)
        polyopt = ((self.params.get('polyorder', 0) > 0) &
                   (obs.get('spectrum', None) is not None))
        if polyopt:
            order = self.params['polyorder']
            mask = obs.get('mask', slice(None))
            # map unmasked wavelengths to the interval -1, 1
            # masked wavelengths may have x>1, x<-1
            x = self.wave_to_x(obs["wavelength"], mask)
            y = (obs['spectrum'] / self._spec)[mask] / norm - 1.0
            yerr = (obs['unc'] / self._spec)[mask] / norm
            yvar = yerr**2
            A = chebvander(x[mask], order)[:, 1:]
            ATA = np.dot(A.T, A / yvar[:, None])
            reg = self.params.get('poly_regularization', 0.)
            if np.any(reg > 0):
                ATA += reg**2 * np.eye(order)
            ATAinv = np.linalg.inv(ATA)
            c = np.dot(ATAinv, np.dot(A.T, y / yvar))
            Afull = chebvander(x, order)[:, 1:]
            poly = np.dot(Afull, c)
            self._poly_coeffs = c
        else:
            poly = 0.0

        return (1.0 + poly) * norm

class PolyFitModel(SedModel):

    """This is a subclass of *SedModel* that generates the multiplicative
    calibration vector as a Chebyshev polynomial described by the
    ``'poly_coeffs'`` parameter of the model, which may be free (fittable)
    """

    def spec_calibration(self, theta=None, obs=None, **kwargs):
        """Implements a Chebyshev polynomial calibration model.  This only
        occurs if ``"poly_coeffs"`` is present in the :py:attr:`params`
        dictionary, otherwise the value of ``params["spec_norm"]`` is returned.

        :param theta: (optional)
            If given, set :py:attr:`params` using this vector before
            calculating the calibration polynomial. ndarray of shape
            ``(ndim,)``

        :param obs:
            A dictionary of observational data, must contain the key
            ``"wavelength"``

        :returns cal:
           If ``params["cal_type"]`` is ``"poly"``, a polynomial given by
           ``'spec_norm'`` :math:`\times (1 + \Sum_{m=1}^M```'poly_coeffs'[m-1]``:math:` \times T_n(x))`.
           Otherwise, the exponential of a Chebyshev polynomial.
        """
        if theta is not None:
            self.set_parameters(theta)

        if ('poly_coeffs' in self.params):
            mask = obs.get('mask', slice(None))
            # map unmasked wavelengths to the interval -1, 1
            # masked wavelengths may have x>1, x<-1
            x = self.wave_to_x(obs["wavelength"], mask)
            # get coefficients.  Here we are setting the first term to 0 so we
            # can deal with it separately for the exponential and regular
            # multiplicative cases
            c = np.insert(self.params['poly_coeffs'], 0, 0)
            poly = chebval(x, c)
            # switch to have spec_norm be multiplicative or additive depending
            # on whether the calibration model is multiplicative in exp^poly or
            # just poly
            if self.params.get('cal_type', 'exp_poly') == 'poly':
                return (1.0 + poly) * self.params.get('spec_norm', 1.0)
            else:
                return np.exp(self.params.get('spec_norm', 0) + poly)
        else:
            return 1.0 * self.params.get('spec_norm', 1.0)

def ln_mvn(x, mean=None, cov=None):
    """Calculates the natural logarithm of the multivariate normal PDF
    evaluated at `x`

    :param x:
        locations where samples are desired.

    :param mean:
        Center(s) of the gaussians.

    :param cov:
        Covariances of the gaussians.
    """
    ndim = mean.shape[-1]
    dev = x - mean
    log_2pi = np.log(2 * np.pi)
    sign, log_det = np.linalg.slogdet(cov)
    exp = np.dot(dev.T, np.dot(np.linalg.pinv(cov, rcond=1e-12), dev))

    return -0.5 * (ndim * log_2pi + log_det + exp)

def gauss(x, mu, A, sigma):
    """Sample multiple gaussians at positions x.

    :param x:
        locations where samples are desired.

    :param mu:
        Center(s) of the gaussians.

    :param A:
        Amplitude(s) of the gaussians, defined in terms of total area.

    :param sigma:
        Dispersion(s) of the gaussians, un units of x.

    :returns val:
        The values of the sum of gaussians at x.
    """
    mu, A, sigma = np.atleast_2d(mu), np.atleast_2d(A), np.atleast_2d(sigma)
    val = A / (sigma * np.sqrt(np.pi * 2)) * np.exp(-(x[:, None] - mu)**2 / (2 * sigma**2))
    return val.sum(axis=-1)

redpajama_set_name: RedPajamaGithub


Home - Smart Imaging Systems Inc.
Smart Imaging Systems is a forward-looking security technology company developing next generation inspection systems.
The company's award winning founding scientists and engineers with decades of experience at NASA in developing ultra miniature, ultra sensitive, high performance detectors and electronic systems have used similar technology to build next generation X-ray systems for security applications.
- For explosives and Contraband detection.
Revolutionary technology shrinks the scanner size to make it ultra compact and hand portable that easily fits in the trunk of a passenger car; operates on small batteries instead of requiring power generators.
With increasing risks at public venues like airports, sporting events and city streets, the need for safe rapid detection of IEDs is acute. Utilizing SIS's revolutionary technology answers that need with miniaturized robot mounted sensors. Left behind packages can be scanned rapidly, at a safe stand-off distance.
A backpack left on a crowded street. A small bag left in the corner of a busy terminal. These are all areas where explosive devices have been found and ready to detonate and harm people. In today's law enforcement environment, First Responders are more sensitive than ever to the possible existence of explosive devices.
Customs officers have the unenviable task of scanning cars for threat items and contraband that may include ammunition, weapons, explosives and drugs. Today's scanning technology is very expensive, time consuming and challenging to fit into a fast-tempo operation scheme.
Developed by the US Government to fill the gap between large truck mounted systems and small handheld systems, Smart Imaging System's MXR delivers a new dimension to the screening of suspicious packages.
The versatility of the CXR system enables the impromptu scanning of motor vehicles for drugs, weapons and bombs.


text:
{{title}}

Welcome to your new single-page application, built with:

ASP.NET Core and C# for cross-platform server-side code

Angular 2 and TypeScript for client-side code

Webpack for building and bundling client-side resources

Bootstrap for layout and styling

To help you get started, we've also set up:

Client-side navigation. For example, click Counter then Back to return here.

Server-side prerendering. For faster initial loading and improved SEO, your Angular 2 app is prerendered on the server. The resulting HTML is then transferred to the browser where a client-side copy of the app takes over.

Webpack dev middleware. In development mode, there's no need to run the webpack build tool. Your client-side resources are dynamically built on demand. Updates are available as soon as you modify any file.

Hot module replacement. In development mode, you don't even need to reload the page after making most changes. Within seconds of saving changes to files, your Angular 2 app will be rebuilt and a new instance injected is into the page.

Efficient production builds. In production mode, development-time features are disabled, and the webpack build tool produces minified static CSS and JavaScript files.

<item-list class="latest"></item-list>
<item-list class="most-viewed"></item-list>
<item-list class="random"></item-list>


Today was a complete down day for most of us. Ws have been going pretty hard, skiing every day and decided it was time to give our bodies a little bit of a rest. Of course Peter did not feel the same way, so in the afternoon he went skiing.
As a result there is not a ton to report. We sat around the apartment all day and worked on our various projects. Stacie and Lea on school and me on the app that I am building. Then in the afternoon Stacie and I went for a little walk around Argentiere and went grocery shopping.
Stacie made a wonderful Pesto Pasta with Shrimp for dinner and we capped off the night with another Newsroom. We have become pretty addicted to the show - too bad it was cancelled after Season 3.


text:
Q: Special characters in chat I have to write in Spanish and I have OSX version of Minecraft. I had a problem with some special characters when playing multiplayer. Everybody is writing these characters and I can see them clearly, but the key on my keyboard to insert this ticks over the vocals does nothing, so when I press the vocal key it's written normal.
The characters I'm talking about are: áéíóú. I have no problem with ñ.
I tried to put the game in Spanish, or turning Forcing Unicode Font to ON, but no luck. Anyone knows what to do to fix this?

A: This is a very long-standing bug in Minecraft on Mac OS X with so-called "dead keys" like Spanish keyboards' ´ and ` keys. (Your ñ works fine because it produces ñ using a single key instead of two.) Unfortunately, Mojang keeps thinking they have Minecraft's dead key handling fixed, but their Mac expertise seems to be insufficient and they don't seem to realise that fixing these keys on Windows doesn't automatically fix them on Mac OS X due to differences in how each operating system handles dead keys.
The most solid (but least quick) solution is to make noise on Mojang's bugtracker. Until Mojang fixes this on their end, there is no way to type these keys on a Mac OS X keyboard layout that uses dead keys.
A workaround is to manually design a new keyboard layout that doesn't use dead keys to type these letters, instead overwriting some of your other keys with keys dedicated to all the other accented characters (like the dedicated ñ key already is). You can do that using the Ukulele program in that link to create a custom layout. You'd have to sacrifice some keys, and which are up to you (I don't use Spanish keyboards and couldn't give useful advice for keys to replace), but the nice thing about Ukulele is that its graphical interface makes it easy to test and tweak your layout until it works for you.
Once you've created your layout and put the file in the right place (see Ukulele's instructions), you can use Mac OS X's Language & Text control panel to select your new keyboard layout as an keyboard option that you can easily switch to before launching Minecraft, then switch back to your regular Spanish layout when you're done. To make switching painless, make sure you have Show Input menu in menu bar selected in that control panel, so you can easily switch layouts from the OS X menu bar.

A: if you are on mac press option then press any letter

redpajama_set_name: RedPajamaStackExchange


text:
Вита́лий Васи́льевич Грибня́к (18 февраля 1929, Свердловск — 2 августа 2006, там же) — советский тренер по боксу, подготовивший более тридцати мастеров спорта и одного мастера спорта международного класса Алексея Пряничникова, за что удостоен звания заслуженного тренера РСФСР. Судья республиканской категории.

Биография
Родился 18 февраля 1929 года в Свердловске. Во время Великой Отечественной войны в возрасте тринадцати лет пошёл работать на местный оборонный завод, на токарном станке обрабатывал корпуса снарядов для систем полевой реактивной артиллерии. В 1945 году в качестве добровольца отправился восстанавливать разрушенные промышленные предприятия на Донбассе.

Вскоре вернулся в Свердловск, трудоустроился и одновременно с работой проходил обучение в школе рабочей молодёжи. Тогда же начал активно заниматься боксом в секции Свердловского окружного дома офицеров. Первое время проходил подготовку под руководством тренера Юрия Савинова, позже присоединился к добровольному спортивному обществу «Динамо» и стал подопечным заслуженного тренера СССР Льва Михайловича Вяжлинского. Был призёром первенств РСФСР среди юниоров и взрослых спортсменов, однако оставался действующим боксёром не долго, решив посвятить себя тренерскому делу.

Окончив Свердловский техникум физической культуры и заочно Омский государственный институт физической культуры, занялся тренерской деятельностью — в разные годы тренировал детей в боксёрских секциях «Спартака», «Юности», «Динамо», «Локомотива». За долгие годы в качестве тренера подготовил большое число боксёров, получивших спортивные разряды, более тридцати мастеров спорта. В числе его воспитанников призёры первенств РСФСР Иван Ипполитов, Владимир Городничев, известные тренеры Олег Меньшиков, Вадим Рыбников и др. Один из самых известных его учеников мастер спорта международного класса Алексей Пряничников является чемпионом мира и Европы среди юниоров — за подготовку этого спортсмена в 1985 году Грибняк был удостоен почётного звания «Заслуженный тренер РСФСР». В общей сложности проработал на тренерском поприще более 50 лет. Участвовал в боксёрских турнирах в качестве судьи республиканской категории. Награждён почётным знаком «За заслуги в развитии физической культуры и спорта».

Умер 2 августа 2006 года в Екатеринбурге в возрасте 77 лет. Похоронен на Широкореченском кладбище.

Примечания

Ссылки
 Грибняк Виталий Васильевич — биография на сайте «Спорт на Урале»
 Грибняк Виталий Васильевич — справка на сайте «Спортивная страна»

Боксёры СССР
Боксёры 1940-х годов
Тренеры по боксу СССР
Заслуженные тренеры РСФСР
Выпускники СибГУФК
Похороненные на Широкореченском кладбище

redpajama_set_name: RedPajamaWikipedia


text:
Skills / Experience
Web /Graphic Design
Concepts in Life
The All Seeing Eye: A Symbol of Consciousness
The Universal Right to Life
Infinite love is the only truth
Karma explained by Electromagnetism
Thoughts on Karma and Consciousness
The Global Brain
The Holographic and Fractal Universe
Exponential Growth in the Fractal Universe
Our basic needs as Human Beings
Human Nature and Morality
On Simulations and the Purpose of Life
Going Crazy in Reverse
The Truth Offends; The Truth Hurts
Change the World; Be Politically Incorrect
Always focus on the negative things in life
The painful truth: I'm a hypocrite
You can learn something from anyone
Dealing with being wrong
If you have nothing to lose, you can only win
Lessons from Jose Mujica
Why I never want to get rich
How not to raise your children
Persuasion, Influencing and Selling are Mind Manipulation
Statism: A System for your Enslavement
Politics: An Exercise in Futility
Why you shouldn't vote
Yahweh the Barbarian
The End of Religion
Why there can be only one true religion, and why not
The Difference between Love and Lust
A World without Jealousy
If you truly love someone, let them be free
Love as many people as you like
Why soul mates and perfect partners don't exist
Why courtship and dating are a waste of time
We should all be Part-Time Lovers
Relationships of the Future
The Manipulated Man
Why getting married is a very bad idea
The true meaning of Husband
The true meaning of Family
Love and relationships don't belong together
Why men and women can't be just friends
What I look for in women
What strong men look for in a woman
Sexual Suppression and Repression I: Definition and Origin
On the forms and the true purpose of Castration
The true meaning of Sex
Humans are naturally polygamists
On gender identity and transgender people
LGBT pride month: There is no pride in mental illness
On the origins of homosexuality and homosexuals
The true cause of Child Sexual Abuse
Everything in Life is about Sex
Understanding Women Part I
Understanding Women Part II
Understanding Women Part III
Understanding Women Part IV
Understanding Women Part V
Money and Economics
Economics is Pseudoscience; Economists are Stupid People
On money, Bitcoin and cryptocurrencies in general
Income Taxation is Slavery
Income Taxation enables Colonialism
Governments and Basic Income: Why they don't go together
Why poor people make bad decisions
Why the poor get poorer and the rich get richer
Got Cancer? Avoid Chemo; Use Hemp Oil
Vaccination is Ineffective and Dangerous
How the Lack of Sex causes Cancer
How the Lack of Sex degrades your Intelligence
HIV Does Not Cause AIDS
Planet X / Nibiru: An analysis of Akkadian Seal VA/243
Apple – The Occult Secrets behind the Brand
Type what you're looking for and press Enter.
Adolf Hitler: The Greatest Story NEVER Told
Apple - The Occult Secrets behind the Brand
Preview WMF and EMF images in Windows File Explorer
Using the QuantumGate Socks5 extender for private encrypted communications
Nathan Fraser on the types of people in the world
The Truth Behind The Original Bible Story
Attention court martial: Internet and mobile traffic not necessary for subversive activities
Criminal US Government has spies in major (tech) companies
Oscar Wilde on being wrong
ET Interventions: Q&A with Paul Wallis
Sex & Circumcision: An American Love Story By Eric Clopper
The Slave Plantation of Eden
Frederic Bastiat on Legislators and the Ruling Class
Microsoft Flight Sucks!
Back in January 2009 Microsoft shut down ACES Studio, the team at Microsoft who were developing the Flight Simulator game. The announcement came as a surprise to everyone, especially the large community of fans, developers and hardware manufacturers that exists around the Flight Simulator franchise. Why would Microsoft want to shut down the team that's responsible for arguably one of the most succesful gaming franchises in the history of the personal computer? At the time there was the ongoing economic crisis, and the shut down of ACES Studio was just part of a layoff of around 5000 people at Microsoft. It seemed to me that Microsoft was trying to cut expenses during the economic crisis, but it's unclear why they would want to shut down such a successful game like Flight Simulator. Back in 2006 when Bill Gates was showing off Windows Vista, Microsoft Flight Simulator X was one of the important games he demoed to show the capabilities of Windows Vista and DirectX 10. Up until today, Microsoft has never given a clear reason as to why exactly they shut down ACES Studio and apparently stopped development of the Flight Simulator game.
As soon as I heard about them shutting down ACES Studio, I wrote an angry email to Microsoft's CEO Steve Ballmer complaining about this decision. Apparently he must have forwarded the email inside Microsoft and a few days later someone from Microsoft Game Studios responded as follows:
Thank you for writing to us about the closing of the Aces Studio and the future of Microsoft Flight Simulator. Closing the studio was not a reflection of the quality of the products Aces has developed, the sales performance of the games, or the quality of the team at Aces. This difficult decision was made to align Microsoft's resources with our strategic priorities. Microsoft Flight Simulator X will remain available at retail stores and web retailers. The Flight Sim community will continue to learn from and encourage one another, and we remain committed to the Flight Simulator franchise for the long term.
Microsoft Game Studios is investing significant resources in many exciting and new areas of gaming and entertainment, including Windows games. We believe these future investments will push innovation, community, and collaboration to unprecedented levels and will provide more synergy with our ongoing investments in Games for Windows – LIVE as well as other Windows entertainment technologies. We have nothing specific to announce at this time, but stay tuned for more information.
We are humbled and proud of the passion and support that the Flight Simulator franchise has developed over its more than twenty-five year history. This includes you, the large community of flight simmers, as well as the vibrant third-party ecosystem that has developed around the game. We will continue to produce, sell, and support the latest version of Flight Simulator as we plan for future versions of the franchise. Thank you for your understanding of our decision and for your continued support!
Pay attention to the section in bold letters. They already had plans to change the course of the Flight Simulator franchise and make some kind of a Windows Live game instead. It would not be until the 18th of August 2010 that Microsoft would announce the development of the Windows Live based Microsoft Flight game.
The decision to shut down ACES Studio and change the course of the Flight Simulator franchise was taken within Microsoft's Interactive Entertainment Business:
Microsoft has confirmed the closure of ACES Studio, with a spokesperson commenting to IGN that the decision was made within Microsoft's Internal Entertainment Business "to align our people against our highest priorities."
In addition, the company noted: "You should expect us to continue to invest in enabling great LIVE experiences on Windows, including flying games, but we have nothing specific to announce at this time."
They shut down ACES Studio even while they were profitable. Even the ACES team didn't understand why Microsoft's Interactive Entertainment Business did what they did:
Aces was steadily evolving a property established in 1982, one synonymous with the entire genre; it had a loyal following and turned a steady profit. At around the same time, Microsoft also closed Ensemble Studios, despite a long history of strong sales and great games. It wasn't about talent. It was about "restructuring".
More than a year later, former Aces employee Kathie Flood is still baffled. "We simply don't know," she admits. "Both studio closures were puzzling, given the rabid enthusiasm of their audiences and the consistent profitability of their titles. Microsoft is a big company. The decision was made outside of the Aces Studio itself, and we weren't privy to the details [of] how or why the decision was made. In hindsight, it provided a tremendous opportunity for us to work on our own game designs, which we're extremely excited about."
Shutting down Flight Simulator development and apparently drastically changing the direction of the franchise is one of the worst decisions made at Microsoft in the last few years. I can't imagine what they must have been thinking at the Interactive Entertainment Business division. I wonder if they ran this by Steve Ballmer before they actually went ahead with the implementation, because I can't imagine Ballmer signing off on something as stupid as this. Or else he must have been misinformed.
With this decision they've managed to shut down and ruin one of the most successful gaming franchises in the history of the personal computer, not to mention THE BEST flight simulator that has ever existed. Nothing comes even close to Flight Simulator X; none of the competitors out there are even half as good.
When Microsoft announced the new Flight game in August 2010, I was somewhat relieved and had hope that we'd possibly get something even better than the older Flight Simulator game. So I decided to wait and give it a chance. But I've recently had the chance to try out a beta version of the new Microsoft Flight game for Windows Live, and the impression I got was that this new title sets back the Flight Simulator series by at least 12 years. Not to mention that it is far from being as good a simulation as Flight Simulator is. The graphics and realism have taken a serious hit and the game has been limited to just a few locations, compared to being able to fly all over the world in Flight Simulator. There's no real world weather simulation and no air traffic control. But the biggest set back has got to be the loss of backwards compatibility with all the content created by fans and third party developers for the Flight Simulator series. This includes thousands of aircrafts, scenery add-ons, plugins and other content. This was really the power of the Flight Simulator franchise and one of the most important reasons why it became so successful. This strategy is similar to why Windows became so popular in the early days, because of Microsoft encouraging developers to develop for the platform providing them with free tools to develop applications for it, which in turn attracted users. In Flight Simulator, even though the original game may have lacked the content people were looking for, such as specific aircrafts to fly, there were many fans and third-party developers who filled in the gaps and created tons of additional content which in turn made the game bigger and more complete. For example, I've been wanting a crop dusting aircraft in Flight Simulator for many, many years. I even wrote the team about it a few times. Very early versions of Flight Simulator had a crop dusting functionality in them and this was left out of the game in later versions. In the end, this aircraft was created by a fan and I finally got what I wanted and spent countless extra hours playing Flight Simulator X just because of this. If I didn't own Flight Simulator, I would have bought it just because it would allow me to fly this crop dusting aircraft. And this is all because of the strategy that was used during the development of the Flight Simulator franchise to not only allow, but also actively support users in generating more content and building on top of the game.
However, all of this, many years of work by thousands of fans and developers, this entire ecosystem around Flight Simulator, is now being destroyed by a couple of business executives at Microsoft who clearly know jack shit about the franchise. It is very sad to see this happening, and what is even sadder is that it is being replaced by something as pathetic as Microsoft Flight.
The technical beta forums at Microsoft are filled with people complaining about the direction Microsoft is heading with Microsoft Flight. People are very, very disappointed and are asking for Microsoft to bring back ACES Studio and start developing an upgrade to Microsoft Flight Simulator X. There's also a petition online for this, please sign it, as well as a Microsoft Flight Sucks Facebook page. Please let your voice be heard and also personally write Microsoft's CEO Steve Ballmer to get him to pay attention to this. Not only is it in Microsoft's best interest to not let Flight Simulator die, but it is also in the interest of the entire industry that exists around this franchise.
This year, 2012, would mark the 30th anniversary of Microsoft Flight Simulator which was first released in 1982. It is a shame that on the 30th anniversary of one of the biggest and most popular PC game franchises, Microsoft will launch Microsoft Flight, which is such a disgrace to what Flight Simulator represented and a huge disappointment.
This entry was published on Wednesday, January 25th, 2012 at 5:06 pm by Karel Donk and is filed under Information Technology reviews. You can use the permanent link to bookmark and/or share it.
Karel Donk's Blog » Xbox One Sucks: Why Don Mattrick can SUCK MY COCK (13/06/2013)
There are 18 responses. Follow any responses to this post through its comments RSS feed. You can leave a response, or trackback from your own site.
matt January 25th, 2012 at 8:24 pm
I highly recommend immediate rectal-cranial inversion therapy.
Either that or cough up the five hundred bucks Lockheed Martin is charging for Prepar3D and stop whining.
john January 28th, 2012 at 3:54 pm
you haven't played flight. You've played a beta inhouse build of the engine, not a final commercialized product. Do you remember the FSX beta? it sucked.
Third party content will come to microsoft flight, even if microsoft doesn't want it. PC gamers love to mod thier PC games 🙂
prepar3d can be had for $10 a month. just do that, or keep playing FSX.
Arno February 14th, 2012 at 7:51 am
@John
there will be NO 3rd party content. Microsoft is nog going to release the code. And if u dont have the code, no 3rd party addons.
This is just something to try to earn money quick. Microsoft is not going to take any risks, thats why they are only giving u Hawaii. So when this game fails they dont loose much money. MS Flight suck$.
Mathieu Landry February 26th, 2012 at 3:43 am
They were working on Train Simulator X and they shut it down too… Microsoft business direction really sucks. Money talkkkkkkkkkk!
gregory February 29th, 2012 at 10:30 pm
flight =bad joke.oh well back to fsx
TT March 1st, 2012 at 5:46 am
How could Microsoft have missed the mark so horribly with Flight? Its nothing short of insulting to those who have supported the franchise and essence of flight since the earliest days. Microsoft Flight is nothing more than an "app" designed for those who do not embrace the true meaning of flight.
JR March 2nd, 2012 at 6:33 am
FSX was already great and I was hoping FLIGHT would be even greater !
What a disappointment, what a bad joke, what an insult for anyone with a brain…
Flight is a badly designed TOY. it is NOT a flight simulator.
They're idiots… People will stick to FSX and won't spend a buck on this new crap called FLIGHT. and some might even migrate to X-Plane, who just released version 10 that's all we hoped from Flight…
shame on you, Microsoft.
sorry March 17th, 2012 at 12:28 pm
You seem to be angry irritating twat (judging from your article). "I wrote an angry email …" it's not ok to insult anyone because game wont be exactly as you want.
"I've been wanting a… …I even wrote the team about it a few times" I WANT SOMETHING SO GIVE IT TO ME (there are tons of people like you im sorry for developers who need to read it). Isn't it simplier to just learn how to add content to game (even if it will take couple months)?
And i don't think that microsoft slashed studio by being stupid. Maybe profit was to small or fan base wasn't growing. It don't make any sense to invest and risk big money in return for marginal profit.
brandon March 24th, 2012 at 2:14 pm
Surely if you would have tried Flight for more than 5 minutes, you would know it is not that bad. It's irritating when people try and pass on the idea that absolutely nothing good has come from it/
Karel Donk March 24th, 2012 at 2:20 pm
I've tried it, it's a piece of shit. Not even 10% of what Flightsim X is.
Michael April 8th, 2012 at 12:00 pm
I've been following Microsoft Flight Simulator since v1.0, in addition to this I've spent a great deal of time around aircraft and my brother is a commercial pilot flying for Emirates.
From a pilots point of view Flightsim X is pretty decent. The 3rd party aircraft and content can utilize the complex and realistic physics exposed by the game. Actually it is more than just a game, pilots use Flightsim X as a training tool, and it is useful with a PPL, or even as a commercial pilot.
My gripes with Flightsim X was as a non pilot, I found that there wasn't enough motivation for me to play flightsim x as a game. I enjoyed the realism, but without any missions in the game, it got dull quick (for me).
Another quirk was the graphics engine used in Flightsim X, dated and laggy even on the most recent hardware.
When I first heard about Flight I thought it would be the best game ever. I thought it would combine all the good elements of Flightsim X, and at the same time introduce a better graphics engine, and missions.
I downloaded it 5 minutes ago, and am furious. What kind of a joke is this? It feels like a play station game, and not a flight simulator.
I was really expecting flight sim x with the ability to transport passengers and cargo with better graphics and a more realistic world to fly in.
What I ended up getting was some bullshit flying through hoops. Could not even get back to menu for free flight, just a pile of shit.
Jason April 11th, 2012 at 9:52 am
I couldn't agree more with the writer of this blog. But evidently we Flight seems to have its defenders. They are all over the place, telling us how good it is, and how wrong we are for not saying so, and trying to bump up review scores by giving it 10/10 and so on. But fortunately they are in a minority. The average of user reviews tend to be less than 5/10. So I don't know who this tribe of defenders are. Perhaps they Microsoft shills, or perhaps they are just just plain stupid. They probably all own Zunes too. But it beats me why anyone who enjoyed FS2004 and FSX, with all it's incredible add-ons, would want to defend a move like this.
The most stupid (yes STUPID) thing Microsoft have done here is to monopolize the DLC, and prohibit modding and freeware. At least if they had left it open to 3rd party developers, there would have been a chance.
moosh May 15th, 2012 at 5:10 am
The new game (2012 edition) although its free, have very poor community of multiplayer. the maps are very limited and also the number of aircrafts.
and what the hell is the "AeroCache"? I belive that microsoft might release a new add-in by themselves.
FreeQuest August 29th, 2012 at 4:00 am
and now flight itself is dead and with it the entire microsoft flight simulator franchise … BTW your recaptha is nearly unreadable
Karel Donk August 29th, 2012 at 4:05 am
No surprise to me. Flight fucking blows.
Mark February 7th, 2013 at 10:38 pm
The reason I believe they shut down the ACES studio is due to the fact that Flight Simulator is a badly programmed video game and the 'experts' that created it know full well the sim will always be full of bugs and technical issues caused by add ons and the like.
I have played it for years and I am always stressed out with the ongoing problems it has within itself.
Basically they have done us all a jolly good favour by closing ACES and I won't have anything whatsoever to do with microsoft flight. Big deal about time this virus invested rubbish was taken out of circulation!!! Good riddance!!!
Karel Donk February 8th, 2013 at 11:05 pm
I've been playing Flight Simulator for a very long time, and it's a great game. No software is free of bugs, but Flight Simulator always worked flawlessly for me. So I'm not sure what issues you had, but it may have been caused by badly written add-ons or perhaps faulty hardware.
FSX Gamer November 17th, 2013 at 4:21 am
Yes, I don't even think of downloading MS flightcrap. Better off paying $$$ for FSX addons than paying $$$ for flightcrap DLCs
Enter your email address to subscribe to this blog and receive notifications of new posts by email. Note that this subscription is different from my newsletter.
© 2020 Karel Donk. All rights reserved.
Software / Design / Photography / About / Blog
Your success, our goal.

redpajama_set_name: RedPajamaCommonCrawl


Mackinac Island and the Straits of Mackinac have always been a very popular Great Lakes Boating destination. Mackinac Island is readily accessible via private watercraft from a number of northern Michigan ports.
Located at the intersection of Lake Michigan and Lake Huron, the Straits area has three large marinas providing a variety of services to transient pleasure craft.
Mackinac Island State Dock has 70 transient slips, pumpout, 30 amp elec., gas & diesel, water, cable TV, internet, heads, showers, laundry, swimming pool & whirlpool, recreation area, grills, picnic tables, dog walk. Phone 906-847-3561. The Mackinac Island Yacht Club is located directly across the street.
St. Ignace Public Marina has 136 slips, state-of-the-art facility, pumpout, 30 & 50 amp elec., 200 amp for large slips, gas & diesel, water, cable TV, internet, wells up to 120 ft., 24-hour security, heads, showers, laundry, grills, picnic tables, dog run. Phone 906-643-8131 or 800-447-2757.
Mackinaw City Marina has 78 transient slips, pumpout, 30 & 50 amp elec., gas & diesel, water, cable TV, internet, heads, showers, laundry, swimming pool & whirlpool, rec area, grills, picnic tables, dog walk.
Click here for reservations for moorings at these Great Lakes boating marinas.
Hazelton Street – four boat ramps, a protected breakwall, paved parking, fish cleaning station and restrooms.
City Dock #3 – one double-wide ramp, a protected breakwall, paved parking, restrooms, picnic area with grills, pavilion, playground.
There are two historic Great Lakes sailing races that all boaters love to follow each summer - the famous Chicago Mackinac Race and the Bayview Mackinac Race. It's a great time to be on the Island when these sailors are in town!
Your Great Lakes boating journey can encompass over 10,900 miles of coastline including Canada's border and with the U.S. border being around 3,288 miles. The Great Lakes are really inland seas and have one-fifth of the world's fresh surface water within its shores.
If you love antique wooden boats then you will want to plan a boating excursion to the Annual Antique Wooden Boat Show in the beautiful Les Cheneaux Islands which is a northern Michigan boating paradise.
There are many different choices for lodging including many quaint bed and breakfasts on the Island. Marcia and Kathy's top six Mackinac Island hotels are great choices for weary boaters wanting some top notch amenities to sooth their bodies. Of course there is always the five star and historic Grand Hotel which is one of the top hotel resorts in the world.
If you are staying just for the day there are many Mackinac Island restaurants walking distance from the marina. You can rent bikes for a few hours in order to explore the Island by following the road around it or to travel inland to see its natural beauty. If you are into history make sure to explore Fort Mackinac. There's fun shopping and there are two golf courses to enjoy.
INSIDER TIP: A wonderful website to learn more about Great Lakes boating adventures is the Great Lakes Cruising Club. Become a member to keep abreast of all the exciting ports of call in northern Michigan.
Return to Home Page from "Great-Lakes-Boating"


Monday morning we hired a car and set out for our next stop Bicheno. Bicheno is a small sleepy sea side resort known for it's penguin spotting and it's closeness to Freycinet National Park. (He was French so the "T" is silent. ) We had Monday to ourselves as our friends from Ireland were not due to arrive until Tuesday afternoon. We therefore decided to drive to Cape Tourville in Freycinet. A lighthouse within the park which boasted some nice views and we weren't disappointed. Before you enter the park you have to get a parks pass, which is a formality as I do not think there is a restriction on numbers. It costs about $20 for a day pass and $50 for an extended pass.
That evening we ate at the Sea Life Centre Restaurant. If you time your visit to coincide with dusk you will get to see the penguins coming into their rooks at night. There are also penguin tours available.
Here's some pics taken from the Cape on a dull enough day.
On our way up to Bicheno we stopped off in Swansea and had something to eat at the Swansea Wine & Wool Centre. The food was good and fortunately, for me, they didn't sell wool, but only finished garments. We weren't long there when we began to realise that our mobile phones had stopped working. I enquired at the restaurant about a signal only to be informed that Vodafone have no reception up the entire East Coast. This was problematic as we were due to meet our friends and had only made tentative arrangements.
Our friends Norah and Ian did manage to find us however and joined us on Tuesday evening. We had booked a Kayaking trip and were wondering if the weather would hold, and hold it did, but just long enough for the trip to go ahead and for us to end up soaking wet. The kayaking set off from Coles Bay and headed around the bay hugging the maintains until finally you have the cross the Bay itself to get back where you started from. Suffice it to say Sharon and I were not naturals and found it hard work. I think we nearly wrecked the kayak twice. Here's some pics, taken with our underwater camera, from the only respite you get on the trip.
During our break the guides explained how sand is normally made up of different types of stone that erode over time and that really old beaches have squeaky sand. This is because really old sand contains only quartz, the most durable stone, which eventually becomes spherical- in much the same way as a pearl I presume. When the spherical grains rub off each other they make a squeaky noise! At least that's how I remember the story. How is this relevant. You'll just have to wait and see.
The next day we set out for Freycinet National Park yet again but this time to go on a 14km tramp that takes in Wineglass Bay. Before we got there however we decided to stop off for petrol in Coles Bay. We pulled up at the only free pump and I got out and waited for the cashier to start the pump once, I presumed, the person before me had paid. I was there for a while and started staring into the shop, as a none so subtle hint, when the cashier came out to the door and yelled. "That pump never works in the morning." I was dumb founded. Here I was, a total stranger, like so many in Coles Bay and I was expected to know that the pump in question never works in the mornings! My psychic powers let me down that morning I can tell you. The guy on the pump beside me, somewhat bemused too, suggested my pump was a bit like himself in the mornings! I got back in the car and waited for the other pump to clear.
We eventually got to Parsons Cove the starting point of our tramp. Once registered we set off. Estimated time of arrival back about four hours later. The tramp is heavy going especially the first part up to the Wineglass Bay lookout. Unfortunately the day itself was pretty dull. Here's some pics from the lookout and one on the beach.
We had dragged our swim gear all along the tramp so we could have a swim in Wineglass Bay and yes the sand was squeaky. In fact so squeaky it had the same affect as rubbing Styrofoam together, which sends a shiver down my spine.
We were all togged off and the three others were in the water- I was going to get in honest. When two people who happened to be passing by suggested that the others should get out of the water. I laughed and watched for their expression to change, eventually asking, somewhat concerned, if they were serious? It was at this point that the guy took the time to point out the Blue Bottle Jellyfish or, as it is also known, the Portuguese Man of War, nearest to me stating they were everywhere on the beach. I knew the answer to the next question but felt compelled to ask it nonetheless. Do they sting? I enquired and as the reply was a resounding affirmative, I decided to relay the good news to my fellow paddlers. In an instant the reward, awaiting us for our long tramp, was dashed. My last few days in Australia and I still hadn't swam in the sea.
In case you don't believe me here's a pic, there small but they pack a punch.
After Wineglass Bay you walk to Hazards Beach and from there back to Parson's Cove. Here's a few pics of Hazard's Beach including one of a Wallaby.
When we got back to the car park we signed out and sat down to rehydrate. They say to bring two and a half litres of water per person and it is definitely a good idea as we didn't bring enough. At the car park there were some more Wallabies.
Ian and myself set off the next day for Hobart as we were going on a fishing "stag" trip early Thursday morning from Port Arthur. The sea was rough, but some of the guys managed to catch some yellow fin tuna fish. Which was slightly ironic, as after I had deposited my breakfast into the sea, I began to marvel at the fine array of yellow colours my own body could produce. I had to ask the boat to come back into port after only about two hours, and I was best man! Enough said. To be fair though, five out of the ten guys decided to get off.
The girls stayed on in Bicheno and after a recommendation from a very nice waiter at the Diamond Restaurant, decided to visit Diamond Island, tide permitting the next day. Apparently the island was really worth the trip, especially if you want to get up close and personal with some penguins. Here's a few abstract pics of them crossing over to the Island, almost impressionist in quality.
When the girls were on the beach waiting to cross over to the island they met the waiter again who pointed out Jan Cameron, the founder of Kathmandu, walking down the beach. He explained how she had bought a lot of property around Bicheno in an effort to preserve it's charm.


text:
Production, Characterization, and Applications
Yong Sik Ok
Sophie M. Uchimiya
Scott X. Chang
Nanthi Bolan
Published September 30, 2020 by CRC Press
438 Pages 5 Color & 63 B/W Illustrations
Published August 28, 2015 by CRC Press
Format Paperback Hardback VitalSource eBook
Biochar: Production, Characterization, and Applications
Encompassing high priority research areas such as bioenergy production, global warming mitigation, and sustainable agriculture, biochar has received increased worldwide interest in the past decade.
Biochar: Production, Characterization, and Applications covers the fundamentals of biochar including its concept, production technology, and characterization. The book builds on this foundation by providing examples of state-of-the-art biochar application technology in agronomy and environmental sciences, along with detailed case studies.
Edited by a group of well-known biochar experts and including chapters written by a group of international experts in their field, this valuable resource can be used both as a textbook for graduate courses or as a handbook for policy makers and practitioners in the field.
GENERAL OVERVIEW. Definitions and Fundamentals of Biochar. Biochar: State of the Art. BIOCHAR PRODUCTION AND CHARACTERIZATION. Biochar Production Technology: An Overview. Chemical, Physical, and Surface Characterization of Biochar. ENVIRONMENTAL APPLICATIONS. Biochar for Inorganic Contaminant Management in Soil. Biochar for Organic Contaminant Management in Soil. Biochar for Inorganic Contaminant Management in Waste and Wastewater. Biochar for Organic Contaminant Management in Water and Wastewater. AGRONOMIC APPLICATIONS AND CLIMATE CHANGE MITIGATION. Biochar Effects on Soil Fertility and Nutrient Cycling. Biochar Substrate for Hydroponic Vegetable Production. Effects of Straw-Derived Biochar on Rice Paddy. Biochar Effects on Soil Organic Carbon Storage. Biochar Effects on Greenhouse Gas Emissions. FUTURE PROSPECTS. Emerging Applications of Biochar.
Yong Sik Ok, professor and director, Korea Biochar Research Center, Department of Biological Environment, Kangwon National University, Chuncheaon, Korea
Sophie M. Uchimiya, research chemist, U.S. Department of Agriculture-Agricultural Research Service (USDAARS), Southern Regional ResearchCenter in New Orleans, Louisiana
Scott X. Chang, professor in forest soils and nutrient dynamics, University of Alberta, Edmonton, Canada
Nanthi Bolan, professor of environmental science, University of Newcastle, Australia