import calendar
import glob
import numpy as np
import openapi_client as dbitApi
import os
import pandas as pd

from datetime import datetime

api = dbitApi.MarketDataApi()

def format_datetime_to_expiry(date):
    if os.name == 'nt':
        return datetime.strftime(date, '%#d%b%y').upper()
    else:
        return datetime.strftime(date, '%-d%b%y').upper()

def get_near_next_terms(now):
    c = calendar.Calendar(firstweekday=calendar.MONDAY)

    this_month_cal = c.monthdatescalendar(now.year, now.month)
    this_fridays = [datetime(day.year, day.month, day.day, 8, 0, 0)
                    for week in this_month_cal for day in week
                    if day.weekday() == calendar.FRIDAY and day.month == now.month
                    and datetime(day.year, day.month, day.day, 8, 0, 0) >= now]

    next_year = now.year if now.month < 12 else now.year + 1
    next_month = now.month + 1 if now.month < 12 else 1

    next_month_cal = c.monthdatescalendar(next_year, next_month)
    next_fridays = [datetime(day.year, day.month, day.day, 8, 0, 0)
                    for week in next_month_cal for day in week
                    if day.weekday() == calendar.FRIDAY and day.month == next_month
                    and datetime(day.year, day.month, day.day, 8, 0, 0) >= now]

    fridays = this_fridays + next_fridays

    near_term, next_term = fridays[0], fridays[1]

    return (format_datetime_to_expiry(near_term), format_datetime_to_expiry(next_term), near_term, next_term)

def get_index(currency='BTC'):
    try:
        index_result = api.public_get_index_get(currency)['result'][currency]
        return index_result
    except dbitApi.exceptions.ApiException as e:
        print(e)
        #logger.exception('Exception when calling MarketDataApi->public_get_instruments_get!')
        exit()

def get_instruments_with_expiry(expiry, currency='BTC', kind='option', expired='false'):
    try:
        instrument_result = api.public_get_instruments_get(currency, kind=kind, expired=expired)['result']
        return [instrument['instrument_name'] for instrument in instrument_result if expiry in instrument['instrument_name']]
    except dbitApi.exceptions.ApiException as e:
        print(e)
        #logger.exception('Exception when calling MarketDataApi->public_get_instruments_get!')
        exit()

def get_ticker(instrument):
    try:
        instrument_result = api.public_ticker_get(instrument)['result']
        return instrument_result
    except dbitApi.exceptions.ApiException as e:
        print(e)
        #logger.exception('Exception when calling MarketDataApi->public_get_instruments_get!')
        exit()

def get_bids_asks(near_list, next_list):
    near_calls = dict()
    near_puts = dict()
    next_calls = dict()
    next_puts = dict()

    for instrument in near_list:
        data = get_ticker(instrument)
        best_bid, best_ask = data['best_bid_price'], data['best_ask_price']
        strike, cp = int(instrument.split('-')[2]), instrument.split('-')[3]

        if cp == 'C':
            near_calls[strike] = {'best_bid': best_bid, 'best_ask': best_ask}
        elif cp == 'P':
            near_puts[strike] = {'best_bid': best_bid, 'best_ask': best_ask}
        else:
            print(f'Error {instrument}')

    for instrument in next_list:
        data = get_ticker(instrument)
        best_bid, best_ask = data['best_bid_price'], data['best_ask_price']
        strike, cp = int(instrument.split('-')[2]), instrument.split('-')[3]

        if cp == 'C':
            next_calls[strike] = {'best_bid': best_bid, 'best_ask': best_ask}
        elif cp == 'P':
            next_puts[strike] = {'best_bid': best_bid, 'best_ask': best_ask}
        else:
            print(f'Error {instrument}')

    near_calls_df = pd.DataFrame.from_dict(near_calls, orient='index').sort_index().replace(0, np.nan)
    near_puts_df = pd.DataFrame.from_dict(near_puts, orient='index').sort_index().replace(0, np.nan)
    next_calls_df = pd.DataFrame.from_dict(next_calls, orient='index').sort_index().replace(0, np.nan)
    next_puts_df = pd.DataFrame.from_dict(next_puts, orient='index').sort_index().replace(0, np.nan)

    return near_calls_df, near_puts_df, next_calls_df, next_puts_df

def calculate_indices(time, near_datetime, next_datetime, const_mature_days, R, near_calls_df, near_puts_df, next_calls_df, next_puts_df):
    # Compute strikes with min call/put price difference
    near_prices = pd.DataFrame(index=near_calls_df.index)
    near_prices['call_price'] = (near_calls_df['best_bid'] + near_calls_df['best_ask']) / 2
    near_prices['put_price'] = (near_puts_df['best_bid'] + near_puts_df['best_ask']) / 2
    near_prices['abs_diff'] = abs(near_prices['call_price'] - near_prices['put_price'])

    min_near_strike = near_prices['abs_diff'].idxmin()
    min_near_diff = near_prices.loc[min_near_strike].abs_diff

    next_prices = pd.DataFrame(index=next_calls_df.index)
    next_prices['call_price'] = (next_calls_df['best_bid'] + next_calls_df['best_ask']) / 2
    next_prices['put_price'] = (next_puts_df['best_bid'] + next_puts_df['best_ask']) / 2
    next_prices['abs_diff'] = abs(next_prices['call_price'] - next_prices['put_price'])

    min_next_strike = next_prices['abs_diff'].idxmin()
    min_next_diff = next_prices.loc[min_next_strike].abs_diff

    n1 = (near_datetime - time).total_seconds() / 60
    n2 = (next_datetime - time).total_seconds() / 60
    nY = 525600
    n = const_mature_days * 24 * 60

    t1 = n1/nY
    t2 = n2/nY

    # Compute forward prices and at-the-money strikes
    f1 = min_near_strike + np.e**(R*t1) * min_near_diff
    k0_1 = max([strike for strike in near_prices.index if strike <= min_near_strike])

    f2 = min_next_strike + np.e**(R*t2) * min_next_diff
    k0_2 = max([strike for strike in next_prices.index if strike <= min_next_strike])

    near_otm_puts_df = near_puts_df.loc[:k0_1].iloc[:-1]
    near_otm_calls_df = near_calls_df.loc[k0_1:].iloc[1:]
    next_otm_puts_df = next_puts_df.loc[:k0_2].iloc[:-1]
    next_otm_calls_df = next_calls_df.loc[k0_2:].iloc[1:]

    near_otm_puts_df = near_otm_puts_df.sort_index(ascending=False)
    near_otm_puts_df = near_otm_puts_df.assign(zero_bid=lambda df: (df['best_bid'] == 0).astype(int))
    near_otm_puts_df['zero_bid_cumsum'] = near_otm_puts_df['zero_bid'].cumsum()
    near_otm_puts_df = near_otm_puts_df[(near_otm_puts_df['zero_bid_cumsum'] <= 2) & (near_otm_puts_df['best_bid'] > 0)]

    near_otm_calls_df = near_otm_calls_df.assign(zero_bid=lambda df: (df['best_bid'] == 0).astype(int))
    near_otm_calls_df['zero_bid_cumsum'] = near_otm_calls_df['zero_bid'].cumsum()
    near_otm_calls_df = near_otm_calls_df[(near_otm_calls_df['zero_bid_cumsum'] <= 2) & (near_otm_calls_df['best_bid'] > 0)]

    next_otm_puts_df = next_otm_puts_df.sort_index(ascending=False)
    next_otm_puts_df = next_otm_puts_df.assign(zero_bid=lambda df: (df['best_bid'] == 0).astype(int))
    next_otm_puts_df['zero_bid_cumsum'] = next_otm_puts_df['zero_bid'].cumsum()
    next_otm_puts_df = next_otm_puts_df[(next_otm_puts_df['zero_bid_cumsum'] <= 2) & (next_otm_puts_df['best_bid'] > 0)]

    next_otm_calls_df = next_otm_calls_df.assign(zero_bid=lambda df: (df['best_bid'] == 0).astype(int))
    next_otm_calls_df['zero_bid_cumsum'] = next_otm_calls_df['zero_bid'].cumsum()
    next_otm_calls_df = next_otm_calls_df[(next_otm_calls_df['zero_bid_cumsum'] <= 2) & (next_otm_calls_df['best_bid'] > 0)]

    near_calc_strikes_df = pd.DataFrame(index=near_prices.index)
    near_calc_strikes_df['price'] = (near_otm_puts_df['best_bid'] + near_otm_puts_df['best_ask']) / 2
    near_calc_strikes_df['price'] = near_calc_strikes_df.price.combine_first((near_otm_calls_df['best_bid'] + near_otm_calls_df['best_ask']) / 2)
    near_calc_strikes_df.at[k0_1] = (near_prices.loc[k0_1].call_price + near_prices.loc[k0_1].put_price) / 2
    near_calc_strikes_df = near_calc_strikes_df.dropna()

    next_calc_strikes_df = pd.DataFrame(index=next_prices.index)
    next_calc_strikes_df['price'] = (next_otm_puts_df['best_bid'] + next_otm_puts_df['best_ask']) / 2
    next_calc_strikes_df['price'] = next_calc_strikes_df.price.combine_first((next_otm_calls_df['best_bid'] + next_otm_calls_df['best_ask']) / 2)
    next_calc_strikes_df.at[k0_2] = (next_prices.loc[k0_2].call_price + next_prices.loc[k0_2].put_price) / 2
    next_calc_strikes_df = next_calc_strikes_df.dropna()

    near_sum = 0
    for i in range(len(near_calc_strikes_df)):
        row = near_calc_strikes_df.iloc[i]
        if i == 0:
            deltaKi = near_calc_strikes_df.iloc[i+1].name - row.name
        elif i == len(near_calc_strikes_df) - 1:
            deltaKi = row.name - near_calc_strikes_df.iloc[i-1].name
        else:
            deltaKi = (near_calc_strikes_df.iloc[i+1].name - near_calc_strikes_df.iloc[i-1].name) / 2

        near_sum += deltaKi/(row.name ** 2) * np.e**(R*t1) * row.price

    next_sum = 0
    for i in range(len(next_calc_strikes_df)):
        row = next_calc_strikes_df.iloc[i]
        if i == 0:
            deltaKi = next_calc_strikes_df.iloc[i+1].name - row.name
        elif i == len(next_calc_strikes_df) - 1:
            deltaKi = row.name - next_calc_strikes_df.iloc[i-1].name
        else:
            deltaKi = (next_calc_strikes_df.iloc[i+1].name - next_calc_strikes_df.iloc[i-1].name) / 2

        next_sum += deltaKi/(row.name ** 2) * np.e**(R*t2) * row.price

    sigma1 = ((2/t1) * near_sum) - (1/t1)*((f1/k0_1 - 1)**2)
    sigma2 = ((2/t2) * next_sum) - (1/t2)*((f2/k0_2 - 1)**2)

    VXBT = 100 * np.sqrt(((t1*sigma1)*((n2-n)/(n2-n1)) + (t2*sigma2)*((n-n1)/(n2-n1)))*(nY/n))

    omega = ((n2-nY)/(n2-n1))*n
    sigma1_a = sigma1 * (f1**-2)
    sigma2_a = sigma2 * (f2**-2)

    GVXBT = np.sqrt(omega*t1*sigma1 + (1-omega)*t2*sigma2)
    AVXBT = np.sqrt(omega*t1*sigma1_a + (1-omega)*t2*sigma2_a)

    return VXBT, GVXBT, AVXBT

def get_indices(maturity=7, rate=0, live=True, time=None, dfs=None):
    if live:
        now = datetime.now()
        near_expiry, next_expiry, near_datetime, next_datetime = get_near_next_terms(now)
        near_instruments = get_instruments_with_expiry(near_expiry)
        next_instruments = get_instruments_with_expiry(next_expiry)

        near_calls_df, near_puts_df, next_calls_df, next_puts_df = get_bids_asks(near_instruments, next_instruments)

        VXBT, GVXBT, AVXBT = calculate_indices(now, near_datetime, next_datetime, maturity, rate, near_calls_df, near_puts_df, next_calls_df, next_puts_df)

    else:
        near_expiry, next_expiry, near_datetime, next_datetime = get_near_next_terms(time)
        VXBT, GVXBT, AVXBT = calculate_indices(time, near_datetime, next_datetime, maturity, rate, dfs[0], dfs[1], dfs[2], dfs[3])

    return VXBT, GVXBT, AVXBT

if __name__ == '__main__':
    print(get_indices())


#!/usr/bin/env python3

import os
import re
import time

import click
from netCDF4 import Dataset
import numpy as np
import numpy.ma as ma
import osr

import projections.geotools as geotools
import projections.utils as utils

BINS = 52

def sum_layers(ds, idx, layers, out):
  out.fill(0)
  for l in layers:
    out += ds.variables[l][idx]
  return

def find_frac(values, tot, frac):
  frac.fill(1)
  ## FIXME: need np.clip() because pos - neg < 0
  frac -= np.clip(ma.where(values[-1] > 0, tot / values[-1], 0), 0, 1)
  #frac -= np.clip(tot / values[-1], 0, 1)
  return

def dorem(values, remove, frac):
  find_frac(values, remove, frac)
  values[1:-1] *= np.broadcast_to(frac, values[1:-1].shape)
  return

def to_year(scenario, idx):
  if scenario == 'historical':
    return idx + 850
  return idx + 2015

def asserts(state, idx, name, values, atol):
  assert np.all(values[0] <= 1.0 + atol), 'current > 1'
  assert np.all(values[0] >= 0.0 - atol), 'current < 0'
  secd = state.variables[name][idx]
  assert np.allclose(values[-1] - secd, 0, atol=atol)

def write_data(out, fnf, idx, values):
  out.variables['secdy%s' % fnf][idx, :, :] = values[0:30].sum(axis=0)
  out.variables['secdi%s' % fnf][idx, :, :] = values[30:50].sum(axis=0)
  out.variables['secdm%s' % fnf][idx, :, :] = values[50]

def write_bins(out, vname, values):
  # FIXME: verify masked values are written and read correctly.
  out.variables[vname][:] = values
  return values

def init_values(state, vname, start_index, mask):
  shape = state.variables[vname].shape
  values = ma.zeros((BINS, shape[1], shape[2]),
                    dtype=state.variables[vname].dtype,
                    fill_value=-9999)
  values.mask = np.broadcast_to(mask == 1.0, values.shape)
  values[-1] = state.variables[vname][start_index]
  values[-2] = state.variables[vname][start_index]
  return values

def roll_values(values):
  values[-3] += values[-2]
  values[-2] = values[0:-2].sum(axis=0)
  return np.roll(values, 1, 0)

def neg_re(fnf):
  return r'secd{fnf}_to_'.format(fnf=fnf)

def pos_re(fnf):
  return r'^(?!secd{fnf}).*_to_secd{fnf}$|prim{fnf}_harv$'.format(fnf=fnf)

@click.command()
@click.option('--scenario', type=click.Choice(utils.luh2_scenarios() +
                                              ('all', )),
              default='all',
              help='Which LUH2 scenario to run (default: all)')
@click.option('--outdir', type=click.Path(file_okay=False),
              default='/out/luh2',
              help='Output directory (default: /out/luh2)')
@click.option('--start-index', type=int, default=0,
              help='Start from given index skipping earlier years (default: 0)')
def doit(scenario, outdir, start_index=0):
  static = Dataset(os.path.join(utils.luh2_dir(), 'staticData_quarterdeg.nc'))
  icwtr = static.variables['icwtr'][:, :]
  atol = 5e-5

  variables = tuple([(x % fnf, 'f4', '1', -9999, 'time')
                     for fnf in ('f', 'n')
                     for x in ('secd%s%%s' % n for n in ('y', 'i', 'm'))] +
                    [('bins%s' % fnf, 'f4', '1', -9999, 'bins')
                     for fnf in ('f', 'n')])
  baselinef = None
  baselinen = None

  if scenario == 'all':
    # historical must be the first scenario processed
    scenarios = sorted(utils.luh2_scenarios())
  else:
    scenarios = [scenario]

  for scenario in scenarios:
    oname = os.path.join(outdir, 'secd-%s.nc' % scenario)
    tname = utils.luh2_transitions(scenario)
    sname = utils.luh2_states(scenario)
    if not (os.path.isfile(tname) and os.path.isfile(sname)):
      click.echo("skipping %s" % scenario)
      continue
    click.echo('%s -> %s' % (scenario, oname))

    with Dataset(oname, 'w') as out:
      click.echo(sname)
      click.echo(tname)
      with Dataset(tname) as trans:
        with Dataset(sname) as state:
          _ = init_nc(out, state, variables)
          if scenario == 'historical':
            # Create a 3-D array to hold the last 50 years (plus 2)
            valuesf = init_values(state, 'secdf', start_index, icwtr)
            valuesn = init_values(state, 'secdn', start_index, icwtr)
          elif baselinef is None or baselinen is None:
            with Dataset(os.path.join(outdir, 'secd-historical.nc')) as hist:
              valuesf = hist.variables['binsf'][:]
              valuesn = hist.variables['binsn'][:]
          else:
            valuesf = baselinef.copy()
            valuesn = baselinen.copy()

          # Write initial data to output.
          valuesf[0].fill(0)
          valuesn[0].fill(0)
          write_data(out, 'f', start_index, valuesf)
          write_data(out, 'n', start_index, valuesn)

          remove = ma.empty_like(valuesf[0])
          frac = ma.empty_like(valuesf[0])
          posf = tuple(filter(lambda x: re.match(pos_re('f'), x),
                              trans.variables.keys()))
          posn = tuple(filter(lambda x: re.match(pos_re('n'), x),
                              trans.variables.keys()))
          negf = tuple(filter(lambda x: re.match(neg_re('f'), x),
                              trans.variables.keys()))
          negn = tuple(filter(lambda x: re.match(neg_re('n'), x),
                              trans.variables.keys()))
          click.echo("  " + ', '.join(posf))
          click.echo("  " + ', '.join(negf))
          click.echo("  " + ', '.join(posn))
          click.echo("  " + ', '.join(negn))
          for idx in range(start_index, trans.variables['time'].shape[0]):
            click.echo("  year %d" % to_year(scenario, idx))
            # Compute transitions from / to secondary.
            sum_layers(trans, idx, negf, remove)
            sum_layers(trans, idx, posf, valuesf[0])
            # Adjust secondary history
            dorem(valuesf, remove, frac)

            # Repeat for non-forested
            sum_layers(trans, idx, negn, remove)
            sum_layers(trans, idx, posn, valuesn[0])
            dorem(valuesn, remove, frac)

            # Check consistency of data.
            asserts(state, idx, 'secdf', valuesf, atol)
            asserts(state, idx, 'secdn', valuesn, atol)

            # Write data to output.
            write_data(out, 'f', idx + 1, valuesf)
            write_data(out, 'n', idx + 1, valuesn)

            # Rotate the array.
            valuesf = roll_values(valuesf)
            valuesn = roll_values(valuesn)

      if scenario == 'historical':
        baselinef = write_bins(out, 'binsf', valuesf).copy()
        baselinen = write_bins(out, 'binsn', valuesn).copy()
        start_index = 0

def init_nc(dst_ds, src_ds, variables):
  # Set attributes
  dst_ds.setncattr('Conventions', u'CF-1.5')
  dst_ds.setncattr('GDAL', u'GDAL 1.11.3, released 2015/09/16')

  # Create dimensions
  dst_ds.createDimension('time', None)
  dst_ds.createDimension('lat', len(src_ds.variables['lat']))
  dst_ds.createDimension('lon', len(src_ds.variables['lon']))
  dst_ds.createDimension('bins', BINS)

  # Create variables
  times = dst_ds.createVariable("time", "f8", ("time"), zlib=True,
                                least_significant_digit=3)
  latitudes = dst_ds.createVariable("lat", "f4", ("lat"), zlib=True,
                                    least_significant_digit=3)
  longitudes = dst_ds.createVariable("lon", "f4", ("lon"), zlib=True,
                                     least_significant_digit=3)
  crs = dst_ds.createVariable('crs', "S1", ())

  # Add metadata
  dst_ds.history = "Created at " + time.ctime(time.time())
  dst_ds.source = "secd-dist.py"
  latitudes.units = "degrees_north"
  latitudes.long_name = 'latitude'
  longitudes.units = "degrees_east"
  longitudes.long_name = "longitude"
  times.units = "years since 850-01-01 00:00:00.0"
  times.calendar = "gregorian"
  times.standard_name = "time"
  times.axis = 'T'

  # Assign data to variables
  latitudes[:] = src_ds.variables['lat'][:]
  longitudes[:] = src_ds.variables['lon'][:]
  times[:] = src_ds.variables['time'][:]

  srs = osr.SpatialReference()
  srs.ImportFromWkt(geotools.WGS84_WKT)
  src_trans = (-180.0, 0.25, 0.0, 90.0, 0.0, -0.25)
  crs.grid_mapping_name = 'latitude_longitude'
  crs.spatial_ref = srs.ExportToWkt()
  crs.GetTransform = ' '.join(tuple(map(str, src_trans)))
  # FIXME: Attribute getters don't work in python3 or GDAL2
  crs.longitude_of_prime_meridian = geotools.srs_get_prime_meridian(srs)
  crs.semi_major_axis = geotools.srs_get_semi_major(srs)
  crs.inverse_flattening = geotools.srs_get_inv_flattening(srs)

  out = {}
  for name, dtype, units, fill, dimension in variables:
    dst_data = dst_ds.createVariable(name, dtype,
                                     (dimension, "lat", "lon"), zlib=True,
                                     least_significant_digit=4,
                                     fill_value=fill)
    dst_data.units = units
    dst_data.grid_mapping = 'crs'
    out[name] = dst_data
  return out

if __name__ == '__main__':
#pylint: disable-msg=no-value-for-parameter
  doit()
#pylint: enable-msg=no-value-for-parameter
  click.echo('done')


# This module needs: numpy, matplotlib, scipy, ase, spglib
import numpy
from futile.Utils import write as safe_print

AU_eV = 27.21138386

def get_ev(ev, keys=None, ikpt=1):
    """Get the correct list of the energies for this eigenvalue."""
    res = False
    if keys is None:
        ener = ev.get('e')
        spin = ev.get('s')
        kpt = ev.get('k')
        if not kpt and ikpt == 1:
            kpt = True
        elif kpt and kpt != ikpt:
            kpt = False
        if ener and (spin == 1 or not spin):
            if kpt:
                res = [ener]
        elif ener and spin == -1:
            if kpt:
                res = [None, ener]
    else:
        for k in keys:
            if k in ev:
                res = ev[k]
                if not isinstance(res, list):  # type(res) != type([]):
                    res = [res]
                break
    return res

def astruct_to_cell(astruct):
    """
    Convert the astruct information as parsed from the module Logfiles into
    the cell structure as needed from spglib
    """
    import numpy
    celltmp = [a if a != float('inf') else 1.0 for a in astruct['cell']]
    lattice = numpy.diag(celltmp)
    pos = [[a/b if b != float('inf') else 0.0
            for a, b in zip(list(at.values())[0], celltmp)]
           for at in astruct['positions']]
    atoms = [list(at.keys())[0] for at in astruct['positions']]
    ianames, iatype = numpy.unique(atoms, return_inverse=True)
    return (lattice, pos, iatype)

class BandArray(numpy.ndarray):
    """
    Defines the array of data for one band. It is a dictionary which contains
    a numpy array for both spin channels.
    """
    def __new__(cls, *args, **kwargs):  # logdata,ikpt=0,kpt=(0.0,0.0,0.0):
        "Takes the data from the logfile and convert it"
        datain = kwargs.get("data", None)
        if datain is not None:
            evs = datain
            shape0 = len(evs)
            norbs = list(map(len, evs))
            if len(norbs) == 1:
                norbs = [norbs[0], 0]
        else:
            evs = [[], []]
            logdata = kwargs.get("logdata", args[0])
            ikpt = kwargs.get("ikpt", 1 if len(args) < 2 else args[1])
            # Ugly patch to detect proper k point in Davidson...
            prev_vrt = True
            cur_ikpt = 0
            # End of ugly patch
            for ev in logdata:
                occ = get_ev(ev, ['e_occ', 'e_occupied'], ikpt=ikpt)
                vrt = get_ev(ev, ['e_vrt', 'e_virt'], ikpt=ikpt)
                eigen = occ or vrt
                # Ugly patch to detect proper k point in Davidson...
                if occ and prev_vrt:
                    cur_ikpt += 1
                prev_vrt = vrt
                if eigen and cur_ikpt != ikpt:
                    continue
                # End of ugly patch
                if not eigen:
                    eigen = get_ev(ev, ikpt=ikpt)
                if not eigen:
                    continue
                for i, e in enumerate(eigen):
                    if e:
                        evs[i].append(e)
            shape0 = 2 if len(evs[1]) > 0 else 1
            norbs = list(map(len, evs))
        data = numpy.ndarray.__new__(cls, shape=(shape0, max(norbs)),
                                     dtype=numpy.float)
        data.fill(numpy.nan)
        data[0, :len(evs[0])] = evs[0]
        if norbs[1] > 0:
            data[1, :len(evs[1])] = evs[1]
        data.info = norbs  # (map(len,evs))
        return data

    def __init__(self, *args, **kwargs):
        ikpt = kwargs.get("ikpt", 1 if len(args) < 2 else args[1])
        kpt = kwargs.get("kpt", (0., 0., 0.) if len(args) < 3 else args[2])
        kwgt = kwargs.get('kwgt', 1.0)
        self.set_kpt(ikpt, kpt, kwgt)

    def set_kpt(self, ikpt, kpt, kwgt=1.0):
        if not isinstance(ikpt, int):
            raise TypeError('ikpt should be a integer')
        if len(kpt) != 3:
            raise TypeError('kpt should be a object of len 3')
        self.ikpt = ikpt
        self.kpt = kpt
        self.kwgt = kwgt

    def __add__(self, b):
        if hasattr(b, 'kpt') and (b.kpt != self.kpt):
            raise ValueError('cannot sum BandArray with different kpoints')
        if hasattr(b, 'kwgt') and (b.kwgt != self.kwgt):
            raise ValueError('cannot sum BandArray with different kweights')
        c = super(type(self), self).__add__(b)
        return BandArray(data=c, ikpt=self.ikpt, kpt=self.kpt, kwgt=self.kwgt)

class BZPath():
    """
    Defines a set of points which are associated to a path in the reduced
    Brillouin Zone.
    """

    def __init__(self, lattice, path, special_points, npts=50):
        import ase.dft.kpoints as ase
        self.special_points = special_points
        path_tmp = []
        self.symbols = []
        # construct the path
        for p in path:
            if isinstance(p, str):
                # then this is a special point
                path_tmp.append(self.special_points[p])
                self.symbols.append(p.replace('G', '$\\Gamma$'))
            else:
                path_tmp.append(list(p.values())[0])
                self.symbols.append(list(p.keys())[0])
        self.path, self.xaxis, self.xlabel = ase.get_bandpath(
            path_tmp, lattice, npts)

class BrillouinZone():
    def __init__(self, astruct, mesh, evals, fermi_energy):
        import spglib
        import numpy
        cell = astruct_to_cell(astruct)
        self.lattice = cell[0]
        # celltmp=[ a if a!=float('inf') else 1.0 for a in astruct['cell']]
        # self.lattice=numpy.diag(celltmp)
        # print 'lattice',self.lattice
        # pos=[[ a/b if b!=float('inf') else 0.0
        #        for a,b in zip(at.values()[0], celltmp)]
        #      for at in astruct['positions']]
        # atoms=[ at.keys()[0] for at in astruct['positions']]
        # ianames,iatype=numpy.unique(atoms,return_inverse=True) #[1,]*4+[2,]*4
        # we should write a function for the iatype
        # print 'iatype', iatype
        # cell=(self.lattice,pos,iatype)
        safe_print('spacegroup', spglib.get_spacegroup(cell, symprec=1e-5))
        # then define the pathes and special points
        import ase.dft.kpoints as ase
        # we should adapt the 'cubic'
        cell_tmp = astruct['cell']
        # print 'cell',
        #        cell_tmp,numpy.allclose(cell_tmp,[cell_tmp[0],]*len(cell_tmp))
        if numpy.allclose(cell_tmp, [cell_tmp[0], ]*len(cell_tmp)):
            lattice_string = 'cubic'
        else:
            lattice_string = 'orthorhombic'
        safe_print('Lattice found:', lattice_string)
        self.special_points = ase.get_special_points(
            lattice_string, self.lattice, eps=0.0001)
        self.special_paths = ase.parse_path_string(
            ase.special_paths[lattice_string])
        self.fermi_energy = fermi_energy
        # dataset = spglib.get_symmetry_dataset(cell, symprec=1e-3)
        # print dataset
        # the shift has also to be put if present
        mapping, grid = spglib.get_ir_reciprocal_mesh(
            mesh, cell, is_shift=[0, 0, 0])
        lookup = []
        for ikpt in numpy.unique(mapping):
            ltmp = []
            for ind, (m, g) in enumerate(zip(mapping, grid)):
                if m == ikpt:
                    ltmp.append((g, ind))
            lookup.append(ltmp)
        safe_print('irreductible k-points', len(lookup))
        # print 'mapping',mapping
        # print 'grid',len(grid),numpy.max(grid)
        coords = numpy.array(grid, dtype=numpy.float)/mesh
        # print 'coords',coords
        # print 'shape',coords.shape
        # print grid #[ x+mesh[0]*y+mesh[0]*mesh[1]*z for x,y,z in grid]
        # brillouin zone
        kp = numpy.array([k.kpt for k in evals])
        ourkpt = numpy.rint(kp*(numpy.array(mesh))).astype(int)
        # print ourkpt
        bz = numpy.ndarray((coords.shape[0], evals[0].size), dtype=float)
        # print bz
        # shift = (numpy.array(mesh)-1)/2
        # print 'shift',shift
        for ik in lookup:
            irrk = None
            for orbs, bzk in zip(evals, ourkpt):
                for (kt, ind) in ik:
                    if (bzk == kt).all():
                        irrk = orbs
                        # print 'hello',orbs.kpt,kt
                        break
                if irrk is not None:
                    break
            if irrk is None:
                safe_print('error in ik', ik)
                safe_print('our', ourkpt)
                safe_print('spglib', grid)
                safe_print('mapping', mapping)
            for (kt, ind) in ik:
                # r=kt+shift
                # ind=numpy.argwhere([(g==kt).all() for g in grid])
                # print 'ik',kt,r,ind
                # print irrk.shape, bz.shape
                # bz[r[0],r[1],r[2],:]=irrk.reshape(irrk.size)
                bz[ind, :] = irrk.reshape(irrk.size)
        # duplicate coordinates for the interpolation
        bztmp = bz  # .reshape((mesh[0]*mesh[1]*mesh[2], -1))
        # print bztmp
        ndup = 7
        duplicates = [[-1, 0, 0], [1, 0, 0], [0, -1, 0],
                      [0, 1, 0], [0, 0, -1], [0, 0, 1]]
        bztot = numpy.ndarray((ndup, bztmp.shape[0], bztmp.shape[1]))
        bztot[0, :, :] = bztmp
        ctot = numpy.ndarray((ndup, coords.shape[0], coords.shape[1]))
        ctot[0, :, :] = coords
        for i, sh in enumerate(duplicates):
            bztot[i+1, :, :] = bztmp
            ctot[i+1, :, :] = coords+sh
            # print 'coors',coords,coords+[1.0,0,0]
        bztot = bztot.reshape((ndup*bztmp.shape[0], -1))
        ctot = ctot.reshape((ndup*coords.shape[0], -1))
        import scipy.interpolate.interpnd as interpnd
        self.interpolator = interpnd.LinearNDInterpolator(ctot, bztot)
        # sanity check of the interpolation
        sanity = 0.0
        for kpt in evals:
            diff = numpy.ravel(numpy.ravel(
                kpt)-numpy.ravel(self.interpolator([kpt.kpt])))
            sanity = max(sanity, numpy.dot(diff, diff))
        print('Interpolation bias', sanity)

    def conversion_factor(self, units):
        if units == 'AU':
            fac = 1.0
        elif units == 'eV':
            fac = AU_eV
        else:
            raise ValueError('Unrecognized units ('+units+')')
        return fac

    def plot(self, path=None, npts=50, units='eV'):
        if path is None:
            # ppath=BZPath(self.lattice,self.special_paths[0]+['G',],self.special_points,npts)
            ppath = BZPath(
                self.lattice, self.special_paths[0], self.special_points, npts)
        else:
            ppath = path
        toto = self.interpolator(ppath.path)
        import matplotlib.pyplot as plt
        # print toto.min(),toto.max()
        for b in toto.transpose():
            plt.plot(ppath.xaxis, [ib * self.conversion_factor(units)
                                   for ib in b])  # , 'b-')

        plt.axhline(self.conversion_factor(units)*self.fermi_energy, color='k',
                    linestyle='--')
        for p in ppath.xlabel:
            plt.axvline(p, color='k', linestyle='-')
            plt.xticks(ppath.xlabel, ppath.symbols)

        # if units == 'eV':
        #    plt.ylabel('Energy [eV]', fontsize=18)
        # else:
        #    plt.ylabel('Energy [Ha]', fontsize=18)

        plt.show()


"""Code for BBPSSW entanglement purification.

This module defines code to support the BBPSSW protocol for entanglement purification.
Success results are pre-determined based on network parameters.
Also defined is the message type used by the BBPSSW code.
"""

from enum import Enum, auto
from typing import List, TYPE_CHECKING
from functools import lru_cache

from numpy.random import random

if TYPE_CHECKING:
    from ..components.memory import Memory
    from ..topology.node import Node

from ..message import Message
from .entanglement_protocol import EntanglementProtocol
from ..utils import log
from ..components.circuit import Circuit

class BBPSSWMsgType(Enum):
    """Defines possible message types for entanglement purification"""

    PURIFICATION_RES = auto()

class BBPSSWMessage(Message):
    """Message used by entanglement purification protocols.

    This message contains all information passed between purification protocol instances.

    Attributes:
        msg_type (BBPSSWMsgType): defines the message type.
        receiver (str): name of destination protocol instance.
    """

    def __init__(self, msg_type: BBPSSWMsgType, receiver: str, **kwargs):
        Message.__init__(self, msg_type, receiver)
        if self.msg_type is BBPSSWMsgType.PURIFICATION_RES:
            self.meas_res = kwargs['meas_res']
        else:
            raise Exception("BBPSSW protocol create unknown type of message: %s" % str(msg_type))

class BBPSSW(EntanglementProtocol):
    """Purification protocol instance.

    This class provides an implementation of the BBPSSW purification protocol.
    It should be instantiated on a quantum router node.

    Variables:
        BBPSSW.circuit (Circuit): circuit that purifies entangled memories.

    Attributes:
        own (QuantumRouter): node that protocol instance is attached to.
        name (str): label for protocol instance.
        kept_memo: memory to be purified by the protocol (should already be entangled).
        meas_memo: memory to measure and discart (should already be entangled).
        another (BBPSSW): pointer of BBPSSW on another side (may be removed in the future).
        meas_res (int): measurement result from circuit.
    """

    circuit = Circuit(2)
    circuit.cx(0, 1)
    circuit.measure(1)

    def __init__(self, own: "Node", name: str, kept_memo: "Memory", meas_memo: "Memory"):
        """Constructor for purification protocol.

        Args:
            own (Node): node protocol is attached to.
            name (str): name of protocol instance.
            kept_memo (Memory): memory to have fidelity improved.
            meas_memo (Memory): memory to measure and discard.
        """

        assert kept_memo != meas_memo
        EntanglementProtocol.__init__(self, own, name)
        self.memories = [kept_memo, meas_memo]
        self.kept_memo = kept_memo
        self.meas_memo = meas_memo
        self.another = None
        self.meas_res = None
        if self.meas_memo is None:
            self.memories.pop()

    def is_ready(self) -> bool:
        return self.another is not None

    def set_others(self, another: "BBPSSW") -> None:
        """Method to set other entanglement protocol instance.

        Args:
            another (BBPSSW): other purification protocol instance.
        """

        self.another = another

    def start(self) -> None:
        """Method to start entanglement purification.

        Run the circuit below on two pairs of entangled memories on both sides of protocol.

        o -------(x)----------| M |
        .         |
        .   o ----.----------------
        .   .
        .   .
        .   o
        .
        o

        The overall circuit is shown below:

         o -------(x)----------| M |
         .         |
         .   o ----.----------------
         .   .
         .   .
         .   o ----.----------------
         .         |
         o -------(x)----------| M |

        Side Effects:
            May update parameters of kept memory.
            Will send message to other protocol instance.
        """

        log.logger.info(self.own.name + " protocol start with partner {}".format(self.another.own.name))

        assert self.another is not None, "other protocol is not set; please use set_others function to set it."
        kept_memo_ent = self.kept_memo.entangled_memory["node_id"]
        meas_memo_ent = self.meas_memo.entangled_memory["node_id"]
        assert kept_memo_ent == meas_memo_ent, "mismatch of entangled memories {}, {} on node {}".format(kept_memo_ent, meas_memo_ent, self.own.name)
        assert self.kept_memo.fidelity == self.meas_memo.fidelity > 0.5

        self.meas_res = self.own.timeline.quantum_manager.run_circuit(self.circuit, [self.kept_memo.qstate_key,
                                                                                self.meas_memo.qstate_key])
        self.meas_res = self.meas_res[self.meas_memo.qstate_key]
        dst = self.kept_memo.entangled_memory["node_id"]

        message = BBPSSWMessage(BBPSSWMsgType.PURIFICATION_RES, self.another.name, meas_res=self.meas_res)
        self.own.send_message(dst, message)

    def received_message(self, src: str, msg: BBPSSWMessage) -> None:
        """Method to receive messages.

        Args:
            src (str): name of node that sent the message.
            msg (BBPSSW message): message received.

        Side Effects:
            Will call `update_resource_manager` method.
        """

        log.logger.info(self.own.name + " received result message, succeeded: {}".format(self.meas_res == msg.meas_res))
        assert src == self.another.own.name
        self.update_resource_manager(self.meas_memo, "RAW")
        if self.meas_res == msg.meas_res:
            self.kept_memo.fidelity = self.improved_fidelity(self.kept_memo.fidelity)
            self.update_resource_manager(self.kept_memo, state="ENTANGLED")
        else:
            self.update_resource_manager(self.kept_memo, state="RAW")

    def memory_expire(self, memory: "Memory") -> None:
        """Method to receive memory expiration events.

        Args:
            memory (Memory): memory that has expired.

        Side Effects:
            Will call `update_resource_manager` method.
        """

        assert memory in self.memories
        if self.meas_memo is None:
            self.update_resource_manager(memory, "RAW")
        else:
            for memory in self.memories:
                self.update_resource_manager(memory, "RAW")

    def release(self) -> None:
        pass

    @staticmethod
    @lru_cache(maxsize=128)
    def success_probability(F: float) -> float:
        """Method to calculate probability of purification success.

        Formula comes from Dur and Briegel (2007) page 14.

        Args:
            F (float): fidelity of entanglement.
        """

        return F ** 2 + 2 * F * (1 - F) / 3 + 5 * ((1 - F) / 3) ** 2

    @staticmethod
    @lru_cache(maxsize=128)
    def improved_fidelity(F: float) -> float:
        """Method to calculate fidelity after purification.

        Formula comes from Dur and Briegel (2007) formula (18) page 14.

        Args:
            F (float): fidelity of entanglement.
        """

        return (F ** 2 + ((1 - F) / 3) ** 2) / (F ** 2 + 2 * F * (1 - F) / 3 + 5 * ((1 - F) / 3) ** 2)


"""plot_utils module for plotting SEDs."""

import copy
import glob
from random import choice

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import pandas as pd
import numpy as np
from astropy import units as u
from astropy.io import fits
from astropy.table import Table
from extinction import apply
from isochrones.interp import DFInterpolator
from matplotlib.collections import LineCollection
from matplotlib.gridspec import GridSpec
from PyAstronomy import pyasl
from scipy.optimize import curve_fit
from scipy.stats import gaussian_kde

import corner
from dynesty import plotting as dyplot

from .config import filesdir, gridsdir, modelsdir
from .isochrone import get_isochrone
from .phot_utils import *
from .sed_library import *
from .utils import *

class SEDPlotter:
    """Artist class for all things SED.

    Parameters
    ----------
    input_files : str
        Directory containing the code's output files.
    out_folder : type
        Directory where to put the output plots.
    pdf : type
        Set to True to output plots in pdf.
    png : type
        Set to True to output plots in png.
    model : type
        Set to override the SED model that's going to be plotted.
        Possible values are:
            - Phoenix
            - BTSettl
            - NextGen
            - CK04 (Castelli & Kurucz 2004)
            - Kurucz (Kurucz 1993)

    Examples
    -------
    Examples should be written in doctest format, and
    should illustrate how to use the function/class.
    >>>

    Attributes
    ----------
    chain_out : str
        Output directory for chain plot.
    like_out : str
        Output directory for likelihood plot.
    post_out : str
        Output directory for posteriors plot.
    moddir : type
        Directory wheere the SED models are located.
    out : dict
        SED fitting routine output.
    engine : str
        Selected fitting engine.
    star : Star
        The fitted Star object.
    coordinator : array_like
        Array coordinating fixed parameters.
    fixed : array_like
        Array coordinating fixed parameters.
    norm : bool
        norm is set to True if a normalization constant is fitted instead of
        radius + distance.
    grid : str
        Selected model grid.
    av_law : function
        Exticntion law chosen for the fit.
    order : array_like
        Array coordinating parameter order.
    interpolator : function
        Interpolator function.
    theta : array_like
        `Best fit` parameter vector

    """

    __wav_file = 'PHOENIXv2/WAVE_PHOENIX-ACES-AGSS-COND-2011.fits'

    def __init__(self, input_files, out_folder, pdf=False,
                 model=None, settings=None, method='averaged',
                 save_model=False):
        """See class docstring."""
        print('\nInitializing plotter.\n')
        # General setup
        self.pdf = pdf
        png = True if not pdf else False
        self.png = png
        self.out_folder = out_folder
        self.bma = False
        self.method = method
        self.save = save_model

        traces = f'{out_folder}/traces'
        histograms = f'{out_folder}/histograms'
        self.traces_out = traces
        self.hist_out = histograms
        self.moddir = modelsdir
        self.settings_dir = settings

        # Read output files.
        if input_files != 'raw':
            out = pickle.load(open(input_files, 'rb'))
            self.out = out
            self.engine = out['engine']
            self.star = out['star']
            self.coordinator = out['coordinator']
            self.fixed = out['fixed']
            self.norm = out['norm']
            if model is None:
                if self.engine != 'Bayesian Model Averaging':
                    self.grid = out['model_grid']
                else:
                    self.bma = True
                    zs = np.array([out['lnZ'][key]
                                   for key in out['lnZ'].keys()])
                    keys = np.array([key for key in out['lnZ'].keys()])
                    grid = keys[np.argmax(zs)]
                    self.grid = grid
            else:
                self.grid = model
            self.av_law = out['av_law']

            # Create target folders
            create_dir(out_folder)
            if self.engine != 'Bayesian Model Averaging':
                create_dir(traces)
            create_dir(histograms)

            self.star.load_grid(self.grid)

            if not self.norm:
                self.order = np.array(
                    [
                        'teff', 'logg', 'z',
                        'dist', 'rad', 'Av',
                    ]
                )
            else:
                self.order = np.array(
                    ['teff', 'logg', 'z', 'norm', 'Av'])

            mask = self.star.filter_mask
            flxs = self.star.flux[mask]
            errs = self.star.flux_er[mask]
            filters = self.star.filter_names[mask]
            wave = self.star.wave[mask]
            for filt, flx, flx_e in zip(filters, flxs, errs):
                p_ = get_noise_name(filt) + '_noise'
                self.order = np.append(self.order, p_)

            if self.grid.lower() == 'phoenix':
                with open(gridsdir + '/Phoenixv2_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'btsettl':
                with open(gridsdir + '/BTSettl_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'btnextgen':
                with open(gridsdir + '/BTNextGen_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'btcond':
                with open(gridsdir + '/BTCond_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'ck04':
                with open(gridsdir + '/CK04_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'kurucz':
                with open(gridsdir + '/Kurucz_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))
            if self.grid.lower() == 'coelho':
                with open(gridsdir + '/Coelho_DF.pkl', 'rb') as intp:
                    self.interpolator = DFInterpolator(pd.read_pickle(intp))

            # Get best fit parameters.
            theta_samples = np.zeros(self.order.shape[0])
            theta_average = np.zeros(self.order.shape[0])
            for i, param in enumerate(self.order):
                if param != 'inflation':
                    theta_samples[i] = out['best_fit_samples'][param]
                    theta_average[i] = out['best_fit_averaged'][param]

            # Calculate best fit model.
            model_samples = model_grid(theta_samples, filters, wave,
                                       self.interpolator, self.norm,
                                       self.av_law)
            model_average = model_grid(theta_average, filters, wave,
                                       self.interpolator, self.norm,
                                       self.av_law)
            if method == 'averaged':
                self.theta = theta_average
                self.model = model_average
            elif method == 'samples':
                self.theta = theta_samples
                self.model = model_samples

            # Get archival fluxes.
            self.__extract_info()
        else:
            self.star = None

        # Setup plots.
        self.__read_config()
        print('\nPlotter initialized.\n')

    def __extract_info(self):
        self.flux = []
        self.flux_er = []
        self.wave = []
        self.bandpass = []

        for i, f in zip(self.star.used_filters, self.star.flux):
            if i:
                self.flux.append(f)
        for i, e in zip(self.star.used_filters, self.star.flux_er):
            if i:
                self.flux_er.append(e)
        for i, w in zip(self.star.used_filters, self.star.wave):
            if i:
                self.wave.append(w)
        for i, bp in zip(self.star.used_filters, self.star.bandpass):
            if i:
                self.bandpass.append(bp)

        self.flux = np.array(self.flux)
        self.flux_er = np.array(self.flux_er)
        self.wave = np.array(self.wave)
        self.bandpass = np.array(self.bandpass).T

    def plot_SED_no_model(self, s=None):
        """Plot raw photometry."""
        if self.star is None:
            self.star = s
        self.__extract_info()
        # Get plot ylims.
        ymin = (self.flux * self.wave).min()
        ymax = (self.flux * self.wave).max()

        f, ax = plt.subplots(figsize=self.figsize)

        # Model plot
        used_f = self.star.filter_names[self.star.filter_mask]
        colors = np.array([
            'tomato', 'indianred', 'tab:red',
            'salmon', 'coral',
            'mediumorchid', 'mediumslateblue', 'tab:blue',
            'darkslateblue', 'darkblue',
            'olivedrab', 'yellowgreen', 'greenyellow', 'yellow',
            'orangered', 'chocolate', 'khaki',
            'limegreen', 'darkgreen', 'lime', 'seagreen', 'lawngreen', 'green',
            'aquamarine', 'turquoise', 'lightseagreen', 'teal', 'cadetblue',
            'firebrick', 'darkred',
            'blueviolet', 'darkviolet',
            'midnightblue', 'blue',
            'deeppink', 'fuchsia', 'mediumslateblue'
        ])

        for c, w, fl, fe, bp, fi in zip(
                colors[self.star.filter_mask],
                self.wave, self.flux, self.flux_er,
                self.bandpass, used_f):
            ax.errorbar(w, fl * w,
                        xerr=bp, yerr=fe,
                        fmt='',
                        ecolor=c,
                        marker=None)

            ax.scatter(w, fl * w,
                       edgecolors='black',
                       marker=self.marker,
                       c=c,
                       s=self.scatter_size,
                       alpha=self.scatter_alpha, label=fi)

        ax.set_ylim([ymin * .8, ymax * 1.25])
        ax.set_xscale('log', nonposx='clip')
        ax.set_yscale('log', nonposy='clip')
        ax.set_ylabel(r'$\lambda$F$_\lambda$ (erg cm$^{-2}$s$^{-1}$)',
                      fontsize=self.fontsize,
                      fontname=self.fontname
                      )
        ax.legend(loc=0)

        ax.tick_params(
            axis='both', which='major',
            labelsize=self.tick_labelsize
        )
        ax.set_xticks(np.linspace(1, 10, 10))
        ax.get_xaxis().set_major_formatter(ticker.ScalarFormatter())
        ax.set_xlim([0.1, 6])

        for tick in ax.get_yticklabels():
            tick.set_fontname(self.fontname)

        if self.pdf:
            plt.savefig(self.out_folder + '/SED_no_model.pdf',
                        bbox_inches='tight')
        if self.png:
            plt.savefig(self.out_folder + '/SED_no_model.png',
                        bbox_inches='tight')
        pass

    def plot_SED(self, method='average'):
        """Create the plot of the SED."""
        if self.moddir is None:
            print('Models directory not provided, skipping SED plot.')
            return
        print('Plotting SED')
        # Get plot ylims.
        ymin = (self.flux * self.wave).min()
        ymax = (self.flux * self.wave).max()

        n_filt = self.star.used_filters.sum()
        n_pars = int(len(self.theta) - n_filt)

        # Get models residuals
        mask = self.star.filter_mask
        mags = self.star.mags[mask]
        flxs = self.star.flux[mask]
        errs = self.star.flux_er[mask]
        filters = self.star.filter_names[mask]
        wave = self.star.wave[mask]

        for i, th in enumerate(self.theta[n_pars:]):
            mag = mags[i]
            filt = filters[i]
            _, er = mag_to_flux(mag, th, filt)
            self.theta[n_pars + i] = er

        residuals, errors = get_residuals(
            self.theta, flxs, errs, wave, filters, self.interpolator, self.norm,
            self.av_law)

        norm_res = residuals / errors

        # Create plot layout

        f = plt.figure(figsize=self.figsize)
        gs = GridSpec(2, 1, height_ratios=[3, 0.5], hspace=0.05)

        ax = f.add_subplot(gs[0])
        ax_r = f.add_subplot(gs[1])

        self.SED(ax)

        # Model plot
        ax.errorbar(self.wave, self.flux * self.wave,
                    xerr=self.bandpass, yerr=errors,
                    fmt=',',
                    ecolor=self.error_color,
                    zorder=0,
                    marker=None)

        ax.scatter(self.wave, self.flux * self.wave,
                   edgecolors='black',
                   marker=self.marker,
                   c=self.marker_colors,
                   s=self.scatter_size, zorder=1,
                   alpha=self.scatter_alpha)

        ax.scatter(self.wave, self.model * self.wave,
                   marker=self.marker_model,
                   edgecolors=self.marker_colors_model,
                   s=self.scatter_size,
                   facecolor='', zorder=3,
                   lw=3)

        # Residual plot
        ax_r.axhline(y=0, lw=2, ls='--', c='k', alpha=.7)

        ax_r.errorbar(self.wave, np.zeros(self.wave.shape[0]),
                      xerr=self.bandpass, yerr=self.flux_er,
                      fmt=',',
                      ecolor=self.error_color,
                      marker=None)
        ax_r.scatter(self.wave, np.zeros(self.wave.shape[0]),
                     edgecolors='black',
                     marker=self.marker,
                     c=self.marker_colors,
                     s=self.scatter_size,
                     alpha=self.scatter_alpha)
        ax_r.scatter(self.wave, norm_res,
                     marker=self.marker_model,
                     edgecolors=self.marker_colors_model,
                     s=self.scatter_size,
                     facecolor='',
                     lw=3,
                     zorder=10)

        # Formatting
        res_std = norm_res.std()
        ax.set_ylim([ymin * 0.8, ymax * 1.2])
        # ax_r.set_ylim([-5, 5])
        ax_r.set_ylim([-4 * res_std, 4 * res_std])
        ax.set_xscale('log', nonposx='clip')
        ax.set_yscale('log', nonposy='clip')
        ax_r.set_xscale('log', nonposx='clip')
        ax_r.set_xlabel(r'$\lambda (\mu m)$',
                        fontsize=self.fontsize,
                        fontname=self.fontname
                        )
        ax.set_ylabel(r'$\lambda$F$_\lambda$ (erg cm$^{-2}$s$^{-1}$)',
                      fontsize=self.fontsize,
                      fontname=self.fontname
                      )
        ax_r.set_ylabel('Residuals\n$(\\sigma)$',
                        fontsize=self.fontsize,
                        fontname=self.fontname
                        )

        ax.tick_params(
            axis='both', which='major',
            labelsize=self.tick_labelsize
        )
        ax_r.tick_params(
            axis='both', which='major',
            labelsize=self.tick_labelsize
        )
        ax_r.set_xticks(np.linspace(1, 10, 10))
        ax_r.get_xaxis().set_major_formatter(ticker.ScalarFormatter())
        ax.set_xticks(np.linspace(1, 10, 10))
        ax.get_xaxis().set_major_formatter(ticker.NullFormatter())
        ylocmin = ticker.LinearLocator(numticks=4)

        ax_r.yaxis.set_minor_locator(ylocmin)
        ax_r.yaxis.set_minor_formatter(ticker.NullFormatter())

        if 'GALEX_FUV' in self.star.filter_names[self.star.filter_mask] or \
                'GALEX_NUV' in self.star.filter_names[self.star.filter_mask]:
            ax.set_xlim([0.125, 6])
            ax_r.set_xlim([0.125, 6])
        else:
            ax.set_xlim([0.25, 6])
            ax_r.set_xlim([0.25, 6])

        labels = [item.get_text() for item in ax.get_xticklabels()]

        empty_string_labels = [''] * len(labels)
        ax.set_xticklabels(empty_string_labels)

        for tick in ax.get_yticklabels():
            tick.set_fontname(self.fontname)
        for tick in ax_r.get_yticklabels():
            tick.set_fontname(self.fontname)
        for tick in ax_r.get_xticklabels():
            tick.set_fontname(self.fontname)

        if self.pdf:
            plt.savefig(f'{self.out_folder}/SED.pdf', bbox_inches='tight')
        if self.png:
            plt.savefig(f'{self.out_folder}/SED.png', bbox_inches='tight')
        if self.save:
            data = np.vstack((self.wave, self.model * self.wave)).T
            np.savetxt(f'{self.out_folder}/synthetic.dat', data, fmt='%s',
                       header='wavelength(mu m) wave*flux(erg cm-2 s-2)')
        pass

    def SED(self, ax):
        """Plot the SED model."""
        Rv = 3.1  # For extinction.
        if not self.norm:
            rad = self.theta[4]
            dist = self.theta[3] * u.pc.to(u.solRad)
            norm = (rad / dist) ** 2
            Av = self.theta[5]
        else:
            norm = self.theta[3]
            Av = self.theta[4]

        # SED plot.
        if self.grid == 'phoenix':
            wave = fits.open(self.moddir + self.__wav_file)[0].data
            wave *= u.angstrom.to(u.um)

            lower_lim = 0.125 < wave
            upper_lim = wave < 4.629296073126975

            flux = self.fetch_Phoenix()

            new_w = wave[lower_lim * upper_lim]

            new_ww = np.linspace(new_w[0], new_w[-1], len(new_w))

            ext = self.av_law(new_w * 1e4, Av, Rv)

            brf, _ = pyasl.instrBroadGaussFast(
                new_ww, flux, 1500,
                edgeHandling="firstlast",
                fullout=True, maxsig=8
            )
            brf = brf[lower_lim * upper_lim]
            brf = apply(ext, brf)
            flx = brf * norm * new_w
            ax.plot(new_w[:-1000], flx[:-1000], lw=1.25, color=self.model_color,
                    zorder=0)

        elif self.grid == 'btsettl':
            wave, flux = self.fetch_btsettl()

            lower_lim = 0.125 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)

            new_w = np.linspace(wave[0], wave[-1], len(wave))

            brf, _ = pyasl.instrBroadGaussFast(
                new_w, flux, 1500,
                edgeHandling="firstlast",
                fullout=True, maxsig=8
            )
            flx = apply(ext, brf)
            flx *= wave * norm
            ax.plot(wave, flx, lw=1.25, color=self.model_color, zorder=0)

        elif self.grid == 'btnextgen':
            wave, flux = self.fetch_btnextgen()

            lower_lim = 0.125 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)

            new_w = np.linspace(wave[0], wave[-1], len(wave))

            brf, _ = pyasl.instrBroadGaussFast(
                new_w, flux, 1500,
                edgeHandling="firstlast",
                fullout=True, maxsig=8
            )
            flx = apply(ext, brf)
            flx *= wave * norm
            ax.plot(wave, flx, lw=1.25, color=self.model_color, zorder=0)

        elif self.grid == 'btcond':
            wave, flux = self.fetch_btcond()

            lower_lim = 0.125 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)

            new_w = np.linspace(wave[0], wave[-1], len(wave))

            brf, _ = pyasl.instrBroadGaussFast(
                new_w, flux, 1500,
                edgeHandling="firstlast",
                fullout=True, maxsig=8
            )
            flx = apply(ext, brf)
            flx *= wave * norm
            ax.plot(wave, flx, lw=1.25, color=self.model_color, zorder=0)

        elif self.grid == 'ck04':
            wave, flux = self.fetch_ck04()

            lower_lim = 0.125 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)
            flux = apply(ext, flux)
            flux *= wave * norm
            ax.plot(wave, flux, lw=1.25, color=self.model_color, zorder=0)

        elif self.grid == 'kurucz':
            wave, flux = self.fetch_kurucz()

            lower_lim = 0.15 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)
            flux = apply(ext, flux)
            flux *= wave * norm
            ax.plot(wave, flux, lw=1.25, color=self.model_color, zorder=0)

        elif self.grid == 'coelho':
            wave, flux = self.fetch_coelho()

            lower_lim = 0.15 < wave
            upper_lim = wave < 4.629296073126975

            wave = wave[lower_lim * upper_lim]
            flux = flux[lower_lim * upper_lim]
            ext = self.av_law(wave * 1e4, Av, Rv)
            flux = apply(ext, flux)
            flux *= wave * norm
            ax.plot(wave, flux, lw=1.25, color=self.model_color, zorder=0)
        if self.save:
            data = np.vstack((wave, flux)).T
            np.savetxt(f'{self.out_folder}/SED.dat', data, fmt='%s',
                       header='wavelength(mu m) wave*flux(erg cm-2 s-2)')
        pass

    def plot_trace(self):
        """Plot SED chains."""
        samples = self.out['posterior_samples']
        for i, param in enumerate(self.order):
            if not self.coordinator[i]:
                f, ax = plt.subplots(figsize=(12, 4))
                ax.step(range(len(samples[param])), samples[param],
                        color='k', alpha=0.8)
                ax.set_ylabel(param,
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                ax.set_xlabel('Steps',
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                best = self.out['best_fit'][param]
                # ax.axhline(np.median(samples[param]), color='red', lw=2)
                ax.axhline(best, color='red', lw=2)
                ax.tick_params(
                    axis='both', which='major',
                    labelsize=self.tick_labelsize
                )
                plt.savefig(self.chain_out + '/' + param +
                            '.png', bbox_inches='tight')
        plt.close('all')
        pass

    def plot_like(self):
        """Plot Likelihoods."""
        samples = self.out['posterior_samples']
        for i, param in enumerate(self.order):
            if not self.coordinator[i]:
                f, ax = plt.subplots(figsize=(12, 4))
                ax.scatter(samples[param], samples['loglike'], alpha=0.5, s=40)
                best = self.out['best_fit'][param]
                # ax.axvline(np.median(samples[param]), color='red', lw=1.5)
                ax.axvline(best, color='red', lw=1.5)
                ax.set_ylabel('log likelihood',
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                ax.set_xlabel(param,
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                ax.tick_params(
                    axis='both', which='major',
                    labelsize=self.tick_labelsize
                )
                plt.savefig(self.like_out + '/' + param + '.png',
                            bbox_inches='tight')
        plt.close('all')
        if self.engine == 'dynesty':
            fig, axes = dyplot.traceplot(
                self.out['dynesty'],
                truths=self.theta,
                show_titles=True, trace_cmap='plasma',
            )
            plt.savefig(self.like_out + '/dynesty_trace.png')
        pass

    def plot_post(self):
        """Plot posteriors."""
        samples = self.out['posterior_samples']
        for i, param in enumerate(self.order):
            if not self.coordinator[i]:
                f, ax = plt.subplots(figsize=(12, 4))
                ax.scatter(samples[param], samples['posteriors'], alpha=0.5,
                           s=40)
                best = self.out['best_fit'][param]
                # ax.axvline(np.median(samples[param]), color='red', lw=1.5)
                ax.axvline(best, color='red', lw=1.5)
                ax.set_ylabel('log posterior',
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                ax.set_xlabel(param,
                              fontsize=self.fontsize,
                              fontname=self.fontname
                              )
                ax.tick_params(
                    axis='both', which='major',
                    labelsize=self.tick_labelsize
                )
                plt.savefig(self.post_out + '/' + param + '.png',
                            bbox_inches='tight')
        plt.close('all')
        pass

    def plot_hist(self):
        pass

    def plot_bma_hist(self):
        """Plot histograms."""
        print('Plotting BMA histograms.')
        colors = [
            'tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple',
            'tab:brown'
        ]
        models = [key for key in self.out['originals'].keys()]
        for i, param in enumerate(self.order):
            if 'noise' in param:
                continue
            if not self.coordinator[i]:
                f1, ax1 = plt.subplots(figsize=(12, 6))
                f2, ax2 = plt.subplots(figsize=(12, 6))
                for j, m in enumerate(models):
                    # Get samples
                    samp = self.out['originals'][m][param]
                    # Plot sample histogram
                    label = m + ' prob: {:.3f}'.format(self.out['weights'][m])
                    # Normal
                    n, bins1, patches = ax1.hist(samp, alpha=.3, bins=20,
                                                 label=label, density=True,
                                                 color=colors[j])
                    # Weighted
                    n, bins2, patches = ax2.hist(
                        samp, alpha=.3, bins=20, label=label,
                        weights=[self.out['weights'][m]] * len(samp)
                    )
                    # Fit a KDE to data
                    kde = gaussian_kde(samp)
                    # Estimate amplitude of the weighted distributions
                    mu, sig = norm.fit(samp)
                    try:
                        bc = bins2[:-1] + np.diff(bins2)
                        popt, pcov = curve_fit(norm_fit, xdata=bc, ydata=n,
                                               p0=[mu, sig, n.max()],
                                               maxfev=50000)
                    except RuntimeError:
                        popt = (mu, sig, n.max())
                    xx1 = np.linspace(bins1[0], bins1[-1], 1000)
                    xx2 = np.linspace(bins2[0], bins2[-1], 1000)
                    # Plot best fit
                    ax1.plot(xx1, kde(xx2), lw=2, alpha=1, color=colors[j])
                    ax2.plot(xx1, kde(xx2) * popt[2], lw=2, alpha=1,
                             color=colors[j])
                # The same but for the weighted samples
                n, bins, patches = ax1.hist(
                    self.out['weighted_samples'][param], alpha=.3,
                    bins=20, label='Weighted sampling', density=True,
                    color='tab:cyan'
                )
                kde = gaussian_kde(self.out['weighted_samples'][param])
                xx = np.linspace(bins[0], bins[-1], 300)
                ax1.plot(xx, kde(xx), color='tab:cyan', lw=2, alpha=1, ls='--')

                # Now ditto for the weighted average
                n, bins, patches = ax1.hist(
                    self.out['weighted_average'][param], alpha=.3,
                    bins=20, label='Weighted average', density=True,
                    color='tab:pink'
                )
                kde = gaussian_kde(self.out['weighted_average'][param])
                xx = np.linspace(bins[0], bins[-1], 300)
                ax1.plot(xx, kde(xx), color='tab:pink', lw=2, alpha=1, ls='-.')
                if param == 'z':
                    param = '[Fe/H]'
                # Normal
                ax1.set_ylabel('PDF',
                               fontsize=self.fontsize,
                               fontname=self.fontname
                               )
                # Weighted
                ax2.set_ylabel('N',
                               fontsize=self.fontsize,
                               fontname=self.fontname
                               )
                axes = [ax1, ax2]
                for ax in axes:
                    ax.set_xlabel(param,
                                  fontsize=self.fontsize,
                                  fontname=self.fontname
                                  )
                    for tick in ax.get_yticklabels():
                        tick.set_fontname(self.fontname)
                    for tick in ax.get_xticklabels():
                        tick.set_fontname(self.fontname)

                    ax.tick_params(
                        axis='both', which='major',
                        labelsize=self.tick_labelsize
                    )
                    ax.legend(loc=0, prop={'size': 16})

                if param == '[Fe/H]':
                    param = 'Fe_H'
                if self.png:
                    f1.savefig(self.hist_out + '/' + param + '.png',
                               bbox_inches='tight')
                    f2.savefig(self.hist_out + '/weighted_' + param + '.png',
                               bbox_inches='tight')
                if self.pdf:
                    f1.savefig(self.hist_out + '/' + param + '.pdf',
                               bbox_inches='tight')
                    f2.savefig(self.hist_out + '/weighted_' + param + '.pdf',
                               bbox_inches='tight')
                plt.close(f1)
                plt.close(f2)

        if self.bma:
            # Age hist
            f, ax = plt.subplots(figsize=(12, 4))
            samp = self.out['mist_samples']['age']
            n, bins, patches = ax.hist(
                samp, alpha=.3, bins=20, label='MIST', density=True
            )
            kde = gaussian_kde(samp)
            xx = np.linspace(bins[0], bins[-1], 10000)
            ax.plot(xx, kde(xx), color='k', lw=2, alpha=.7)
            ax.set_ylabel('PDF',
                          fontsize=self.fontsize,
                          fontname=self.fontname)
            ax.set_xlabel('Age',
                          fontsize=self.fontsize,
                          fontname=self.fontname)

            for tick in ax.get_yticklabels():
                tick.set_fontname(self.fontname)
            for tick in ax.get_xticklabels():
                tick.set_fontname(self.fontname)

            ax.tick_params(
                axis='both', which='major',
                labelsize=self.tick_labelsize
            )
            plt.legend(loc=0)
            if self.png:
                plt.savefig(self.hist_out + '/age.png',
                            bbox_inches='tight')
            if self.pdf:
                plt.savefig(self.hist_out + '/age.pdf',
                            bbox_inches='tight')
            plt.close(f)
            # Mass hist
            f, ax = plt.subplots(figsize=(12, 4))
            samp = self.out['mist_samples']['iso_mass']
            n, bins, patches = ax.hist(
                samp, alpha=.3, bins=20, label='MIST', density=True
            )
            kde = gaussian_kde(samp)
            xx = np.linspace(bins[0], bins[-1], 10000)
            ax.plot(xx, kde(xx), color='k', lw=2, alpha=.7)
            ax.set_ylabel('PDF',
                          fontsize=self.fontsize,
                          fontname=self.fontname)
            ax.set_xlabel('Mass',
                          fontsize=self.fontsize,
                          fontname=self.fontname)

            for tick in ax.get_yticklabels():
                tick.set_fontname(self.fontname)
            for tick in ax.get_xticklabels():
                tick.set_fontname(self.fontname)

            ax.tick_params(
                axis='both', which='major',
                labelsize=self.tick_labelsize
            )
            plt.legend(loc=0)
            if self.png:
                plt.savefig(self.hist_out + '/iso_mass.png',
                            bbox_inches='tight')
            if self.pdf:
                plt.savefig(self.hist_out + '/iso_mass.pdf',
                            bbox_inches='tight')
            plt.close(f)
            # EEP hist
            f, ax = plt.subplots(figsize=(12, 4))
            samp = self.out['mist_samples']['eep']
            n, bins, patches = ax.hist(
                samp, alpha=.3, bins=20, label='MIST', density=True
            )
            kde = gaussian_kde(samp)
            xx = np.linspace(bins[0], bins[-1], 10000)
            ax.plot(xx, kde(xx), color='k', lw=2, alpha=.7)
            ax.set_ylabel('PDF',
                          fontsize=self.fontsize,
                          fontname=self.fontname)
            ax.set_xlabel('EEP',
                          fontsize=self.fontsize,
                          fontname=self.fontname)

            for tick in ax.get_yticklabels():
                tick.set_fontname(self.fontname)
            for tick in ax.get_xticklabels():
                tick.set_fontname(self.fontname)

            ax.tick_params(
                axis='both', which='major',
                labelsize=self.tick_labelsize
            )
            plt.legend(loc=0)
            if self.png:
                plt.savefig(self.hist_out + '/EEP.png',
                            bbox_inches='tight')
            if self.pdf:
                plt.savefig(self.hist_out + '/EEP.pdf',
                            bbox_inches='tight')
            plt.close(f)

    def plot_bma_HR(self, nsamp):
        """Plot HR diagram for the star."""
        print('Plotting HR diagram')
        # Get necessary info from the star.
        age = self.out[f'best_fit_{self.method}']['age']
        feh = self.out[f'best_fit_{self.method}']['z']
        teff = np.log10(self.out[f'best_fit_{self.method}']['teff'])
        lum = np.log10(self.out[f'best_fit_{self.method}']['lum'])
        teff_lo, teff_hi = self.out[f'uncertainties_{self.method}']['teff']
        lum_lo, lum_hi = self.out[f'uncertainties_{self.method}']['lum']
        teff_lo = teff_lo / (10 ** teff * np.log(10))
        teff_hi = teff_hi / (10 ** teff * np.log(10))
        lum_lo = lum_lo / (10 ** lum * np.log(10))
        lum_hi = lum_hi / (10 ** lum * np.log(10))
        ages = self.out['mist_samples']['age']
        m = self.method if self.method == 'samples' else 'average'
        fehs = self.out[f'weighted_{m}']['z']

        if feh > 0.5:
            feh = 0.5

        iso_bf = get_isochrone(np.log10(age) + 9, feh)

        logteff = iso_bf['logTeff'].values
        loglum = iso_bf['logL'].values
        mass = iso_bf['mass'].values

        fig, ax = plt.subplots(figsize=self.hr_figsize)

        points = np.array([logteff, loglum]).T.reshape(-1, 1, 2)
        segments = np.concatenate([points[:-1], points[1:]], axis=1)

        norm = plt.Normalize(mass.min(), mass.max())
        lc = LineCollection(segments, cmap=self.hr_cmap, norm=norm,
                            linewidths=5)

        lc.set_array(mass)
        line = ax.add_collection(lc)
        line.zorder = 1000
        cbar = fig.colorbar(line, ax=ax, pad=0.01)
        cbar.set_label(r'$M_\odot$',
                       rotation=270,
                       fontsize=self.fontsize,
                       fontname=self.fontname,
                       labelpad=20)

        for i in range(nsamp):
            a = np.log10(choice(ages)) + 9
            z = choice(fehs)
            if z > 0.5:
                z = 0.5
            iso = get_isochrone(a, z)

            logt = iso['logTeff'].values
            logl = iso['logL'].values
            ax.plot(logt, logl, color='gray')

        ax.errorbar(teff, lum, xerr=[[teff_lo], [teff_hi]],
                    yerr=[[lum_lo], [lum_hi]], color=self.hr_color,
                    zorder=1001)
        ax.scatter(teff, lum, s=350, color=self.hr_color, zorder=1002,
                   edgecolors='k', marker=self.hr_marker)

        ax.invert_xaxis()
        ax.set_xlabel('logTeff',
                      fontsize=self.fontsize,
                      fontname=self.fontname)
        ax.set_ylabel('logL',
                      fontsize=self.fontsize,
                      fontname=self.fontname)
        ax.tick_params(
            axis='both', which='major',
            labelsize=self.tick_labelsize
        )
        for ll in cbar.ax.yaxis.get_ticklabels():
            ll.set_fontsize(self.tick_labelsize)
        for tick in ax.get_yticklabels():
            tick.set_fontname(self.fontname)
        for tick in ax.get_yticklabels():
            tick.set_fontname(self.fontname)

        if self.png:
            plt.savefig(self.out_folder + '/HR_diagram.png',
                        bbox_inches='tight')
        if self.pdf:
            plt.savefig(self.out_folder + '/HR_diagram.pdf',
                        bbox_inches='tight')

    def plot_corner(self):
        """Make corner plot."""
        print('Plotting corner.')
        m = self.method if self.method == 'samples' else 'average'
        samples = self.out[f'weighted_{m}']
        all_samps = []
        theta_lo = []
        theta_up = []

        for i, o in enumerate(self.order):
            if 'noise' in o:
                self.coordinator[i] = 1

        theta = self.theta[self.coordinator == 0]
        used_params = self.order[self.coordinator == 0]

        for i, param in enumerate(self.order):
            if not self.coordinator[i]:
                if 'noise' in param:
                    continue
                _, lo, up = credibility_interval(samples[param])
                theta_lo.append(lo)
                theta_up.append(up)
                all_samps.append(samples[param])

        corner_samp = np.vstack(all_samps)

        titles = self.__create_titles(used_params, theta, theta_up, theta_lo)
        labels = self.__create_labels(used_params)

        fig = corner.corner(
            corner_samp.T,
            plot_contours=True,
            fill_contours=False,
            plot_datapoints=True,
            no_fill_contours=True,
            max_n_ticks=4
        )

        axes = np.array(fig.axes).reshape((theta.shape[0], theta.shape[0]))

        for i in range(theta.shape[0]):
            ax = axes[i, i]
            ax.axvline(theta[i], color=self.corner_med_c,
                       linestyle=self.corner_med_style)
            ax.axvline(theta_lo[i], color=self.corner_v_c,
                       linestyle=self.corner_v_style)
            ax.axvline(theta_up[i], color=self.corner_v_c,
                       linestyle=self.corner_v_style)
            t = titles[i]

            ax.set_title(t, fontsize=self.corner_fontsize,
                         fontname=self.fontname)

        for yi in range(theta.shape[0]):
            for xi in range(yi):
                ax = axes[yi, xi]
                if xi == 0:
                    for tick in ax.yaxis.get_major_ticks():
                        tick.label.set_fontsize(self.corner_tick_fontsize)
                        tick.label.set_fontname(self.fontname)
                        ax.set_ylabel(
                            labels[yi],
                            labelpad=self.corner_labelpad,
                            fontsize=self.corner_fontsize,
                            fontname=self.fontname
                        )
                if yi == theta.shape[0] - 1:
                    for tick in ax.xaxis.get_major_ticks():
                        tick.label.set_fontsize(self.corner_tick_fontsize)
                        tick.label.set_fontname(self.fontname)
                        ax.set_xlabel(
                            labels[xi],
                            labelpad=self.corner_labelpad,
                            fontsize=self.corner_fontsize,
                            fontname=self.fontname
                        )
                ax.axvline(theta[xi], color=self.corner_med_c,
                           linestyle=self.corner_med_style)
                ax.axhline(theta[yi], color=self.corner_med_c,
                           linestyle=self.corner_med_style)
                ax.plot(theta[xi], theta[yi], self.corner_marker)
            axes[-1, -1].set_xlabel(
                labels[-1],
                labelpad=self.corner_labelpad,
                fontsize=self.corner_fontsize,
                fontname=self.fontname
            )
            for tick in axes[-1, -1].xaxis.get_major_ticks():
                tick.label.set_fontsize(self.corner_tick_fontsize)
                tick.label.set_fontname(self.fontname)

            if self.pdf:
                plt.savefig(f'{self.out_folder}/CORNER.pdf',
                            bbox_inches='tight')
            if self.png:
                plt.savefig(f'{self.out_folder}/CORNER.png',
                            bbox_inches='tight')
        pass

    def clean(self):
        """Close opened figures."""
        plt.close('all')

    def fetch_Phoenix(self):
        """Fetch correct Phoenixv2 SED file.

        The directory containing the Phoenix spectra must be called PHOENIXv2
        Within PHOENIXv2 there should be the wavelength file called
        WAVE_PHOENIX-ACES-AGSS-COND-2011.fits and several folders called
        Z[-/+]X.X where X.X are the metallicities (e.g. Z-0.0, Z+1.0, etc)
        """
        # Change hdd to a class variable depending on an env param.
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff])
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        selected_SED = self.moddir + 'PHOENIXv2/Z'
        metal_add = ''
        if sel_z < 0:
            metal_add = str(sel_z)
        if sel_z == 0:
            metal_add = '-0.0'
        if sel_z > 0:
            metal_add = '+' + str(sel_z)
        selected_SED += metal_add
        selected_SED += '/lte'
        selected_SED += str(sel_teff) if len(str(sel_teff)) == 5 else \
            '0' + str(sel_teff)
        selected_SED += '-' + str(sel_logg) + '0'
        selected_SED += metal_add
        selected_SED += '.PHOENIX-ACES-AGSS-COND-2011-HiRes.fits'
        flux = fits.open(selected_SED)[0].data
        flux *= (u.erg / u.s / u.cm ** 2 / u.cm).to(
            u.erg / u.s / u.cm ** 2 / u.um)
        return flux

    def fetch_btsettl(self):
        """Fetch correct BT-Settl SED file.

        The directory containing the BT-Settl spectra must be called BTSettl
        Within BTSettl there should be yet another directory
        called AGSS2009, within BTSettl/AGSS2009 there should be the SED fits
        files with the following naming convention:

        lteTTT-G.G[-/+]Z.Za+0.0.BT-Settl.AGSS2009.fits

        where TTT are the first 3 digits of the effective temperature if it's a
        number over 10000, else it's the first 2 digit prepended by a 0.
        G.G is the log g and Z.Z the metallicity.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff]) // 100
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        metal_add = ''
        if sel_z < 0:
            metal_add = str(sel_z)
        if sel_z == 0:
            metal_add = '-0.0'
        if sel_z > 0:
            metal_add = '+' + str(sel_z)
        selected_SED = self.moddir + 'BTSettl/AGSS2009/lte'
        selected_SED += str(sel_teff) if len(str(sel_teff)) == 3 else \
            '0' + str(sel_teff)
        selected_SED += '-' + str(sel_logg) + metal_add + 'a+*'
        gl = glob.glob(selected_SED)
        selected_SED = gl[0]
        tab = Table(fits.open(selected_SED)[1].data)
        flux = np.array(tab['FLUX'].tolist()) * conversion
        wave = np.array(tab['WAVELENGTH'].tolist()) * u.angstrom.to(u.um)
        return wave, flux

    def fetch_btnextgen(self):
        """Fetch correct BT-NextGen SED file.

        The directory containing the BT-NextGen spectra must be called
        BTNextGen. Within BTNextGen there should be yet another directory
        called AGSS2009, within BTNextGen/AGSS2009 there should be the SED fits
        files with the following naming convention:

        lteTTT-G.G[-/+]Z.Za+0.0..BT-NextGen.AGSS2009.fits

        where TTT are the first 3 digits of the effective temperature if it's a
        number over 10000, else it's the first 2 digit prepended by a 0.
        G.G is the log g and Z.Z the metallicity.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff]) // 100
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        metal_add = ''
        if sel_z < 0:
            metal_add = str(sel_z)
        if sel_z == 0:
            metal_add = '-0.0'
        if sel_z > 0:
            metal_add = '+' + str(sel_z)
        selected_SED = self.moddir + 'BTNextGen/AGSS2009/lte'
        selected_SED += str(sel_teff) if len(str(sel_teff)) == 3 else \
            '0' + str(sel_teff)
        selected_SED += '-' + str(sel_logg) + metal_add + 'a+*'
        gl = glob.glob(selected_SED)
        selected_SED = gl[0]
        tab = Table(fits.open(selected_SED)[1].data)
        flux = np.array(tab['FLUX'].tolist()) * conversion
        wave = np.array(tab['WAVELENGTH'].tolist()) * u.angstrom.to(u.um)
        return wave, flux

    def fetch_btcond(self):
        """Fetch correct BT-COND SED file.

        The directory containing the BT-COND spectra must be called
        BTCOND. Within BTCOND there should be yet another directory
        called CIFIST2011, within BTCOND/CIFIST2011 there should be the SED
        fits files with the following naming convention:

        lteTTT-G.G[-/+]Z.Za+0.0..BT-Cond.CIFIST2011.fits

        where TTT are the first 3 digits of the effective temperature if it's a
        number over 10000, else it's the first 2 digit prepended by a 0.
        G.G is the log g and Z.Z the metallicity.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff]) // 100
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        metal_add = ''
        if sel_z < 0:
            metal_add = str(sel_z)
        if sel_z == 0:
            metal_add = '-0.0'
        if sel_z > 0:
            metal_add = '+' + str(sel_z)
        selected_SED = self.moddir + 'BTCond/CIFIST2011/lte'
        selected_SED += str(sel_teff) if len(str(sel_teff)) == 3 else \
            '0' + str(sel_teff)
        selected_SED += '-' + str(sel_logg) + metal_add + 'a+*'
        gl = glob.glob(selected_SED)
        selected_SED = gl[0]
        tab = Table(fits.open(selected_SED)[1].data)
        flux = np.array(tab['FLUX'].tolist()) * conversion
        wave = np.array(tab['WAVELENGTH'].tolist()) * u.angstrom.to(u.um)
        return wave, flux

    def fetch_ck04(self):
        """Fetch correct Castelli-Kurucz 2004 SED file.

        The directory containing the Castelli-Kurucz spectra must be called
        Castelli_Kurucz. Within Castelli_Kurucz there should be a group of
        directories called ck[pm]ZZ where ZZ is the metalicity without the dot.
        Within each directory there are fits files named:

        ck[pm]ZZ_TTTT.fits

        where ZZ is metalicity as previous and TTTT is the effective
        temperature.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff])
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        metal_add = ''
        if sel_z < 0:
            metal_add = 'm' + str(-sel_z).replace('.', '')
        if sel_z == 0:
            metal_add = 'p00'
        if sel_z > 0:
            metal_add = 'p' + str(sel_z).replace('.', '')
        name = 'ck' + metal_add
        lgg = 'g{:.0f}'.format(sel_logg * 10)
        selected_SED = self.moddir + 'Castelli_Kurucz/' + name + '/' + name
        selected_SED += '_' + str(sel_teff) + '.fits'
        tab = Table(fits.open(selected_SED)[1].data)
        wave = np.array(tab['WAVELENGTH'].tolist()) * u.angstrom.to(u.um)
        flux = np.array(tab[lgg].tolist()) * conversion
        return wave, flux

    def fetch_kurucz(self):
        """Fetch correct Kurucz 1993 SED file.

        The directory containing the Kurucz spectra must be called
        Kurucz. Within Kurucz there should be a group of
        directories called k[pm]ZZ where ZZ is the metalicity without the dot.
        Within each directory there are fits files named:

        k[pm]ZZ_TTTT.fits

        where ZZ is metalicity as previous and TTTT is the effective
        temperature.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff])
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        metal_add = ''
        if sel_z < 0:
            metal_add = 'm' + str(-sel_z).replace('.', '')
        if sel_z == 0:
            metal_add = 'p00'
        if sel_z > 0:
            metal_add = 'p' + str(sel_z).replace('.', '')
        name = 'k' + metal_add
        lgg = 'g{:.0f}'.format(sel_logg * 10)
        selected_SED = self.moddir + 'Kurucz/' + name + '/' + name
        selected_SED += '_' + str(sel_teff) + '.fits'
        tab = Table(fits.open(selected_SED)[1].data)
        wave = np.array(tab['WAVELENGTH'].tolist()) * u.angstrom.to(u.um)
        flux = np.array(tab[lgg].tolist()) * conversion
        return wave, flux

    def fetch_coelho(self):
        """Fetch correct Coelho 2014 SED file.

        The directory containing the Coelho spectra must be called
        Coelho14. Within Coelho14 there should be a group of
        files called t[0X]XXXX_g[+-]Y.Y_[mp]ZZp0[14]_sed.fits
        where X is the temperature, Y is the logg and Z the metallicity.
        """
        conversion = (u.erg / u.s / u.cm ** 2 / u.angstrom)
        conversion = conversion.to(u.erg / u.s / u.cm ** 2 / u.um)
        teff = self.theta[0]
        logg = self.theta[1]
        z = self.theta[2]
        select_teff = np.argmin((abs(teff - np.unique(self.star.teff))))
        select_logg = np.argmin((abs(logg - np.unique(self.star.logg))))
        select_z = np.argmin((abs(z - np.unique(self.star.z))))
        sel_teff = int(np.unique(self.star.teff)[select_teff])
        sel_logg = np.unique(self.star.logg)[select_logg]
        sel_z = np.unique(self.star.z)[select_z]
        sel_teff = str(sel_teff) if sel_teff >= 1e5 else '0{}'.format(sel_teff)
        selected_SED = self.moddir + 'Coelho14/t' + sel_teff + '_g'
        sel_logg = '+{:.1f}'.format(sel_logg) if sel_logg > 0 else '-0.5'
        selected_SED += sel_logg
        if sel_z < 0:
            selected_SED += '_m{:02.0f}'.format(-sel_z * 10)
        else:
            selected_SED += '_p{:02.0f}'.format(sel_z * 10)

        selected_SED = glob.glob(selected_SED + 'p0[04]_sed.fits')
        selected_SED = selected_SED[0]
        hdul = fits.open(selected_SED)
        head = hdul[0].header
        data = hdul[0].data
        flux = data * conversion
        CRVAL1 = head['CRVAL1']
        CDEL1 = head['CDELT1']
        wave = 10 ** np.array(
            [CRVAL1 + CDEL1 * i for i in range(data.shape[0])])
        wave *= u.angstrom.to(u.um)
        return wave, flux

    def __create_titles(self, titles, theta, theta_up, theta_lo):
        new_titles = np.empty(titles.shape[0], dtype=object)
        for i, param in enumerate(titles):
            if param == 'teff':
                new_titles[i] = r'Teff ='
            if param == 'logg':
                new_titles[i] = r'    Log g ='
            if param == 'z':
                new_titles[i] = r'        [Fe/H] ='
            if param == 'dist':
                new_titles[i] = r'    D ='
            if param == 'rad':
                new_titles[i] = r'R ='
            if param == 'norm':
                new_titles[i] = r'    (R/D)$^2$ ='
            if param == 'Av':
                new_titles[i] = r'Av ='
            if param == 'inflation':
                new_titles[i] = r'$\sigma$ ='
            if param == 'rad' or param == 'dist':
                new_titles[i] += '{:.3f}'.format(theta[i])
                new_titles[i] += r'$^{+' + \
                                 '{:.3f}'.format(theta_up[i] - theta[i])
                new_titles[i] += r'}_{-' + \
                                 '{:.3f}'.format(theta[i] - theta_lo[i])
                new_titles[i] += r'}$'
            else:
                new_titles[i] += '{:.2f}'.format(theta[i])
                new_titles[i] += r'$^{+' + \
                                 '{:.2f}'.format(theta_up[i] - theta[i])
                new_titles[i] += r'}_{-' + \
                                 '{:.2f}'.format(theta[i] - theta_lo[i])
                new_titles[i] += r'}$'
        return new_titles

    def __create_labels(self, labels):
        new_labels = np.empty(labels.shape[0], dtype=object)
        for i, param in enumerate(labels):
            if param == 'teff':
                new_labels[i] = r'Teff (K)'
            if param == 'logg':
                new_labels[i] = r'Log g'
            if param == 'z':
                new_labels[i] = r'[Fe/H]'
            if param == 'dist':
                new_labels[i] = r'D (pc)'
            if param == 'rad':
                new_labels[i] = r'R $($R$_\odot)$'
            if param == 'norm':
                new_labels[i] = r'(R/D)'
            if param == 'Av':
                new_labels[i] = r'Av'
            if param == 'inflation':
                new_labels[i] = r'$\sigma$'
        return new_labels

    def __read_config(self):
        """Read plotter configuration file."""
        if self.settings_dir is None:
            settings = open(filesdir + '/plot_settings.dat', 'r')
        else:
            settings = open(self.settings_dir, 'r')
        for line in settings.readlines():
            if line[0] == '#' or line[0] == '\n':
                continue
            splt = line.split(' ')
            attr = splt[0]
            if attr == 'figsize':
                vals = splt[1].split('\n')[0].split(',')
                val = (int(vals[0]), int(vals[1]))
            elif attr == 'hr_figsize':
                vals = splt[1].split('\n')[0].split(',')
                val = (int(vals[0]), int(vals[1]))
            elif 'alpha' in attr:
                val = float(splt[1].split('\n')[0])
            else:
                try:
                    val = int(splt[1].split('\n')[0])
                except ValueError:
                    val = splt[1].split('\n')[0]
            setattr(self, attr, val)