<reponame>drtaiga/Thermistor_Notebook
#Import the schemdraw modules for drawing circuit schematics
import schemdraw.elements as elm
import schemdraw

import numpy as np
import matplotlib.pyplot as plt

from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

#import scipy.optimize as fsolve
from scipy.optimize import fsolve
import scipy.optimize as opt

#---------------------------------
#-- Voltage divider schematics
def draw_dividers():

    v_divider = schemdraw.Drawing(inches_per_unit=.5, unit=2)
    v_divider.add(elm.DOT,open='true',label='$V_o$')
    vdr1 = v_divider.add(elm.RES, d='down', label='$R_0$')
    v_divider.add(elm.DOT)
    vdr2 = v_divider.add(elm.RES, d='down', label='$R_1$')
    v_divider.add(elm.GND, botlabel='Fig. 1A',lblofst=1)
    v_divider.add(elm.LINE, d='right', xy=vdr1.end, l=v_divider.unit)
    v_divider.add(elm.DOT, open='true', label='$V^{(A)}$')
    v_divider.here = [v_divider.here[0]+2*v_divider.unit, v_divider.here[1]]
    vdr3=v_divider.add(elm.DOT)
    v_divider.add(elm.RES, d='up', label='$R_1$')
    v_divider.add(elm.DOT, open='true', label='$V_o$')
    v_divider.add(elm.RES,d='down',xy=vdr3.start, label='$R_0$')
    v_divider.add(elm.GND, botlabel='Fig. 1B',lblofst=1)
    v_divider.add(elm.LINE, d='right', xy=vdr3.end, l=v_divider.unit)
    v_divider.add(elm.DOT, open='true', label='$V^{(B)}$')
    return v_divider.draw()

def draw_dividers2():

    #SchemDraw schematic of a generic voltage divider circuit
    v2_divider = schemdraw.Drawing(inches_per_unit=.5, unit=2)
    v2_divider.add(elm.DOT,open='true',label='$V_o$')
    v2dr1 = v2_divider.add(elm.RES, d='down', label='$R_0$')
    v2_divider.add(elm.DOT)
    v2dr2 = v2_divider.add(elm.RES_VAR, reverse='true',d='down', label='$R(T)$')
    v2_divider.add(elm.GND, botlabel='Fig. 2A',lblofst=1)
    v2_divider.add(elm.LINE, d='right', xy=v2dr1.end, l=v2_divider.unit)
    v2_divider.add(elm.DOT, open='true', label='$V^{(2A)}$')
    v2_divider.here = [v2_divider.here[0]+2*v2_divider.unit, v2_divider.here[1]]
    v2dr3=v2_divider.add(elm.DOT)
    v2_divider.add(elm.RES_VAR, d='up', flip='true',label='$R(T)$')
    v2_divider.add(elm.DOT, open='true', label='$V_o$')
    v2_divider.add(elm.RES,d='down',xy=v2dr3.start, label='$R_0$')
    v2_divider.add(elm.GND, botlabel='Fig. 2B',lblofst=1)
    v2_divider.add(elm.LINE, d='right', xy=v2dr3.end, l=v2_divider.unit)
    v2_divider.add(elm.DOT, open='true', label='$V^{(2B)}$')
    return v2_divider.draw()

def draw_divamp():

    div_amp = schemdraw.Drawing(inches_per_unit=.5, unit=2)

    op = div_amp.add(elm.Opamp,flip='true')
    div_amp.add(elm.LINE, d='left', xy=op.in2, l=div_amp.unit*.75)
    p1=div_amp.add(elm.DOT)
    div_amp.add(elm.LINE,l=1.0, d='left')
    div_amp.add(elm.RES_VAR,d='left',label="$R(T)$",reverse='true',flip='true')
    div_amp.add(elm.LINE,d='left',l=0.5)
    div_amp.add(elm.DOT,open='true',label='$V_o$')
    div_amp.add(elm.LINE,d='down', l=div_amp.unit*1, xy=p1.start)
    div_amp.add(elm.GND)
    p3=div_amp.add(elm.LINE,d='left', xy=op.in1, l=div_amp.unit/4)
    div_amp.add(elm.LINE,d='right', xy=op.out,l=1)
    div_amp.add(elm.DOT,open='true',label='$V$')
    div_amp.add(elm.LINE,xy=p3.end,d='down',l=.75)
    div_amp.add(elm.GND)
    div_amp.add(elm.LINE,d='down', xy=op.vd, l=.5)
    div_amp.add(elm.GND)
    div_amp.add(elm.LINE,d='up', xy=op.vs, l=.5)
    div_amp.add(elm.VDD,label='$V_0$')
    div_amp.add(elm.LINE,d='down', xy=op.n2, l=.5)
    div_amp.add(elm.LINE,d='right',l=.25)
    div_amp.add(elm.DOT,rgtlabel='$V_{ref}$')
    return div_amp.draw()
#--- Voltage divider schematics
#---------------------------------

#---------------------------------
#--- Wheatstone bridge schematics
def draw_bridge():

    wbridge = schemdraw.Drawing(inches_per_unit=.5, unit=3)
    br1 = wbridge.add(elm.RES,theta=45, toplabel='$R_1$')
    br_top=wbridge.add(elm.DOT)
    br2 = wbridge.add(elm.RES,theta=-45, toplabel='$R_3$')
    br_right=wbridge.add(elm.DOT)
    br3 = wbridge.add(elm.RES_VAR, theta=-135, botlabel='$R(T)$', flip='true',reverse='true')
    br_bot=wbridge.add(elm.DOT,botlabel='Fig. 3A',lblofst=1)
    br4 = wbridge.add(elm.RES,theta=135, botlabel='$R_2$')
    br_left=wbridge.add(elm.DOT)
    wbridge.add(elm.LINE,d='right',xy=br_top.start,l=wbridge.unit*1.25)
    wbridge.add(elm.DOT, open=True, label='$V_T^{(3A)}$')
    wbridge.add(elm.LINE,d='right',xy=br_bot.start,l=wbridge.unit*1.25)
    wbridge.add(elm.DOT,open=True, label='$V_B^{(3A)}$')
    wbridge.add(elm.LINE,d='left',xy=br_left.start,l=wbridge.unit/4)
    wbridge.add(elm.VDD,label='$V_0$')
    wbridge.add(elm.LINE,d='right',xy=br_right.start,l=wbridge.unit/4)
    wbridge.add(elm.GND)

    wbridge.here = [wbridge.here[0]+1.5*wbridge.unit, wbridge.here[1]]
    br2_left=wbridge.add(elm.DOT)
    br5=wbridge.add(elm.RES,theta=45, toplabel='$R_1$')
    br2_top=wbridge.add(elm.DOT)
    br6 = wbridge.add(elm.Resistor(theta=-45, toplabel='$R_3$'))
    br2_right=wbridge.add(elm.Dot())
    br7 = wbridge.add(elm.RES, theta=-135, botlabel='$R_2$', flip='true',reverse='true')
    br2_bot=wbridge.add(elm.DOT,botlabel='Fig. 3B',lblofst=1)
    br8 = wbridge.add(elm.RES_VAR,theta=135, flip='true',botlabel='$R(T)$')

    wbridge.add(elm.LINE,d='left',xy=br2_left.start,l=wbridge.unit/4)
    wbridge.add(elm.VDD,label='$V_0$')
    wbridge.add(elm.LINE,d='right',xy=br2_right.start,l=wbridge.unit/4)
    wbridge.add(elm.GND)
    wbridge.add(elm.LINE,d='right',xy=br2_top.start,l=wbridge.unit*1.25)
    wbridge.add(elm.DOT, open=True, label='$V_T^{(3B)}$')
    wbridge.add(elm.LINE,d='right',xy=br2_bot.start,l=wbridge.unit*1.25)
    wbridge.add(elm.DOT, open=True, label='$V_B^{(3B)}$')

    return wbridge.draw()

def draw_bridgeamp():

    wwbridge = schemdraw.Drawing(inches_per_unit=.5, unit=3)
    wbr1 = wwbridge.add(elm.RES,theta=45, toplabel='$R_1$')
    wbr_top=wwbridge.add(elm.DOT)
    #wbridge.add(elm.Vdd(label='$V_0$'))
    wbr2 = wwbridge.add(elm.RES,theta=-45, toplabel='$R_3$')
    wbr_right=wwbridge.add(elm.DOT)
    wbr3 = wwbridge.add(elm.RES_VAR,theta=-135,flip='true', botlabel='$R(T)$')
    wbr_bot=wwbridge.add(elm.DOT)
    #wbridge.add(elm.Ground())
    wbr4 = wwbridge.add(elm.RES,theta=135, botlabel='$R_2$')
    wbr_left=wwbridge.add(elm.DOT)
    wwbridge.add(elm.LINE,d='right',xy=wbr_top.start,l=wwbridge.unit*1.25)
    rn1=wwbridge.add(elm.DOT,open=True, label='$V_T$')
    wwbridge.add(elm.LINE,d='right',xy=wbr_bot.start,l=wwbridge.unit*1.25)
    rn2=wwbridge.add(elm.DOT, open=True, botlabel='$V_B$')
    wwbridge.add(elm.LINE,d='left',xy=wbr_left.start,l=wwbridge.unit/4)
    wwbridge.add(elm.VDD,label='$V_0$')
    wwbridge.add(elm.LINE,d='right',xy=wbr_right.start,l=wwbridge.unit/4)
    wwbridge.add(elm.GND)
    wwbridge.add(elm.LINE,d='down',xy=rn1.start,l=wwbridge.unit*0.5)
    wwbridge.add(elm.LINE,d='right',l=wwbridge.unit*0.5)
    O1=wwbridge.add(elm.OPAMP,anchor='in2',flip='true')
    wwbridge.add(elm.LINE,d='up',xy=rn2.start,l=wwbridge.unit*0.5)
    wwbridge.add(elm.LINE,d='left', l=wwbridge.unit*0.5, xy=O1.in1)
    wwbridge.add(elm.LINE,d='up', xy=O1.vs, l=1/2)
    wwbridge.add(elm.VDD,label='$V_0$')
    wwbridge.add(elm.LINE,d='down', xy=O1.vd, l=1/2)
    wwbridge.add(elm.GND)
    wwbridge.add(elm.LINE,d='right', xy=O1.out,l=1)
    wwbridge.add(elm.DOT,open='true',label='$V$')
    wwbridge.add(elm.LINE,d='down', xy=O1.n2, l=.5)
    wwbridge.add(elm.LINE,d='right',l=.25)
    wwbridge.add(elm.DOT,rgtlabel='$V_{ref}$')

    return wwbridge.draw()

#--- Wheatstone bridge schematics
#---------------------------------

#---------------------------------
# Voltage divider: thermistor-to-ground configuration (Configuration 'A')
def div_tg(T, Vin, res0, B, R25, T25):
    #R25 = 1.0e4
    #B = 3977.0
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f=res/(res0+res) * Vin
    return f

def ddt_div_tp(T, Vin, res0, B, R25, T25):

    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f=Vin*B*res0/T**2.0/(res0+res)**2.0 * res

    return f

def ddt_div_tg(T, Vin, res0, B, R25, T25):

    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f= Vin*(B*res**2.0 /(T**2.0 * (res0+res)**2.0) - \
        B*res/(T**2.0 * (res0+res)))

    return f

# Voltage divider: thermistor-to-power configuration (Configuration 'B')
def div_tp(T, Vin, res0, B, R25, T25):
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f = res0/(res0+res) * Vin
    return f

# Bridge: thermistor-to-ground configuration
def b_tg(T, Vin, res0, B, R25, T25, rho):
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f = -(1/(1.0 + rho)-res/(res0+res)) * Vin
    return  f

# Bridge: thermistor-to-power configuration
def b_tp(T, Vin, res0, B, R25, T25, rho):
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f = -(rho/(1.0+rho) - res0/(res0+res)) * Vin
    return f

def div_plot(R0):
    R25 = 1.0e4
    B = 3977.0
    T25 = 273.15 + 25.0
    V_in=3.3
    # Input temperatures in Kelvin
    T25 = 273.15 + 25.0
    temp_K = np.arange(250.0, 310.0, 0.2)
    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    out_tg=np.clip(div_tg(temp_K, V_in, R0, B, R25, T25),0,V_in)
    out_tp=np.clip(div_tp(temp_K, V_in, R0, B, R25, T25),0,V_in)

    t_infl = opt.fsolve(f_inflection, 280., args=R0)

    slope_tg=ddt_div_tg(t_infl, V_in, R0, B, R25, T25)
    slope_tp=ddt_div_tp(t_infl, V_in, R0, B, R25, T25)

    lin_tg = div_tg(t_infl, V_in, R0, B, R25, T25) + \
        slope_tg*(temp_K - t_infl)
    lin_tp = div_tp(t_infl, V_in, R0, B, R25, T25) + \
        slope_tp*(temp_K - t_infl)

    ax.plot(temp_K,out_tg,label='$V^{(2A)}$')
    ax.plot(temp_K,out_tp,label='$V^{(2B)}$')
    plt.plot(t_infl, np.clip(div_tg(t_infl, V_in, R0, B, R25, T25),0,V_in), \
             marker='o',color="gray" )
    plt.plot(t_infl, np.clip(div_tp(t_infl, V_in, R0, B, R25, T25),0,V_in), \
             marker='o', color="gray" )
    plt.plot(temp_K,lin_tg,':', color="gray")
    plt.plot(temp_K,lin_tp,':', color="gray")
    plt.ylim(0.,4.0)
    plt.legend()
    plt.title('Outputs from the Thermistor Voltage Divider Configurations \n (10K Thermistor: Vishay Model Booo )')
    plt.ylabel('Divider Circuit Output (Volts)')
    plt.xlabel('Temperature (Kelvin)')
    return plt.show()

def divider_plot():

    return widgets.interact(div_plot, \
        R0=widgets.IntSlider(min=5000, max=100000, step=500, value=17900.,description=r'\(R_0 (\Omega) \)'))

# Bridge: thermistor-to-ground configuration
def bridge_tg(T, Vin, res0, B, R25, T25,rho):
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f=(1.0/(rho+1.0)-res/(res0+res)) * Vin
    return  f

# Bridge: thermistor-to-power configuration
def bridge_tp(T, Vin, res0, B, R25, T25,rho):
    res = R25 * np.exp(B*(1.0/T-1.0/T25))
    f=(1.0/(rho+1.0) - res0/(res0+res)) * Vin
    return f

def br_plot(R0, RHO):

    R25 = 1.0e4
    B = 3977.0
    T25 = 273.15 + 25.0
    V_in=3.3

    temp_K = np.arange(250.0, 310.0, 0.2)

    fig, ax = plt.subplots(1, 1, figsize=(6, 4))

    out_tg=np.clip(bridge_tg(temp_K, V_in, R0, \
                                 B, R25, T25,RHO), 0,V_in)
    out_tp=np.clip(bridge_tp(temp_K, V_in, R0, \
                                 B, R25, T25,RHO),0,V_in)

    t_infl = opt.fsolve(f_inflection, 280., args=R0)

    slope_tg=-ddt_div_tg(t_infl, V_in, R0, B, R25, T25)
    slope_tp=-ddt_div_tp(t_infl, V_in, R0, B, R25, T25)

    lin_tg = bridge_tg(t_infl, V_in, R0, B, R25, T25, RHO) + \
        slope_tg*(temp_K - t_infl)
    lin_tp = bridge_tp(t_infl, V_in, R0, B, R25, T25, RHO) + \
        slope_tp*(temp_K - t_infl)

    ax.plot(temp_K,out_tg,label='$V^{(3A)}$')
    ax.plot(temp_K,out_tp,label='$V^{(3B)}$')

    plt.plot(t_infl, np.clip(bridge_tg(t_infl, V_in, R0, B, R25, T25,RHO),0,V_in), \
             marker='o',color="gray" )
    plt.plot(t_infl, np.clip(bridge_tp(t_infl, V_in, R0, B, R25, T25, RHO),0,V_in), \
             marker='o', color="gray" )
    plt.plot(temp_K,lin_tg,':', color="gray")
    plt.plot(temp_K,lin_tp,':', color="gray")
    #ax.plot(temp_K,out_div_tp,label='$V_{div}$')
    plt.ylim(0.,3.5)
    plt.legend()
    plt.title('Outputs from the Amplified Voltage Divider Configurations')
    plt.ylabel('Divider Circuit Output (Volts)')
    plt.xlabel('Temperature (Kelvin)')

    return plt.show()

def bridge_plot2():

    return widgets.interact(br_plot, \
        R0=widgets.IntSlider(min=5000, max=100000, step=100, value=17500.,description=r'\(R_0 (\Omega) \)'), \
        RHO=widgets.FloatSlider(min=0, max=0.5, step=.005, value=0,description=r'\(\rho\)'))

def amp_plot(R0,V_ref,RHO,A_G):
    R25 = 1.0e4
    B = 3977.0
    T25 = 273.15 + 25.0
    V_in=3.3
    temp_K = np.arange(250.0, 310.0, 0.2)
    #temp_K = np.arange(250.0, 310.0, 0.2)
    fig, ax = plt.subplots(1, 1, figsize=(6,4))
    out_tg=np.clip(A_G*b_tg(temp_K, V_in, R0, \
                                 B, R25, T25,RHO)+V_ref,0.0,V_in)
    out_tp=np.clip(A_G*b_tp(temp_K, V_in, R0, \
                                 B, R25, T25,RHO)+V_ref,0.0,V_in)
    out_div_tp=np.clip(A_G*div_tp(temp_K, V_in, R0, B, R25, T25)+V_ref,0,V_in)

    #ax.plot(temp_K,out_tg,label='$V_{TG}$')
    ax.plot(temp_K,out_tp,label='$V_{br}$')
    ax.plot(temp_K,out_div_tp,label='$V_{div}$')
    plt.ylim(0.,3.5)
    plt.legend()
    plt.title('Outputs from the Amplified Voltage Divider Configurations')
    plt.ylabel('Divider Circuit Output (Volts)')
    plt.xlabel('Temperature (Kelvin)')

    return plt.show()

def ampcircuits_plot():

    return widgets.interact(amp_plot, \
        R0=widgets.IntSlider(min=5000, max=100000, step=100,value=17500.,description=r'\(R_0 (\Omega) \)'), \
        V_ref=widgets.FloatSlider(min=0, max=3.3, step=.01,value=0,description=r'\(V_{\it ref} (V)\)'), \
        RHO=widgets.FloatSlider(min=0, max=1, step=.01,value=0,description=r'\(\rho\)'), \
        A_G=widgets.FloatSlider(min=1, max=2, step=.01,value=0,description=r'\(A_{G}\)'))

#---------------------------------

def therm_res(T, B, R25, T25):

    #B-Parameter equation for thermistor resistance

    res = R25 * np.exp(B*(1.0/T-1.0/T25))

    return res

def d_therm_res(T, B, R25, T25):

    f= - B/T**2.0 * therm_res(T,B,R25,T25)

    return f

def thermres_plot():

    #Define the temperature range in Kelvin
    temp_K = np.arange(250.0, 310.0, 0.2)

    #Define thermistor parameters

    R25 = 1.0e4
    T25 = 273.15 + 25.0
    B = 3977.0

    r_t = therm_res(temp_K, B, R25, T25)

    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    ax.plot(temp_K,r_t/1000.)
    plt.title('Temperature Dependence of Thermistor Resistance \n (10K Thermistor: Vishay Model )');
    plt.ylabel('Thermistor Resistance ($k\Omega$)');
    plt.xlabel('Temperature (Kelvin)');

    return plt.show()

def inflection():

    R25 = 1.0e4
    T25 = 273.15 + 25.0
    B = 3977.0

    #Define the temperature range in Kelvin
    temp_K = np.arange(250.0, 310.0, 0.2)

    res0= (B-2.0*temp_K)/(B+2.0*temp_K)*therm_res(temp_K,B,R25,T25)

    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    ax.plot(temp_K,res0/1000.,temp_K,therm_res(temp_K,B,R25,T25)/1000.)
    plt.title('Temperature Dependence of Thermistor Resistance \n (10K Thermistor: Vishay Model )');
    plt.ylabel('Thermistor Resistance ($k\Omega$)');
    plt.xlabel('Temperature (Kelvin)');

    return plt.show()

def f_inflection(tkel, *data):

    R0=data

    R25 = 1.0e4
    T25 = 273.15 + 25.0
    B = 3977.0

    f = (B-2.0*tkel)/(B+2.0*tkel)*therm_res(tkel,B,R25,T25)-R0

    return f

def f_prime(tkel):

    R25 = 1.0e4
    T25 = 273.15 + 25.0
    B = 3977.0

    fp = - (2.0 + 2.0*(B-2.0*tkel)/(B+2.0*tkel) + B*(B-2.0*tkel)/tkel**2.0) \
        * therm_res(tkel,B,R25,T25) / (B+2.0*tkel)

    return fp


#!/usr/bin/env python

""" Assemble the specified FEM matrix.
"""

import argparse
import pymesh
import scipy.sparse

def parse_args():
    parser = argparse.ArgumentParser(__doc__);
    parser.add_argument("--type", "-t", help="The type of matrix",
            choices=["stiffness", "mass", "lumped_mass", "laplacian",
                "displacement_strain", "elasticity_tensor",
                "engineer_strain_stress", "rigid_motion", "gradient"]);
    parser.add_argument("input_mesh");
    parser.add_argument("output_matrix");
    return parser.parse_args();

def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    assembler = pymesh.Assembler(mesh);
    M = assembler.assemble(args.type);
    scipy.sparse.save_npz(args.output_matrix, M);

if __name__ == "__main__":
    main();


<filename>pypower/qps_pips.py
# Copyright (c) 1996-2015 PSERC. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

"""Uses the Python Interior Point Solver (PIPS) to solve QP (quadratic
programming) problems.
"""

from numpy import Inf, ones, zeros, dot

from scipy.sparse import csr_matrix as sparse

from pypower.pips import pips

def qps_pips(H, c, A, l, u, xmin=None, xmax=None, x0=None, opt=None):
    """Uses the Python Interior Point Solver (PIPS) to solve the following
    QP (quadratic programming) problem::

            min 1/2 x'*H*x + C'*x
             x

    subject to::

            l <= A*x <= u       (linear constraints)
            xmin <= x <= xmax   (variable bounds)

    Note the calling syntax is almost identical to that of QUADPROG from
    MathWorks' Optimization Toolbox. The main difference is that the linear
    constraints are specified with C{A}, C{L}, C{U} instead of C{A}, C{B},
    C{Aeq}, C{Beq}.

    Example from U{http://www.uc.edu/sashtml/iml/chap8/sect12.htm}:

        >>> from numpy import array, zeros, Inf
        >>> from scipy.sparse import csr_matrix
        >>> H = csr_matrix(array([[1003.1,  4.3,     6.3,     5.9],
        ...                       [4.3,     2.2,     2.1,     3.9],
        ...                       [6.3,     2.1,     3.5,     4.8],
        ...                       [5.9,     3.9,     4.8,     10 ]]))
        >>> c = zeros(4)
        >>> A = csr_matrix(array([[1,       1,       1,       1   ],
        ...                       [0.17,    0.11,    0.10,    0.18]]))
        >>> l = array([1, 0.10])
        >>> u = array([1, Inf])
        >>> xmin = zeros(4)
        >>> xmax = None
        >>> x0 = array([1, 0, 0, 1])
        >>> solution = qps_pips(H, c, A, l, u, xmin, xmax, x0)
        >>> round(solution["f"], 11) == 1.09666678128
        True
        >>> solution["converged"]
        True
        >>> solution["output"]["iterations"]
        10

    All parameters are optional except C{H}, C{c}, C{A} and C{l} or C{u}.
    @param H: Quadratic cost coefficients.
    @type H: csr_matrix
    @param c: vector of linear cost coefficients
    @type c: array
    @param A: Optional linear constraints.
    @type A: csr_matrix
    @param l: Optional linear constraints. Default values are M{-Inf}.
    @type l: array
    @param u: Optional linear constraints. Default values are M{Inf}.
    @type u: array
    @param xmin: Optional lower bounds on the M{x} variables, defaults are
                 M{-Inf}.
    @type xmin: array
    @param xmax: Optional upper bounds on the M{x} variables, defaults are
                 M{Inf}.
    @type xmax: array
    @param x0: Starting value of optimization vector M{x}.
    @type x0: array
    @param opt: optional options dictionary with the following keys, all of
                which are also optional (default values shown in parentheses)
                  - C{verbose} (False) - Controls level of progress output
                    displayed
                  - C{feastol} (1e-6) - termination tolerance for feasibility
                    condition
                  - C{gradtol} (1e-6) - termination tolerance for gradient
                    condition
                  - C{comptol} (1e-6) - termination tolerance for
                    complementarity condition
                  - C{costtol} (1e-6) - termination tolerance for cost
                    condition
                  - C{max_it} (150) - maximum number of iterations
                  - C{step_control} (False) - set to True to enable step-size
                    control
                  - C{max_red} (20) - maximum number of step-size reductions if
                    step-control is on
                  - C{cost_mult} (1.0) - cost multiplier used to scale the
                    objective function for improved conditioning. Note: The
                    same value must also be passed to the Hessian evaluation
                    function so that it can appropriately scale the objective
                    function term in the Hessian of the Lagrangian.
    @type opt: dict

    @rtype: dict
    @return: The solution dictionary has the following keys:
               - C{x} - solution vector
               - C{f} - final objective function value
               - C{converged} - exit status
                   - True = first order optimality conditions satisfied
                   - False = maximum number of iterations reached
                   - None = numerically failed
               - C{output} - output dictionary with keys:
                   - C{iterations} - number of iterations performed
                   - C{hist} - dictionary of arrays with trajectories of the
                     following: feascond, gradcond, coppcond, costcond, gamma,
                     stepsize, obj, alphap, alphad
                   - C{message} - exit message
               - C{lmbda} - dictionary containing the Langrange and Kuhn-Tucker
                 multipliers on the constraints, with keys:
                   - C{eqnonlin} - nonlinear equality constraints
                   - C{ineqnonlin} - nonlinear inequality constraints
                   - C{mu_l} - lower (left-hand) limit on linear constraints
                   - C{mu_u} - upper (right-hand) limit on linear constraints
                   - C{lower} - lower bound on optimization variables
                   - C{upper} - upper bound on optimization variables

    @see: L{pips}

    @author: <NAME> (PSERC Cornell)
    """
    if isinstance(H, dict):
        p = H
    else:
        p = {'H': H, 'c': c, 'A': A, 'l': l, 'u': u}
        if xmin is not None: p['xmin'] = xmin
        if xmax is not None: p['xmax'] = xmax
        if x0 is not None: p['x0'] = x0
        if opt is not None: p['opt'] = opt

    if 'H' not in p or p['H'] == None:#p['H'].nnz == 0:
        if p['A'] is None or p['A'].nnz == 0 and \
           'xmin' not in p and \
           'xmax' not in p:
#           'xmin' not in p or len(p['xmin']) == 0 and \
#           'xmax' not in p or len(p['xmax']) == 0:
            print('qps_pips: LP problem must include constraints or variable bounds')
            return
        else:
            if p['A'] is not None and p['A'].nnz >= 0:
                nx = p['A'].shape[1]
            elif 'xmin' in p and len(p['xmin']) > 0:
                nx = p['xmin'].shape[0]
            elif 'xmax' in p and len(p['xmax']) > 0:
                nx = p['xmax'].shape[0]
        p['H'] = sparse((nx, nx))
    else:
        nx = p['H'].shape[0]

    p['xmin'] = -Inf * ones(nx) if 'xmin' not in p else p['xmin']
    p['xmax'] =  Inf * ones(nx) if 'xmax' not in p else p['xmax']

    p['c'] = zeros(nx) if p['c'] is None else p['c']

    p['x0'] = zeros(nx) if 'x0' not in p else p['x0']

    def qp_f(x, return_hessian=False):
        f = 0.5 * dot(x * p['H'], x) + dot(p['c'], x)
        df = p['H'] * x + p['c']
        if not return_hessian:
            return f, df
        d2f = p['H']
        return f, df, d2f

    p['f_fcn'] = qp_f

    sol = pips(p)

    return sol["x"], sol["f"], sol["eflag"], sol["output"], sol["lmbda"]

if __name__ == "__main__":
    import doctest
    doctest.testmod()


import torch
from torch import nn, Tensor
from scipy.spatial.distance import cosine
from typing import Optional
def mean(x : Tensor, dim : int = 1, weights: Optional[ None, Tensor ] = None) -> Tensor:
    '''
        Apply the mean to the index that represents the number of words
    '''
    return x.mean(dim=dim) if weights is not None else x.mean(dim=dim) * weights

def centroid(x : Tensor, index: int = 1) -> Tensor:
    '''
        Calculate the mean vector and return the one closest to the center
    '''
    from utils.funcs import normedChebyshev
    mu = mean( x , index )
    best = torch.argmin([ normedChebyshev( mu, x_i ) for x_i in x[ :, index ] ])
    return x[:, [best], :]

def diff(x : Tensor) -> Tensor:
    '''
        Take the difference between the min and max of each dimension.
        This will produce only positive values.

        NOTE: This kind of seems dumb, but I'm leaving it for now.
    '''
    pass


<filename>gemelli/rpca.py
# ----------------------------------------------------------------------------
# Copyright (c) 2019--, gemelli development team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
# ----------------------------------------------------------------------------

import biom
import skbio
import numpy as np
import pandas as pd
from typing import Union
from gemelli.matrix_completion import MatrixCompletion
from gemelli.preprocessing import matrix_rclr
from gemelli._defaults import (DEFAULT_COMP,
                               DEFAULT_MSC, DEFAULT_MFC,
                               DEFAULT_OPTSPACE_ITERATIONS,
                               DEFAULT_MFF)
from scipy.linalg import svd

def rpca(table: biom.Table,
         n_components: Union[int, str] = DEFAULT_COMP,
         min_sample_count: int = DEFAULT_MSC,
         min_feature_count: int = DEFAULT_MFC,
         min_feature_frequency: float = DEFAULT_MFF,
         max_iterations: int = DEFAULT_OPTSPACE_ITERATIONS) -> (
        skbio.OrdinationResults,
        skbio.DistanceMatrix):
    """Runs RPCA with an matrix_rclr preprocessing step.

       This code will be run by both the standalone and QIIME 2 versions of
       gemelli.
    """
    # get shape of table
    n_features, n_samples = table.shape

    # filter sample to min seq. depth
    def sample_filter(val, id_, md):
        return sum(val) > min_sample_count

    # filter features to min total counts
    def observation_filter(val, id_, md):
        return sum(val) > min_feature_count

    # filter features by N samples presence
    def frequency_filter(val, id_, md):
        return (np.sum(val > 0) / n_samples) > (min_feature_frequency / 100)

    # filter and import table for each filter above
    table = table.filter(observation_filter, axis='observation')
    table = table.filter(frequency_filter, axis='observation')
    table = table.filter(sample_filter, axis='sample')
    # table to dataframe
    table = pd.DataFrame(table.matrix_data.toarray(),
                         table.ids('observation'),
                         table.ids('sample')).T
    # check the table after filtering
    if len(table.index) != len(set(table.index)):
        raise ValueError('Data-table contains duplicate indices')
    if len(table.columns) != len(set(table.columns)):
        raise ValueError('Data-table contains duplicate columns')
    # Robust-clt (matrix_rclr) preprocessing and OptSpace (RPCA)
    opt = MatrixCompletion(n_components=n_components,
                           max_iterations=max_iterations).fit(
                               matrix_rclr(table))
    # get new n-comp when applicable
    n_components = opt.s.shape[0]
    # get PC column labels for the skbio OrdinationResults
    rename_cols = ['PC' + str(i + 1) for i in range(n_components)]
    # get completed matrix for centering
    X = opt.sample_weights @ opt.s @ opt.feature_weights.T
    # center again around zero after completion
    X = X - X.mean(axis=0)
    X = X - X.mean(axis=1).reshape(-1, 1)
    # re-factor the data
    u, s, v = svd(X)
    # only take n-components
    u = u[:, :n_components]
    v = v.T[:, :n_components]
    # calc. the new variance using projection
    p = s**2 / np.sum(s**2)
    p = p[:n_components]
    s = s[:n_components]
    # save the loadings
    feature_loading = pd.DataFrame(v, index=table.columns,
                                   columns=rename_cols)
    sample_loading = pd.DataFrame(u, index=table.index,
                                  columns=rename_cols)
    # % var explained
    proportion_explained = pd.Series(p, index=rename_cols)
    # get eigenvalues
    eigvals = pd.Series(s, index=rename_cols)

    # if the n_components is two add PC3 of zeros
    # this is referenced as in issue in
    # <https://github.com/biocore/emperor/commit
    # /a93f029548c421cb0ba365b4294f7a5a6b0209ce>
    # discussed in gemelli -- PR#29
    if n_components == 2:
        feature_loading['PC3'] = [0] * len(feature_loading.index)
        sample_loading['PC3'] = [0] * len(sample_loading.index)
        eigvals.loc['PC3'] = 0
        proportion_explained.loc['PC3'] = 0

    # save ordination results
    short_method_name = 'rpca_biplot'
    long_method_name = '(Robust Aitchison) RPCA Biplot'
    ord_res = skbio.OrdinationResults(
        short_method_name,
        long_method_name,
        eigvals.copy(),
        samples=sample_loading.copy(),
        features=feature_loading.copy(),
        proportion_explained=proportion_explained.copy())
    # save distance matrix
    dist_res = skbio.stats.distance.DistanceMatrix(
        opt.distance, ids=sample_loading.index)

    return ord_res, dist_res

def auto_rpca(table: biom.Table,
              min_sample_count: int = DEFAULT_MSC,
              min_feature_count: int = DEFAULT_MFC,
              min_feature_frequency: float = DEFAULT_MFF,
              max_iterations: int = DEFAULT_OPTSPACE_ITERATIONS) -> (
        skbio.OrdinationResults,
        skbio.DistanceMatrix):
    """Runs RPCA but with auto estimation of the
       rank peramater.
    """
    ord_res, dist_res = rpca(table,
                             n_components='auto',
                             min_sample_count=min_sample_count,
                             min_feature_count=min_feature_count,
                             min_feature_frequency=min_feature_frequency,
                             max_iterations=max_iterations)
    return ord_res, dist_res


import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
# matplotlib.rcParams['ps.useafm'] = True
# matplotlib.rcParams['pdf.use14corefonts'] = True
# matplotlib.rcParams['text.usetex'] = True
matplotlib.use('Agg')
import matplotlib.pyplot as plot
import matplotlib.cm as cm # cm.rainbow
from random import expovariate
import sys, pprint, math, numpy, simpy, getopt, itertools

from simplex_sim import *
from simplex_models import *
from mds_models import mds_exactbound_on_ar
# from mds_exp import sim_mds_nk

from scipy.interpolate import UnivariateSpline

def plot_reptoall_steadystate_probhist():
  t, r, k = 1, 2, 2
  def get_state_prob_m(ar):
    log(WARNING, "ar= {}, t= {}, r= {}, k= {}".format(ar, t, r, k) )
    env = simpy.Environment()
    pg = PG(env, "pg", ar)
    avq = AVQ("avq", env, t, r, k, serv="Exp", servdist_m={'mu': 1} )
    # monitor = AVQMonitor(env, avq, poll_dist=lambda: 0.1)
    # avq.join_q.out_m = monitor
    pg.out = avq
    env.run(until=50000)

    # print("monitor.polled_state__counter_map= {}".format(pprint.pformat(monitor.polled_state__counter_map) ) )
    total_counter = sum([c for rs, c in monitor.polled_state__counter_map.items() ] )
    state_prob_m = {rs:float(c)/total_counter for rs, c in monitor.polled_state__counter_map.items() }
    # print("polled_state__counter_map= {}".format(pprint.pformat(polled_state__counter_map) ) )
    return state_prob_m # ['0,(0,0)']
  # for ar in numpy.arange(0.05, 1.2, 0.1):
  color = iter(cm.rainbow(numpy.linspace(0, 1, 20) ) )
  plot.figure(figsize=(20,10) )
  for ar in numpy.arange(0.05, 1.3, 0.1):
  # for ar in numpy.arange(0.05, 0.1, 0.1):
    state_prob_m = get_state_prob_m(ar)

    def state(kp, i, j):
      return "{},({},{})".format(kp, i, j)
    i__tau_l_map = {}
    for i in range(10):
      if i not in i__tau_l_map:
        i__tau_l_map[i] = []
      for kp in range(i, 10):
        s_u, s_l = state(kp, i, 0), state(kp+1, i, 0)
        if s_u in state_prob_m and s_l in state_prob_m:
          i__tau_l_map[i].append(state_prob_m[s_l]/state_prob_m[s_u] )
        # if state(k+1, 0, i) in state_prob_m:
        #   i__tau_l_map[i].append(state_prob_m[state(k+1, 0, i) ] /state_prob_m[state(k, 0, i) ] )
    log(WARNING, "i__tau_l_map=\n {}".format(pprint.pformat(i__tau_l_map) ) )
    #
    wing_cutoff_i = 2
    wing_cutoff_sum = 0
    for s, p in state_prob_m.items():
      split_l = s.split(",")
      if int(split_l[1].split("(")[1] ) > wing_cutoff_i or int(split_l[2].split(")")[0] ) > wing_cutoff_i:
        wing_cutoff_sum += p

    s_l, p_l = [], []
    for s, p in state_prob_m.items():
      if p > 0.01:
        s_l.append(s)
        p_l.append(p)
    plot.bar(range(len(p_l) ), p_l, color=next(color) )
    plot.xticks([i+0.5 for i in range(len(s_l) ) ], s_l, size='small')
    plot.xlabel("State")
    plot.ylabel("Steady-state probability")
    plot.title(r't= {}, $\lambda$= {}, sum_on_plot= {}, wing_cutoff_sum= {}'. \
      format(t, "{0:.2f}".format(ar), "{0:.2f}".format(sum(p_l)), "{0:.2f}".format(wing_cutoff_sum) ) )
    plot.savefig("plot_reptoall_steadystate_probhist_ar_{0:.2f}.png".format(ar) )
    plot.clf()

def test_avq(nf, ar, t, r, k, serv="Exp", servdist_m=None,
             w_sys=True, mixed_traff=False, sching="rep-to-all", p_i_l= [] ):
  E_T_f_sum = 0
  for f in range(nf):
    log(WARNING, "ar= {}, t= {}, r= {}, k= {}, servdist_m= {}, w_sys= {}, mixed_traff= {}, sching= {}". \
        format(ar, t, r, k, servdist_m, w_sys, mixed_traff, sching) )

    env = simpy.Environment()
    if mixed_traff:
      sym_l, sym__rgroup_l_m = simplex_sym_l__sym__rgroup_l_m(t)
      log(WARNING, "sym__rgroup_l_m=\n {}".format(pprint.pformat(sym__rgroup_l_m) ) )
      pg = MT_PG(env, "pg", ar, sym_l)
      avq = MT_AVQ("mt_avq", env, t, sym__rgroup_l_m, serv, servdist_m)
      # monitor = AVQMonitor(env, aq=avq, poll_dist=lambda: 0.1)
      # avq.join_q.out_m = monitor
    else:
      psize = None
      if serv == "Bern*Pareto":
        psize = "Pareto"
        serv = "Bern"
      pg = PG(env, "pg", ar, psize=psize, psize_dist_m=servdist_m)
      avq = AVQ("avq", env, t, r, k, servdist_m, sching, w_sys=w_sys)
      # monitor = AVQMonitor(env, aq=avq, poll_dist=lambda: 0.1)
      # avq.join_q.out_m = monitor
    pg.out = avq
    pg.init()
    c = 3 if serv == "Pareto" or serv == "Bern" else 1
    env.run(until=c*50000) # 20

    if mixed_traff:
      print("pg.sym__n_sent= {}".format(pprint.pformat(pg.sym__n_sent) ) )
    st_l = avq.jsink.st_l
    if len(st_l) > 0:
      E_T_f_sum += float(sum(st_l) )/len(st_l)
      # continue
    # print("avq.jsink.qid__num_win_map= {}".format(pprint.pformat(avq.jsink.qid__num_win_map) ) )
    total_n_wins = sum([n for i, n in avq.jsink.qid__num_win_map.items() ] )
    print("pg.n_sent= {}, total_n_wins= {}".format(pg.n_sent, total_n_wins) )
    qid_winfreq_map = {i:float(n)/total_n_wins for i, n in avq.jsink.qid__num_win_map.items() }
    print("qid_winfreq_map= {}".format(pprint.pformat(qid_winfreq_map) ) )
    # if not mixed_traff:
    #   total_n_types = sum(avq.servtype__num_m)
    #   p_i_l[:] = [n/total_n_types for t, n in enumerate(avq.servtype__num_m) ]
    #   print("p_i_l= {}".format(p_i_l) )
    """
    print("\n")
    # print("avq.join_q.state__num_found_map= {}".format(pprint.pformat(avq.join_q.state__num_found_map) ) )
    # total_num_founds = sum([n for s, n in avq.join_q.state__num_found_map.items() ] )
    # state__found_freq_map = {s:float(n)/total_num_founds for s, n in avq.join_q.state__num_found_map.items() }
    # print("state__found_freq_map= {}".format(pprint.pformat(state__found_freq_map) ) )

    print("\n")
    # print("monitor.polled_state__counter_map= {}".format(pprint.pformat(monitor.polled_state__counter_map) ) )
    total_counter = sum([c for rs, c in monitor.polled_state__counter_map.items() ] )
    polled_state__counter_map = {rs:float(c)/total_counter for rs, c in monitor.polled_state__counter_map.items() }
    print("polled_state__counter_map= {}".format(pprint.pformat(polled_state__counter_map) ) )

    print("\n")
    # print("monitor.state__num_found_by_job_departed_map= {}".format(pprint.pformat(monitor.state__num_found_by_job_departed_map) ) )
    total_counter = sum([c for rs, c in monitor.state__num_found_by_job_departed_map.items() ] )
    state__freq_found_by_job_departed_map = {rs:float(c)/total_counter for rs, c in monitor.state__num_found_by_job_departed_map.items() }
    print("state__freq_found_by_job_departed_map= {}".format(pprint.pformat(state__freq_found_by_job_departed_map) ) )

    print("\n")
    # print("monitor.start_setup__num_found_by_job_departed_map= {}".format(pprint.pformat(monitor.start_setup__num_found_by_job_departed_map) ) )
    total_counter = sum([c for rs, c in monitor.start_setup__num_found_by_job_departed_map.items() ] )
    start_setup__freq_found_by_job_departed_map = {rs:float(c)/total_counter for rs, c in monitor.start_setup__num_found_by_job_departed_map.items() }
    print("start_setup__freq_found_by_job_departed_map= {}".format(pprint.pformat(start_setup__freq_found_by_job_departed_map) ) )
    """
  E_T = E_T_f_sum/nf
  print(">> E_T= {}".format(E_T) )
  if E_T > 100: return None
  return E_T

def plot_winning_freqs():
  t, r, k = 1, 2, 2
  mu = 1
  servdist_m = {'dist': 'Exp', 'mu': mu}
  ar_ub = reptoall_innerbound_on_ar(t, servdist_m)
  log(WARNING, "t= {}, servdist_m= {}, ar_ub={}".format(t, servdist_m, ar_ub) )
  ar_l = []
  qid__winfreq_l_map = {}
  for ar in numpy.linspace(0.05, ar_ub*1.1, 20):
    env = simpy.Environment()
    pg = PG(env, "pg", ar)
    avq = AVQ("avq", env, t, r, k, servdist_m, "rep-to-all")
    pg.out = avq
    pg.init()
    # monitor = AVQMonitor(env, aq=avq, poll_dist=lambda: 1)
    env.run(until=50000)

    total_n_wins = sum([n for i, n in avq.jsink.qid__num_win_map.items() ] )
    qid_winfreq_map = {i:float(n)/total_n_wins for i, n in avq.jsink.qid__num_win_map.items() }
    print("ar= {}, qid_winfreq_map= {}".format(ar, pprint.pformat(qid_winfreq_map) ) )

    ar_l.append(ar)
    for qid, win_freq in qid_winfreq_map.items():
      if qid not in qid__winfreq_l_map:
        qid__winfreq_l_map[qid] = []
      qid__winfreq_l_map[qid].append(win_freq)

  plot.axhline(y=0.6, label=r'Lower-bound, $w_s$', c=next(dark_color), lw=2, ls='--')
  plot.axhline(y=0.4, label=r'Upper-bound, $w_r$', c=next(dark_color), lw=2, ls='--')
  counter = 0
  for qid, win_freq_l in qid__winfreq_l_map.items():
    if counter == 0:
      plot.plot(ar_l, win_freq_l, label=r'Simulation, $w_s$', color=next(dark_color), marker=next(marker), ms=8, mew=2, ls=':')
    else:
      plot.plot(ar_l, win_freq_l, label=r'Simulation, $w_r$', color=next(dark_color), marker=next(marker), ms=8, mew=2, ls=':')
    counter += 1

  fontsize = 16
  plot.legend(fontsize=13)
  plot.xlabel(r'Arrival rate $\lambda$', fontsize=fontsize)
  plot.ylabel("Fraction of request completions", fontsize=fontsize)
  plot.title(r'Replicate-to-all $t=1$, $\gamma=\alpha=\beta= {}$'.format(mu), fontsize=fontsize)
  fig = plot.gcf()
  # def_size = fig.get_size_inches()
  # fig.set_size_inches(def_size[0]/1.4, def_size[1]/1.4)
  fig.set_size_inches(6, 4)
  fig.tight_layout()
  # plot.savefig("plot_winning_freqs.png", bbox_inches='tight')
  plot.savefig("plot_winning_freqs.pdf", dpi=fig.dpi)
  plot.gcf().clear()
  log(WARNING, "done.")

def plot_simplex_vs_rep():
  t, r, k = 3, 2, 2
  serv = "Exp"
  mu = 1
  servdist_m['mu'] = mu
  if t == 1: ar_ub = 1.6
  elif t == 3: ar_ub = 2.4
  elif t == 7:
    ar_ub = float(1.1*reptoall_innerbound_on_ar(mu, t, r, w_sys=True) )
  mixed_traff = False
  if mixed_traff: ar_ub = 1.1*ar_ub
  log(WARNING, "t= {}, ar_ub= {}, serv= {}, servdist_m= {}, mixed_traff= {}".format(t, ar_ub, serv, servdist_m, mixed_traff) )

  n = 2*t + 1
  n_sym = int(numpy.log2(n+1) )
  # # Same distance
  # n_rep = t + 1
  # n_total_rep = n_sym*n_rep
  # mu_rep = n*mu/n_total_rep

  # n_mds = n_sym + t
  # k_mds = n_sym
  # mu_mds = (2*t+1)*mu/n_mds
  # ar_ub_mds = None
  # if t == 3 and not mixed_traff: ar_ub_mds = ar_ub + 0.15 # mds_exactbound_on_ar(mu_mds, n_mds, k_mds)

  # Preserving hot-cold data mix
  # n_rep = t + 1
  # n_total_rep = n_rep
  # ar_ub_mds = None

  # Same repair bandwidth
  n_rep = t + 1
  n_total_rep = int(n_sym*(t+1)/2)
  mu_rep = n*mu/n_total_rep if not mixed_traff else n*mu/n_total_rep/n_sym
  ar_ub_mds = None

  ar_ub_rep = n_rep*mu_rep

  sim_simplex_reqed = False
  ET_sim_l = []
  if not mixed_traff and t == 1:
    ET_sim_l= [
      0.6775872854372559,
      0.7909557937247363,
      0.9486987202221493,
      1.166209238915134,
      1.5685720588787688,
      2.478342315521276,
      2.6376081306859107,
      2.906788473547391,
      3.263700392764921,
      3.5974807041868426,
      4.289127887822366,
      4.794525358984301,
      5.896928018871929,
      8.099664758903687,
      12.74155958739236]
  elif mixed_traff and t == 1:
    ET_sim_mixedtraff_l= [
      0.6795142458623882,
      0.7748927520953908,
      0.9120551663968248,
      1.1017354073281063,
      1.4008309793905753,
      2.0319166972531395,
      2.3461415096416802,
      2.617752845887241,
      2.931842457820586,
      3.3957906721917803,
      4.275140545352988,
      5.384652265631004,
      8.289396804081276,
      None, # 21.85423973012918,
      None]
  elif not mixed_traff and t == 3:
    ET_sim_l= [
      0.4676519075931255,
      0.5247256264186801,
      0.6230081386991332,
      0.775814486873029,
      1.0207917160021767,
      1.6244613243247372,
      1.7481208563178903,
      1.9667165686859327,
      2.163968348080258,
      2.5923594863306776,
      3.0700378671376627,
      3.796384731111067,
      4.841880170965622,
      6.610367379250164,
      13.559429107437742]
  elif mixed_traff and t == 3:
    ET_sim_mixedtraff_l= [
      0.46628732795742817,
      0.5184094604634668,
      0.5975473670434864,
      0.7272615729604553,
      0.9228862984361961,
      1.3432430706439402,
      1.5297012938889547,
      1.7382202900329649,
      2.006828591863818,
      2.409746021676913,
      2.9987862815607667,
      4.1494167022302415,
      6.7589082110731376,
      None,
      None]
  elif not mixed_traff and t == 7:
    ET_sim_l= [
      0.31868938934489865,
      0.3650196292881234,
      0.4281058344507201,
      0.5206469367259021,
      0.6957249200007437,
      1.1325417176453465,
      1.2307386079673424,
      1.3867025010207843,
      1.5768489395874896,
      1.865829597118924,
      2.1844400783734677,
      2.89287730113055,
      4.276904798075734,
      6.184072327220002,
      None]
  else:
    sim_simplex_reqed = True

  sim_mds_reqed = False
  E_T_sim_mds_l = []
  if t == 3:
    E_T_sim_mds_l= [
      0.4291382378049635,
      0.4859752967032978,
      0.5573834220518918,
      0.6504572423217563,
      0.7912534680581111,
      1.0617796194912665,
      1.1173955998468372,
      1.1864819039768486,
      1.3132561853089193,
      1.4183354786680833,
      1.5441924947724337,
      1.6800188501504796,
      1.97388257061194,
      2.365205967704707,
      2.552714259149294]
  else:
    sim_mds_reqed = True

  sim_mds_split_to_one_reqed = False
  E_T_sim_split_to_one_mds_l = []
  if t == 3:
    E_T_sim_split_to_one_mds_l= [
      0.77365082603341717,
      0.82440222647912942,
      0.88499585518811741,
      0.95059809100622572,
      1.026735997953014,
      1.1276811830357545,
      1.2540326440649683,
      1.4212608769595043,
      1.6517287453133336,
      1.9954850953566452,
      2.5853499093220909,
      3.8254183518878659,
      8.5337611351281506,
      None,
      None]
  else:
    sim_mds_split_to_one_reqed = True

  mew, ms = 3, 8
  nf = 2
  def plot_reptoall():
    # Simplex
    ar_simplex_l = []
    for ar in [*numpy.linspace(0.05, 0.8*ar_ub, 5, endpoint=False), *numpy.linspace(0.8*ar_ub, ar_ub, 10) ]:
      ar_simplex_l.append(ar)
      if sim_simplex_reqed:
        ET_sim_l.append(test_avq(nf, ar, t, r, k, serv, servdist_m, w_sys=True, mixed_traff=mixed_traff) )
    c = next(dark_color)
    label = 'Simplex' # if t != 1 else 'Simplex or MDS'
    print("ET_sim_l= {}".format(pprint.pformat(ET_sim_l) ) )
    plot.plot(ar_simplex_l, ET_sim_l, label=label, color=c, marker=next(marker), mew=mew, ms=ms, linestyle=':')
    # stab_lim = ET_simplex_approx(t, ar, servdist_m, incremental=True, ar_ub=True)
    # plot.axvline(stab_lim, label="Simplex stability", color=c, linestyle='--')
    # Rep
    ar_rep_l, E_T_rep_n_1_l = [], []
    for ar in numpy.linspace(0.05, ar_ub_rep-0.05, 20):
      ar_rep_l.append(ar)
      E_T_rep_n_1_l.append(E_T_rep_n_1(ar, mu_rep, n_rep) )
    # E_T_rep_n_1_l = [e*n_rep for e in E_T_rep_n_1_l]
    c = next(dark_color)
    plot.plot(ar_rep_l, E_T_rep_n_1_l, label=r'Replication', color=c, marker=next(marker), mew=mew, ms=ms, linestyle=':')
    # plot.axvline(ar_ub_rep, label="Rep stability", color=c, linestyle='--')
    # # MDS
    # if ar_ub_mds is not None:
    #   ar_mds_l = []
    #   for ar in [*numpy.linspace(0.05, 0.7*ar_ub_mds, 5, endpoint=False), *numpy.linspace(0.7*ar_ub_mds, ar_ub, 10, endpoint=False) ]:
    #   # for ar in numpy.linspace(ar_ub_mds, ar_ub_mds, 1):
    #     ar_mds_l.append(ar)
    #     if sim_mds_reqed:
    #       E_T_sim_mds_l.append(test_avq(nf, ar, t=1, r, k, serv, {'mu': mu_mds}, w_sys=True) )
    #   print("E_T_sim_mds_l= {}".format(pprint.pformat(E_T_sim_mds_l) ) )
    #   plot.plot(ar_mds_l, E_T_sim_mds_l, label=r'MDS', color=next(dark_color), marker=next(marker), mew=mew, ms=ms, linestyle=':')
  def plot_selectone():
    # Simplex
    ar_ub = arub_simplex_selectone(t, mu) + 0.1
    log(WARNING, "ar_ub= {}".format(ar_ub) )
    ar_l, ET_l = [], []
    for ar in numpy.linspace(0.05, ar_ub, 20):
      ar_l.append(ar)
      ET_l.append(ET_selectone(t, ar, mu) )
    label = 'Simplex' # if t != 1 else 'Simplex or MDS'
    plot.plot(ar_l, ET_l, label=label, color=next(dark_color), marker=next(marker), mew=mew, ms=ms, linestyle=':')
    # Rep
    ar_ub_rep = n_rep*mu_rep
    ar_l, E_T_rep_l = [], []
    for ar in numpy.linspace(0.05, ar_ub_rep-0.2, 20):
      ar_l.append(ar)
      E_T_rep_l.append(E_T_rep_n_1_split_to_one(ar, mu_rep, n_rep) )
    plot.plot(ar_l, E_T_rep_l, label=r'Replication', color=next(dark_color), marker=next(marker), mew=mew, ms=ms, linestyle=':')
  plot_reptoall()
  scheduling = "Replicate-to-all"
  # plot_selectone()
  # scheduling = "Split-to-one"
  plot.legend(prop={'size':12})
  plot.xlabel(r'Arrival rate $\lambda$ (Request/s)', fontsize=12)
  plot.ylabel(r'Average download time (s)', fontsize=12)
  # plot.title(r'$t={}, \mu={}$'.format(t, mu) )
  plot.title(r'{} scheduling, $t= {}$'.format(scheduling, t) )
  fig = plot.gcf()
  def_size = fig.get_size_inches()
  fig.set_size_inches(def_size[0]/1.4, def_size[1]/1.4)
  fig.tight_layout()
  plot.savefig("plot_simplex_vs_rep_t_{}_{}.pdf".format(t, scheduling) )
  fig.clear()
  # Energy
  # ar_simplex_l, Energy_simplex_l = [], []
  # for ar in numpy.linspace(0.1, ar_ub, 20):
  #   ar_simplex_l.append(ar)
  #   Energy_simplex_l.append(n/ar)
  # ar_rep_l, Energy_rep_l = [], []
  # for ar in numpy.linspace(0.1, ar_ub_rep, 20):
  #   ar_rep_l.append(ar)
  #   Energy_rep_l.append(n_total_rep/ar)
  # plot.plot(ar_simplex_l, Energy_simplex_l, label='Simplex', color=next(dark_color), marker=next(marker), mew=mew, ms=ms, linestyle=':')
  # plot.plot(ar_rep_l, Energy_rep_l, label='Rep', color=next(dark_color), marker=next(marker), mew=mew, ms=ms, linestyle=':')
  # plot.legend()
  # plot.xlabel(r'Arrival rate $\lambda$', fontsize=12)
  # plot.ylabel(r'Unit of energy per request', fontsize=12)
  # plot.title(r'$t={}, \mu={}$'.format(t, mu) )
  # fig = plot.gcf()
  # def_size = fig.get_size_inches()
  # fig.set_size_inches(def_size[0]/1., def_size[1]/1.)
  # fig.tight_layout()
  # plot.savefig("plot_simplex_vs_rep_t_{}_energy.pdf".format(t) )
  # fig.clear()
  log(WARNING, "done; scheduling= {}, t= {}".format(scheduling, t) )

def plot_reptoall():
  mixed_traff, w_sys = False, True
  t, r, k = 1, 2, 2
  serv = "Exp" # "Bern" # "Bern*Pareto" # "Pareto" # "Dolly"
  mu = 1
  # loc, a = 1, 2
  # U, L, p, loc, a = 1, 8, 0.2, 0.1, 1.5 # 1, 8, 0.2, 1, 3
  U, L, p, loc, a = 1, 10, 0.3, 0.1, 1.5 # 1, 8, 0.2, 1, 3
  # For rep-to-all
  if serv == "Exp":
    servdist_m = {'dist': serv, 'mu': mu}
    if t == 1: ar_ub = 1.6
    elif t == 3: ar_ub = 2.4
    elif t == 7: ar_ub = float(1.1*reptoall_innerbound_on_ar(t, servdist_m) )
    else: ar_ub = reptoall_innerbound_on_ar(t, servdist_m)
  elif serv == "Pareto":
    servdist_m = {'dist': serv, 'loc': loc, 'a': a}
    ar_ub = reptoall_innerbound_on_ar(t, servdist_m)
  elif serv == "TPareto":
    servdist_m = {'dist': serv, 'l': l, 'u': u, 'a': a}
    ar_ub = reptoall_innerbound_on_ar(t, servdist_m)
  elif serv == "Bern" or serv == "Bern*Pareto":
    servdist_m = {'dist': serv, 'U': U, 'L': L, 'p': p, 'loc': loc, 'a': a}
    ar_ub = reptoall_innerbound_on_ar(t, servdist_m)
  elif serv == "Dolly":
    servdist_m = None
    if t == 1: ar_ub = 0.28
    elif t == 3: ar_ub = 0.4
  log(WARNING, "w_sys= {}, t= {}, r= {}, k= {}, servdist_m= {}, ar_ub= {}, mixed_traff= {}".format(w_sys, t, r, k, servdist_m, ar_ub, mixed_traff) )

  ET_sm_l, ET_sim_l, ET_l, ET_lb_l = [], [], [], []
  ET_alt_l, ET_matrixanalytic_l = [], []
  ET_bestapprox_l, ET_betterapprox_l, ET_naiveapprox_l, ET_varkigauri_lb_l = [], [], [], []
  ET_simbasedapprox_l = []
  ET_sim_mixedtraff_l = []

  # All below w_sys=True
  nf = 3
  sim_simplex = False
  if serv == "Exp":
    if t == 1:
      ET_sim_l= [
        0.6775872854372559,
        0.7909557937247363,
        0.9486987202221493,
        1.166209238915134,
        1.5685720588787688,
        2.478342315521276,
        2.6376081306859107,
        2.906788473547391,
        3.263700392764921,
        3.5974807041868426,
        4.289127887822366,
        4.794525358984301,
        5.896928018871929,
        8.099664758903687,
        12.74155958739236]
    elif t == 3:
      ET_sim_l= [
        0.4676519075931255,
        0.5247256264186801,
        0.6230081386991332,
        0.775814486873029,
        1.0207917160021767,
        1.6244613243247372,
        1.7481208563178903,
        1.9667165686859327,
        2.163968348080258,
        2.5923594863306776,
        3.0700378671376627,
        3.796384731111067,
        4.841880170965622,
        6.610367379250164,
        13.559429107437742]
    else: sim_simplex = True
  elif serv == "Pareto":
    if loc == 1 and a == 2:
      if t == 1:
        ET_sim_l= [
          1.5299993522735693,
          1.7233577876041122,
          1.8952577131712123,
          2.2418712080584897,
          2.853623528849504,
          4.2208097489868,
          4.586420599121132,
          5.191481636572133,
          5.6340499086639815,
          5.9712033727746,
          7.94309766204549,
          9.599736059102067,
          13.280357368839619,
          17.20104661693977,
          25.449711725024084]
      elif t == 3:
        ET_sim_l= [
          1.3221090353539466,
          1.4459274633541828,
          1.6229349092564267,
          1.9043964678064051,
          2.4154300633936936,
          3.6666730405584844,
          3.9217550909479577,
          4.256167164955279,
          4.717366068731679,
          5.891743883842969,
          6.04468767433355,
          8.073514650754076,
          9.880581947509592,
          15.816118977624845,
          28.433468299774272]
      else: sim_simplex = True
    elif loc == 1 and a == 5:
      if t == 3:
        ET_sim_l= [
          1.1276007604818075,
          1.240550592912947,
          1.3862061325608057,
          1.645653757532261,
          2.0688083303883276,
          3.2115831386711813,
          3.2986018954384835,
          3.8148027478966227,
          4.033705086448495,
          5.448028336643181,
          5.697392211154507,
          9.053323168666376,
          10.17868048265699,
          23.644561610837382,
          None] # 93.02644300031747
      else: sim_simplex = True
    else: sim_simplex = True
  elif serv == "Bern":
    if U == 1 and L == 8 and p == 0.2:
      if t == 1:
        # nf = 3
        ET_sim_l= [
          1.6376474738985423,
          1.9851446427827089,
          2.4840795375267626,
          3.1829054073054217,
          4.39332366216294,
          7.063110373762194,
          7.4445330550351665,
          8.208129233744382,
          9.309321611480481,
          10.747520637423975,
          12.460023568734707,
          15.038255521201348,
          18.778687793661728,
          23.582209372296532,
          36.21619587757658]
      elif t == 3:
        # nf = 1
        ET_sim_l= [
          1.1072895175117927,
          1.2582695204803385,
          1.4572200912301614,
          1.8340775367273732,
          2.4430722742069184,
          4.053853819806121,
          4.4494192069988605,
          5.061922101782603,
          5.883304533639656,
          6.705043861319703,
          8.307668993372534,
          11.041651319984396,
          17.564101468045756,
          33.184482866801716,
          None]
      else: sim_simplex = True
    else: sim_simplex = True
  elif serv == "Bern*Pareto":
    if U == 1 and L == 8 and p == 0.2 and loc == 1 and a == 3:
      if t == 11:
        # nf = 3
        ET_sim_l= [
          2.142631836594827,
          2.5302711620514966,
          2.941315337537391,
          3.8773353598252345,
          4.550420407107853,
          6.649089020276313,
          7.000687768519389,
          7.681497353358071,
          8.058275694322152,
          9.541434770613856,
          10.136837383356713,
          11.027889242435874,
          14.072462480848941,
          18.721889173565945,
          29.85022801496356]
      elif t == 33:
        pass
      else: sim_simplex = True
    else: sim_simplex = True
  else: sim_simplex = True

  # Mixed traff
  sim_simplex_mixed_traff = False
  if mixed_traff:
    if serv == "Exp":
      if t == 1:
        ET_sim_mixedtraff_l= [
          0.678978501641253,
          0.7748022818617738,
          0.9072886738372506,
          1.0928902616368403,
          1.43754904360929,
          2.0810587767368154,
          2.266461910378062,
          2.5977047234601125,
          3.2441553951140985,
          3.585616438620215,
          4.415600179701042,
          6.099149242270735,
          9.786138444920114,
          None, # 21.631079441147904
          None]
      elif t == 3:
        ET_sim_mixedtraff_l= [
          0.46217641274184773,
          0.5249541076176077,
          0.6065798815902482,
          0.7193352388312126,
          0.9238674360581351,
          1.363955390788439,
          1.4654931553890183,
          1.733811055160431,
          2.0493965738680795,
          2.479767271681704,
          3.065826086322138,
          4.300842192226751,
          8.05986376865404,
          None, # 35.70730644518723,
          None]
      else:
        sim_simplex_mixed_traff = True

  ar_l = []
  for ar in [*numpy.linspace(0.05, 0.8*ar_ub, 5, endpoint=False), *numpy.linspace(0.8*ar_ub, ar_ub, 10) ]:
  # for ar in numpy.linspace(0.05, ar_ub, 2):
    ar_l.append(ar)

    p_i_l = []
    if sim_simplex:
      ET_sim = test_avq(nf, ar, t, r, k, serv, servdist_m, w_sys=w_sys, p_i_l=p_i_l)
      print("*** ET_sim= {}".format(ET_sim) )
      ET_sim_l.append(ET_sim)
      # ET_sim_l.append(None)

    # ET_simbasedapprox_l.append(ET_simplex_approx(t, ar, servdist_m, p_i_l=p_i_l)[0] )
    # if sim_simplex_mixed_traff:
    #   ET_sim_mixedtraff_l.append(test_avq(nf, ar, t, r, k, serv, servdist_m, w_sys=w_sys, p_i_l=p_i_l, mixed_traff=True) )

    ET_sm_l.append(ET_simplex_sm(t, ar, servdist_m) )
    ET_lb_l.append(ET_simplex_lb(t, ar, servdist_m) )
    if serv == "Exp":
      if t == 1:
        ET_l.append(ET_reptoall_t1(ar, mu) )
        ET_matrixanalytic_l.append(ET_reptoall_t1_matrixanalytic(t, ar, mu) )
      elif t == 2:
        if w_sys:
          ET_alt_l.append(simplex_w_two_repair__E_T(ar, mu, M=2) )
          ET_l.append(simplex_w_two_repair__E_T(ar, mu, M=5) )
        else:
          ET_l.append(simplex_wo_sys_w_two_repair__E_T(ar, mu) )
    ET_naiveapprox_l.append(ET_simplex_approx(t, ar, servdist_m, naive=True)[0] )
    ET_betterapprox_l.append(ET_simplex_approx(t, ar, servdist_m)[0] )
    ET_bestapprox_l.append(ET_simplex_approx(t, ar, servdist_m, incremental=True)[0] )
    # ET_varkigauri_lb_l.append(E_T_simplex_varki_gauri_lb(t, ar, gamma, mu)[0] )

  ar_mixed_traff_l = []
  # for ar in numpy.linspace(0.2, 0.2, 1):
  for ar in [*numpy.linspace(0.05, 0.8*ar_ub, 5, endpoint=False), *numpy.linspace(0.8*ar_ub, 1.1*ar_ub, 10) ]:
    ar_mixed_traff_l.append(ar)
    if sim_simplex_mixed_traff:
      ET_sim_mixedtraff_l.append(test_avq(nf, ar, t, r, k, serv, servdist_m, w_sys=w_sys, mixed_traff=True) )

  # mew, ms = 0.1, 10
  mew, ms = 2, 5
  def plot_poster():
    # for better looking plot
    ar_approx_l = list(ar_l)

    ar = ar_ub + 0.03
    ar_approx_l.append(ar)
    ET_bestapprox_l.append(ET_simplex_approx(t, ar, servdist_m, incremental=True) )

    plot.plot(ar_l, ET_sim_l, label="FJ-FA, simulation", marker=next(marker), zorder=1, color=next(dark_color), linestyle=':', mew=mew, ms=ms)
    plot.plot(ar_approx_l, ET_bestapprox_l, label="FJ-FA, M/G/1 approximation", zorder=2, marker=next(marker), color='black', linestyle=':', mew=mew, ms=ms)

  def get_xs_l_ys_l(_x_l, _y_l):
    x_l, y_l = [], []
    for i, y in enumerate(_y_l):
      if y is not None:
        x_l.append(_x_l[i])
        y_l.append(y)

    s = UnivariateSpline(x_l, y_l, s=0.001)
    xs_l = np.linspace(min(x_l), max(x_l), 20)
    ys_l = s(xs_l)
    return xs_l, ys_l

  def plot_():
    log(WARNING, "ET_sim_l= {}".format(pprint.pformat(ET_sim_l) ) )
    # plot.plot(ar_l, ET_simbasedapprox_l, label=r'Sim-based approximation', marker=next(marker), zorder=1, color=next(dark_color), linestyle=':', mew=mew, ms=ms)
    label = 'Simulation, fixed-arrivals' if mixed_traff else 'Simulation'

    xs_l, ys_l = get_xs_l_ys_l(ar_l, ET_sim_l)
    # plot.plot(ar_l, ET_sim_l, label=label, marker=next(marker), zorder=1, color=next(dark_color), linestyle=':', mew=mew, ms=ms)
    plot.plot(xs_l, ys_l, label=label, marker=next(marker), zorder=1, color=next(dark_color), linestyle=':', mew=mew, ms=ms)

    if mixed_traff:
      log(WARNING, "ET_sim_mixedtraff_l= {}".format(pprint.pformat(ET_sim_mixedtraff_l) ) )
      plot.plot(ar_mixed_traff_l, ET_sim_mixedtraff_l, label=r'Simulation, mixed-arrivals', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
    else:
      xs_l, ys_l = get_xs_l_ys_l(ar_l, ET_sm_l)
      # plot.plot(ar_l, ET_sm_l, label=r'Split-Merge upper bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      plot.plot(xs_l, ys_l, label=r'Split-Merge upper bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)

      # plot.plot(ar_l, ET_bestapprox_l, label=r'$M/G/1$ approximation', zorder=2, marker=next(marker), color='black', linestyle=':', mew=mew, ms=ms)
      xs_l, ys_l = get_xs_l_ys_l(ar_l, ET_lb_l)
      # plot.plot(ar_l, ET_lb_l, label=r'Fast-Split-Merge lower bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      plot.plot(xs_l, ys_l, label=r'Fast-Split-Merge lower bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      if t == 1:
        xs_l, ys_l = get_xs_l_ys_l(ar_l, ET_matrixanalytic_l)
        # plot.plot(ar_l, ET_matrixanalytic_l, label=r'Matrix-analytic upper-bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
        plot.plot(xs_l, ys_l, label=r'Matrix-analytic upper-bound', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)

        xs_l, ys_l = get_xs_l_ys_l(ar_l, ET_l)
        # plot.plot(ar_l, ET_l, label=r'High-traffic approximation', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
        plot.plot(xs_l, ys_l, label=r'High-traffic approximation', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      # plot.plot(ar_l, ET_naiveapprox_l, label=r'Straightforward approximation', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      # plot.plot(ar_l, ET_betterapprox_l, label=r'Better approximation', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew, ms=ms)
      # plot.plot(ar_l, ET_bestapprox_l, label=r'Fine-grained approximation', zorder=2, marker=next(marker), color='black', linestyle=':', mew=mew, ms=ms)
      # plot.plot(ar_l, ET_varkigauri_lb_l, label=r'$E[\hat{T}_{fast-serial}]$', color=next(dark_color), marker=next(marker), linestyle=':', mew=mew)
    # stab_lim = ET_simplex_approx(t, ar, servdist_m, incremental=True, ar_ub=True)
    # plot.axvline(stab_lim, label="Stability limit", color='black', linestyle='--')
    # plot.gca().set_xlim([0, stab_lim+0.1] )

  def plot_selectone():
    ar_ub = 0.9*arub_simplex_selectone(t, serv, servdist_m)
    log(WARNING, "ar_ub={}".format(ar_ub) )
    ar_l, ET_l = [], []
    for ar in numpy.linspace(0.05, ar_ub, 50):
    # for ar in numpy.linspace(0.05, ar_ub, 2):
      ar_l.append(ar)
      # if sim:
      #   ET_l.append(test_avq(nf, ar, t, r, k, serv, servdist_m, w_sys=w_sys, sching="select-one") )
      ET_l.append(ET_selectone(t, ar, mu) )
    # log(WARNING, "ET_l= {}".format(pprint.pformat(ET_l) ) )
    plot.plot(ar_l, ET_l, 'b', label=r'Select-one', linestyle='--', lw=3, mew=mew, ms=ms)
  # plot_poster()
  plot_()

  # plot.plot(ar_l, ET_sim_l, 'k', label=r'Replicate-to-all', linestyle='-', lw=3)
  # plot_selectone()
  fontsize = 16
  plot.yscale('log')
  plot.legend(loc='upper left', fontsize=13, framealpha=0.25)
  plot.xlabel(r'Arrival rate $\lambda$', fontsize=fontsize)
  plot.ylabel(r'Average download time', fontsize=fontsize)
  serv_in_latex = None
  if serv == "Exp":
    serv_in_latex = '\mathrm{Exp}' + r'(\mu={})'.format(mu)
  elif serv == "Pareto":
    serv_in_latex = r'Pareto(s={}, \alpha={})'.format(loc, a)
  elif serv == "Bern":
    serv_in_latex = r'Bernoulli(U={}, L={}, p={})'.format(U, L, p)
  elif serv == "Dolly":
    serv_in_latex = r'Dolly'
  plot.title(r'FJ-FA with $r= {}$, $t= {}$, $\mu= {}$'.format(r, t, mu), fontsize=fontsize)
  # plot.title(r'$t={}$, Servers $\sim {}$'.format(t, serv_in_latex) )
  fig = plot.gcf()
  fig.set_size_inches(6, 4)
  fig.tight_layout()
  plot.savefig("plot_FJFA_r{}_t{}.pdf".format(r, t) )
  log(WARNING, "done; t= {}, r= {}, k= {}".format(t, r, k) )

def get_opts(argv):
  opt_map = {}
  try:
    opts, args = getopt.getopt(argv, '', ['num_q='] )
  except getopt.GetoptError:
    log(ERROR, "Unexpected command line arg, expecting: exp.py --num_q=<>")
    sys.exit(1)

  for opt, arg in opts:
    opt_map[opt] = arg
  return opt_map

if __name__ == "__main__":
  # opt_map = get_opts(sys.argv[1:] )
  # log(WARNING, "opt_map= {}".format(pprint.pformat(opt_map) ) )
  # num_q = int(opt_map["--num_q"] )

  # plot_winning_freqs()
  plot_reptoall()
  # plot_simplex_vs_rep()


<reponame>Fuligor/Uczenie-sie-rekonstrukcji-rozdzielczosci-obrazow-za-pomoca-sieci-glebokich<gh_stars>0
import numpy as np
from scipy import signal
from PIL import Image

def resample(hr_image):
    return hr_image[range(0, hr_image.shape[0], 2)][:, range(0, hr_image.shape[1], 2)]

def downsample(hr_image, kernel):
    temp = np.zeros_like(hr_image)

    for i in range(temp.shape[2]):
        temp[:, :, i] = signal.convolve2d(hr_image[:, :, i], kernel, mode="same", boundary="symm")

    if temp.dtype != np.uint8:
        temp = (temp * 255).astype(np.uint8)

    temp = Image.fromarray(temp, mode='RGB')

    size = (hr_image.shape[1]//2, hr_image.shape[0]//2)
    lr_image = temp.resize(size=size, resample=Image.BOX)
    lr_image = lr_image.resize(size=(hr_image.shape[1], hr_image.shape[0]), resample=Image.NEAREST)

    return (np.array(lr_image) / 255).astype(np.float32)

def create_image_patches(image, patch_size, step):
    patches = []

    for i in range(0, image.shape[0] - patch_size[0], step):
        for j in range(0, image.shape[1] - patch_size[1], step):
            patch = image[i:i+patch_size[0], j:j+patch_size[1]]

            patches.append(patch)

    return patches


<reponame>guanyilun/cosmo-codes<filename>cosmoslib/aps/ps.py
"""Reusable functions related to camb and power spectrum calculation

This module collects some reusable functions that I used when working
with camb, power spectrum and covariance matrix
"""

import numpy as np
from scipy import interpolate
import healpy as hp
import pickle
from functools import reduce
import operator

class PS:
    """A container for CMB power spectrum."""
    def __init__(self, arg=None, order=('ell','TT','EE','BB','TE'), prefactor=False, verbose=False):
        """Simple power spectrum data wrapper

        Args:
            arg (str or ndarray): input data, can be a string to a file to load or
                an np.ndarray that contains the power spectrum. The array has to have
                a shape like [n_ell, n_spec].
            order (tuple(str)): order of columns in the input ps. Follow the naming
                convention like ell,TT,EE,BB,TE which is default
            prefactor (bool): whether input array has l(l+1)/2\pi prefactor included
        """
        self.ps = {}
        self.order=order
        self.prefactor=prefactor
        self.verbose=False
        # populate ps depending on the inputs
        if type(arg) == str:
            self.load_file(arg, order, prefactor)
        elif type(arg) == np.ndarray:
            self.load_arr(arg, order, prefactor)

    def __getattr__(self, key):
        if key in self.ps:
            return self.ps[key]
        return self.__dict__[key]

    def load_arr(self, arr, order=('ell','TT','EE','BB','TE'), prefactor=True):
        """Load data from a given array"""
        if arr.shape[-1] != len(order):
            # see if we are missing ells
            if arr.shape[-1] == len(order)-1:
                print("Didn't find ell, generating based on the shape now...")
                ell = np.arange(arr.shape[0])
                arr = np.pad(arr, ((0,0),(1,0)))
                arr[:,0] = ell
            else:
                raise ValueError("provided order doesn't match the input array!")
        # now populate fields
        self.order = order
        for i,c in enumerate(order):
            self.ps[c] = arr[:,i]
        # by default keep the unprefactored version
        self.prefactor = prefactor
        if prefactor:
            return self.remove_prefactor()
        else:
            return self

    def load_file(self, infile, order=('ell','TT','EE','BB','TE'), prefactor=True):
        """load ps from a given file, will be read using np.readtxt"""
        data = np.loadtxt(infile)
        return self.load_arr(data, order, prefactor)

    def __repr__(self):
        order = str(self.order).replace(' ','')
        return f"PS(lmin={int(self.lmin)},lmax={int(self.lmax)},prefactor={self.prefactor},order={order})"

    def __add__(self, other):
        if not issubclass(type(other), PS):
            raise NotImplementedError("Currently only support PS type ops!")
        # check for ell mismatch
        if np.any(self.ell != other.ell):
            if self.verbose:
                print("Warning: ells mismatch, interpolating...")
            return self.resample(other.ell) + other.resample(self.ell)
        # find common specs
        new_order = ['ell'] + [s for s in self.specs if s in other.specs]
        if len(new_order) < 2: raise ValueError("No common specs!")
        if self.prefactor != other.prefactor:
            # if prefactor mismatch, add prefactor to both of them
            self.remove_prefactor()
            other.remove_prefactor()
        new_ps = PS(order=new_order, prefactor=self.prefactor)
        assert np.all(self.ell == other.ell)
        new_ps.ps['ell'] = self.ell
        for s in new_ps.specs:
            new_ps.ps[s] = self.ps[s] + other.ps[s]
        return new_ps

    def __sub__(self, other):
        if not issubclass(type(other), PS):
            raise NotImplementedError("Currently only support PS type ops!")
        # check for ell mismatch
        if np.any(self.ell != other.ell):
            if self.verbose:
                print("Warning: ells mismatch, interpolating...")
            return self.resample(other.ell) - other.resample(self.ell)
        # find common specs
        new_order = ['ell'] + [s for s in self.specs if s in other.specs]
        if len(new_order) < 2: raise ValueError("No common specs!")
        if self.prefactor != other.prefactor:
            # if prefactor mismatch, add prefactor to both of them
            self.remove_prefactor()
            other.remove_prefactor()
        new_ps = PS(order=new_order, prefactor=self.prefactor)
        new_ps.ps['ell'] = self.ell
        for s in new_ps.specs:
            new_ps.ps[s] = self.ps[s] - other.ps[s]
        return new_ps

    def __mul__(self, other):
        if issubclass(type(other), PS):
            raise NotImplementedError("Currently only support number ops!")
        new_ps = PS(order=self.order, prefactor=self.prefactor)
        new_ps.ps['ell'] = self.ell
        for s in self.specs:
            new_ps.ps[s] = self.ps[s] * other
        return new_ps

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        if issubclass(type(other), PS):
            raise NotImplementedError("Currently only support number ops!")
        new_ps = PS(order=self.order, prefactor=self.prefactor)
        new_ps.ps['ell'] = self.ell
        for s in self.specs:
            new_ps.ps[s] = self.ps[s] / other
        return new_ps

    def __getitem__(self, field):
        if field not in self.order:
            raise ValueError(f"{field} not found!")
        return self.ps[field]

    @classmethod
    def from_arr(cls, arr, order=('ell','TT','EE','BB','TE'), prefactor=True):
        return cls(arr, order, prefactor)

    @property
    def lmin(self):
        return self.ps['ell'].min()

    @property
    def lmax(self):
        return self.ps['ell'].max()

    @property
    def ell(self):
        return self.ps['ell']

    @property
    def specs(self):
        return [o for o in self.order if o != 'ell']

    @property
    def values(self):
        # made sure ell starts from index 0
        return np.vstack([self.ps[s] for s in self.order]).T

    @property
    def shape(self):
        return self.values.shape

    def add_prefactor(self, inplace=True):
        if self.prefactor: return self
        if inplace:
            ell = self.ell
            for c in self.specs:
                self.ps[c] *= (ell+1)*ell/(2*np.pi)
            self.prefactor = True
            return self
        else:
            return PS(self.values,self.order,prefactor=False).add_prefactor()

    def remove_prefactor(self, inplace=True):
        if not self.prefactor: return self
        if inplace:
            ell = self.ell
            for c in self.specs:
                self.ps[c] *= 2*np.pi/(ell*(ell+1))
            self.prefactor = False
            return self
        else:
            return PS(self.values,self.order,prefactor=True).remove_refactor()

    def resample(self, new_ell, **kwargs):
        ell = self.ell
        # make sure we are within interpolation range
        m = np.logical_and(new_ell<=self.lmax,new_ell>=self.lmin)
        # create a new ps object
        new_ps = PS(order=self.order,prefactor=self.prefactor)
        new_ps.ps['ell'] = new_ell[m]
        for s in self.specs:
            new_ps.ps[s] = interpolate.interp1d(ell,self.ps[s],**kwargs)(new_ell[m])
        return new_ps

    def plot(self, fmt="-", name='C_\ell', axes=None, ncol=2, figsize=(12,9),
             legend=False, legend_below=True, anchor=(0.6,-0.2), filename=None,
             prefactor=True, logx=True, logy=True, show_cov=False, loc='best',
             cov=None, xlim=[], ylim=[], show_abs=True, tight=True, **kwargs):
        """Plot the power spectra"""
        import matplotlib.pyplot as plt
        ell = self.ell
        nrow = int(np.ceil(len(self.specs)/ncol))
        if not np.any(axes):
            fig, axes = plt.subplots(nrow, ncol,figsize=figsize)
        for i,s in enumerate(self.specs):
            spec = self.ps[s]
            ax = axes[i//ncol,i%ncol]
            if prefactor:
                spec_name = r'$\ell(\ell+1)%s^{\rm %s}/2\pi$' % (name, s)
            else:
                spec_name = r'$%s^{\rm %s}$' % (name, s)
            if show_abs:
                spec = np.abs(spec)
            if prefactor and not self.prefactor:
                spec = spec*ell*(ell+1)/2/np.pi
            elif not prefactor and self.prefactor:
                spec = spec*2*np.pi/ell/(ell+1)
            if show_cov:
                assert isinstance(cov, Covmat), "covmat not provided or invalid"
                assert np.allclose(cov.ell, ell), 'ell mismatch in cov'
                yerr = np.sqrt(cov[f'{s}{s}'])
                if prefactor and not self.prefactor:
                    yerr *= ell*(ell+1)/2/np.pi
                ax.errorbar(ell, spec, yerr=yerr, fmt=fmt, **kwargs)
            else:
                ax.plot(ell, spec, fmt, **kwargs)
            ax.set_xlabel(r'$\ell$')
            ax.set_ylabel(spec_name)
            if logx:
                ax.set_xscale('log')
            if logy:
                ax.set_yscale('log')
            if len(xlim) == 2:
                ax.set_xlim(xlim)
            if len(ylim) == 2:
                ax.set_ylim(ylim)
            if legend and not legend_below:
                ax.legend(loc=loc)
        if tight: plt.tight_layout()
        if legend and legend_below:
            ax.legend(ncol=4, bbox_to_anchor=anchor, frameon=False)
        if filename:
            plt.savefig(filename, bbox_inches='tight')
        return axes

    def gen_sim(self):
        """Generate a sim realization of the power spectra, use internal version"""
        # make sure we have everything we want
        target = ['ell','TT','EE','BB','TE']
        ok = [s for s in target if s in self.order] == target
        if not ok:
            raise ValueError("PS does not contain all of ell,TT,EE,BB,TE required")
        data = np.hstack([self.ps[s].reshape(-1,1) for s in target])
        rdata = gen_ps_realization(data, self.prefactor)
        new_ps = PS(order=target, prefactor=self.prefactor)
        for i,s in enumerate(target): new_ps.ps[s] = rdata[:,i]
        return new_ps

    def gen_sim_hp(self):
        """Generate a sim realization of the power spectra, wrapped around healpy,
        this is often 30% faster"""
        alm = self.gen_alm_hp()
        cl = hp.sphtfunc.alm2cl(alm)
        ell = np.arange(cl.shape[1])
        ps = PS(cl.T, order=('TT', 'EE', 'BB', 'TE', 'EB', 'TB'), prefactor=False)
        ps.ps['ell'] = ell
        ps.order += ('ell',)
        return ps

    def gen_alm_hp(self):
        if self.prefactor:
            self.remove_prefactor()
        # healpy requires array starts from zero, fill will 0
        ps = np.zeros((4,self.lmax+1))
        ps[:,self.lmin:] = self.values[:,1:].T
        alm = hp.synalm((ps[0],ps[1],ps[2],ps[3],np.zeros_like(ps[0]),np.zeros_like(ps[0])),
                        lmax=self.lmax, verbose=False, new=True)
        return alm

    def gen_map(self, nside, n=1):
        if n > 1:
            return [self.gen_map(nside) for i in range(n)]
        else:
            alm = self.gen_alm_hp(nside)
            return hp.alm2map(alm, nside)

    def covmat(self, noise, f_sky=1):
        """get covariance matrix given a noise model
        Args:
            noise: noise model of PS class
            f_sky: sky coverage fraction, 1 means full-sky coverage
        Returns:
            cov: a tensor of size [n_ell, n_ps, n_ps], for example with
                 a lmax of 5000, the tensor size will be [5000, 4, 4]
        """
        # assuming the beam is a gaussian beam with an ell dependent
        # beam size
        # ps_w_noise = self + noise
        ps = self.resample(noise.ell)
        ell, ClTT, ClEE, ClBB, ClTE = [ps.ps[spec]
                                       for spec in ['ell', 'TT','EE','BB','TE']]
        new_noise = noise.resample(ell)
        NlTT, NlEE, NlBB, NlTE = [new_noise.ps[spec] for spec in ['TT','EE','BB','TE']]
        # initialize empty covariance tensor. Since the covariance matrix
        # depends on ell, we will make a higher dimensional array [n_ell,
        # n_ps, n_ps] where the first index represents different ells, the
        # second and third parameters represents different power spectra
        n_ells = len(ell)
        cov = np.zeros([n_ells, 4, 4])
        cov[:,0,0] = 2/(2*ell+1)*(ClTT+NlTT)**2
        cov[:,1,1] = 2/(2*ell+1)*(ClEE+NlEE)**2
        cov[:,2,2] = 2/(2*ell+1)*(ClBB+NlBB)**2
        cov[:,3,3] = 1/(2*ell+1)*(ClTE**2+(ClTT+NlTT)*(ClEE+NlEE))
        cov[:,0,1] = 2/(2*ell+1)*ClTE**2
        cov[:,1,0] = 2/(2*ell+1)*ClTE**2
        cov[:,0,3] = 2/(2*ell+1)*ClTE*(ClTT+NlTT)
        cov[:,3,0] = 2/(2*ell+1)*ClTE*(ClTT+NlTT)
        cov[:,1,3] = 2/(2*ell+1)*ClTE*(ClEE+NlEE)
        cov[:,3,1] = 2/(2*ell+1)*ClTE*(ClEE+NlEE)
        # now we include the effect of partial sky coverage
        cov /= f_sky
        covmat = Covmat(ell, cov)

        return covmat

    def save(self, filename):
        np.savetxt(filename, self.values, comments=",".join(self.order))

class Noise(PS):
    def __init__(self, lmin, lmax):
        self.order = ('ell','TT','EE','BB','TE')
        self.prefactor = False
        ell = np.arange(lmin, lmax+1)
        self.ps = {'ell': ell}

class SimpleNoise(Noise):
    def __init__(self, nlev, fwhm, lmin, lmax):
        super().__init__(lmin, lmax)
        self.nlev = nlev
        self.fwhm = fwhm
        ell = self.ps['ell']
        NlTT = nlev**2*np.exp(ell*(ell+1)*fwhm**2/(8.*np.log(2)))
        NlPP = 2*NlTT
        self.ps.update({'TT': NlTT, 'EE': NlPP,
                        'BB': NlPP, 'TE': np.zeros_like(ell)})

class Covmat:
    """Simple block diagonal covariance matrix"""
    def __init__(self, ell, cov, order=('TT','EE','BB','TE')):
        self.order = order
        self.cov = cov
        self.ell = ell
    def inv(self):
        icov = np.zeros_like(self.cov)
        for i in range(len(self.ell)):
            icov[i,:,:] = np.linalg.inv(self.cov[i,:,:])
        return Covmat(self.ell, icov)
    def save(self, filename):
        with open(filename, 'wb') as f:
            pickle.dump(self, f)
    def __getitem__(self, field):
        """field can be of form TTTT,TTEE, etc"""
        if len(field) !=4:
            raise ValueError("Field has to be of form TTEE, TTBB, etc!")
        spec1 = field[:2]
        spec2 = field[2:]
        if (spec1 not in self.order) or (spec2 not in self.order):
            raise ValueError(f"{field} not found")
        idx1 = self.order.index(spec1)
        idx2 = self.order.index(spec2)
        return self.cov[:,idx1,idx2]

    @classmethod
    def from_file(cls, filename):
        with open(filename, "rb") as f:
            try: return pickle.load(f)
            except UnicodeDecodeError:
                return pickle.load(f, encoding='latin1')

def _check_ps(ps):
    """Check the type of power spectra"""
    # if ps is a 2D array
    if len(ps.shape)>1:
        # if ps has five columns -> tensor-like
        if ps.shape[-1] == 5:
            return "TENSOR"
        # if ps has four columns -> scaler-like
        elif ps.shape[-1] == 4:
            return "SCALER"
        # not sure what's inside
        else:
            return None
    else:
        raise ValueError

def add_prefactor(ps):
    """Add the l(l+1)/2\pi prefactor in a power spectrum"""
    # check the dimension of power spectra
    ells = ps[:, 0]
    for i in range(1,ps.shape[1]):
        ps[:,i] /= 2*np.pi/(ells*(ells+1))
    return ps

def remove_prefactor(ps):
    """Remove the l(l+1)/2\pi prefactor in a power spectrum"""
    ells = ps[:, 0]
    for i in range(1,ps.shape[1]):
        ps[:,i] *= 2*np.pi/(ells*(ells+1))
    return ps

def resample(ps, ell):
    ell_old = ps[:, 0]

    # interpolate into the theory,
    tt_old = ps[:, 1]
    ee_old = ps[:, 2]
    bb_old = ps[:, 3]
    te_old = ps[:, 4]

    tt_predicted = interpolate.interp1d(ell_old, tt_old)(ell)
    te_predicted = interpolate.interp1d(ell_old, te_old)(ell)
    ee_predicted = interpolate.interp1d(ell_old, ee_old)(ell)
    bb_predicted = interpolate.interp1d(ell_old, bb_old)(ell)

    cl_predicted = np.stack([ell, tt_predicted, ee_predicted, bb_predicted, te_predicted], axis=1)

    return cl_predicted

def join_noise_models(noise_models, method='min'):
    """join multiple noise models by a given method. Currently
    only method that works is the min, which means choose the
    noise_models with minimum noise in each ell.

    Args:
        noise_models: list of noise models
        method: method used to combine
    Returns:
        A new noise model with the noise models combined
    """
    # find lmin, lmax
    lmin = min(nm.lmin for nm in noise_models)
    lmax = max(nm.lmax for nm in noise_models)
    # placeholder to find corresponding ells
    ell = np.arange(0, lmax+1)
    noise = Noise(lmin, lmax)
    for spec in ['TT','EE','BB','TE']:
        # place holder to find min noise
        cl = np.zeros_like(ell).astype('float64')
        for nm in noise_models:
            nm_ell = nm.ell.astype(int)
            mask = np.logical_or(nm[spec]<cl[nm_ell], cl[nm_ell]==0)
            orig_mask = np.zeros_like(ell).astype(bool)
            orig_mask[nm_ell] = mask
            idx = np.where(orig_mask)[0]
            cl[idx] = nm[spec][mask]
        noise.ps[spec] = cl[noise.ell]
    return noise

def Dl2Cl(ps, inplace=False):
    """Add the l(l+1)/2\pi prefactor in a power spectrum"""
    # check the dimension of power spectra
    ells = ps[:, 0]
    if not inplace:
        new_ps = ps.copy()
    else:
        new_ps = ps
    for i in range(1,ps.shape[1]):
        new_ps[:,i] /= 2*np.pi/(ells*(ells+1))
    return new_ps

def Cl2Dl(ps, inplace=False):
    """Remove the l(l+1)/2\pi prefactor in a power spectrum"""
    ells = ps[:, 0]
    if not inplace:
        new_ps = ps.copy()
    else:
        new_ps = ps
    for i in range(1,ps.shape[1]):
        new_ps[:,i] *= 2*np.pi/(ells*(ells+1))
    return new_ps

def gen_ps_realization(ps, prefactor=True):
    """Generate a random power spectra realization

    Args:
        ps: power spectra
        prefactor: true if ps is Dl

    Returns:
        ps realization: consistent with prefactor choice
    """
    # first make a copy to make sure we don't affect the original
    if prefactor:
        ps = Dl2Cl(ps, inplace=False)
    ells, ClTT, ClEE, ClBB, ClTE = ps[:,0], ps[:,1], ps[:,2], ps[:,3], ps[:,4]

    # define empty arrays to hold the generated power spectra
    m_ps = np.zeros_like(ps)
    m_ps[:,0] = ells

    # this is certainly slow, but i don't need to run this very often
    # so it's fine to leave it like this. in principle i don't need to
    # keep all the negative part as well. These can be improved if
    # performance becomes an issue
    for i in range(len(ells)):
        l = int(ells[i])
        # generate gaussian random complex numbers with unit variance
        zeta1 = (np.random.randn(l+1)+1j*np.random.randn(l+1))/np.sqrt(2)
        zeta2 = (np.random.randn(l+1)+1j*np.random.randn(l+1))/np.sqrt(2)
        zeta3 = (np.random.randn(l+1)+1j*np.random.randn(l+1))/np.sqrt(2)

        # for m=0, zeta has to be real
        zeta1[0] = np.abs(zeta1[0])
        zeta2[0] = np.abs(zeta2[0])
        zeta3[0] = np.abs(zeta3[0])

        # generate alm
        aTlm = zeta1 * ClTT[i]**0.5
        aElm = zeta1 * ClTE[i] / (ClTT[i])**0.5 + zeta2*(ClEE[i] - ClTE[i]**2/ClTT[i])**0.5
        aBlm = zeta3 * ClBB[i]**0.5

        i_ClTT = np.real((aTlm[0]**2 + 2*(np.sum(np.abs(aTlm[1:])**2)))/(2*l+1))
        i_ClEE = np.real((aElm[0]**2 + 2*(np.sum(np.abs(aElm[1:])**2)))/(2*l+1))
        i_ClBB = np.real((aBlm[0]**2 + 2*(np.sum(np.abs(aBlm[1:])**2)))/(2*l+1))
        i_ClTE = np.real((aTlm[0]*aElm[0] + 2*(np.sum(np.conj(aTlm[1:])*aElm[1:])))/(2*l+1))

        # assign the new values to the new array
        m_ps[i,1] = i_ClTT
        m_ps[i,2] = i_ClEE
        m_ps[i,3] = i_ClBB
        m_ps[i,4] = i_ClTE

    if prefactor:
        return Cl2Dl(m_ps, inplace=True)
    else:
        return m_ps

def fisher_matrix(model, cov, ratio=0.01):
    """Estimate the fisher matrix near the model provided.

    Args:
        model: a python dictionary that contains the cosmological
        parameters that one is interested in estimating the fisher
        matrix between them (fiducial model)

        cov: the covariance matrix between the power spectra

        ratio: the ratio of the parameter to vary. This is because fisher
        matrix calculation involves differenciating the power spectra
        with respect to the parameters given. To estimate the derivatives
        one needs a small variation near the best fit value. This ratio
        characterize how much variation is needed.

    Returns:
        alpha: fisher matrix
        params: a list of the parameters corresponding to each index
    """
    # first we need to generate multiple models near the best fit model
    # we will generate four models for each parameter, so in total the
    # underlying CAMB is called n_par * 4 times. First we create an empty
    # list to hold our derivatives. Each element in dCldp will correspond
    # to the derivative of the corresponding parameter in parameters list
    params = [key for key in model.keys()]
    n_params = len(params)
    dCldp = []

    for p in params:
        # generate four models by tweaking parameters
        new_model_m1 = model.copy()
        new_model_m2 = model.copy()
        new_model_p1 = model.copy()
        new_model_p2 = model.copy()

        # step size
        h = model[p]*ratio

        new_model_m2[p] = model[p] - h
        new_model_m1[p] = model[p] - 0.5*h
        new_model_p1[p] = model[p] + 0.5*h
        new_model_p2[p] = model[p] + h

        # generate four sets of parameters
        pars_m1 = generate_camb_params(**new_model_m1)
        pars_m2 = generate_camb_params(**new_model_m2)
        pars_p1 = generate_camb_params(**new_model_p1)
        pars_p2 = generate_camb_params(**new_model_p2)

        # get power spectra for both models
        ClTT_m1, ClEE_m1, ClBB_m1, ClTE_m1 = generate_cmb_power_spectra(pars_m1)
        ClTT_m2, ClEE_m2, ClBB_m2, ClTE_m2 = generate_cmb_power_spectra(pars_m2)
        ClTT_p1, ClEE_p1, ClBB_p1, ClTE_p1 = generate_cmb_power_spectra(pars_p1)
        ClTT_p2, ClEE_p2, ClBB_p2, ClTE_p2 = generate_cmb_power_spectra(pars_p2)

        # calculate differenciations to p
        dClTTdp = (4.0/3.0 * (ClTT_p1-ClTT_m1) -1.0/6.0 * (ClTT_p2 - ClTT_m2)) / h
        dClEEdp = (4.0/3.0 * (ClEE_p1-ClEE_m1) -1.0/6.0 * (ClEE_p2 - ClEE_m2)) / h
        dClBBdp = (4.0/3.0 * (ClBB_p1-ClBB_m1) -1.0/6.0 * (ClBB_p2 - ClBB_m2)) / h
        dClTEdp = (4.0/3.0 * (ClTE_p1-ClTE_m1) -1.0/6.0 * (ClTE_p2 - ClTE_m2)) / h

        dCldp.append(np.vstack([dClTTdp, dClEEdp, dClBBdp, dClTEdp]))

    # now that the derivatives are calculated, we are ready to calculate the
    # fisher matrix alpha.
    # TODO: This calculation can perhaps be optimized
    alpha = np.zeros([n_params, n_params])
    n_ell = cov.shape[0]
    for i in range(n_params):
        for j in range(n_params):
            for l in np.arange(n_ell):
                alpha[i,j] += np.einsum('i,ij,j', dCldp[i][:,l], np.linalg.inv(cov[l,:,:]), dCldp[j][:,l])

    return alpha, params

def combine_noise_models(noises):
    """Combine noise from multiple frequencies using inverse average, adapted
    from astropaint

    Parameters
    ----------
    noises: list of noise objects

    Returns
    -------
    noise object with combined noise power spectrum
    1/N_tot = 1/N1 + 1/N2 + ...

    """
    assert isinstance(Nls, (list,Noise))
    # find a dummy noise object by adding them, we simply
    # use this to get the ell interpolation to work
    combined = reduce(operator.add, noises)
    for spec in combined.specs:
        combined.ps[spec] = np.sum([1/n.resample(combined.ell).ps[spec]
                                    for n in noises])**-1
    return combined