```python
from IPython.core.display import display, HTML
display(HTML("<style>div.output_scroll { height: 55em; }</style>"))
%matplotlib notebook
import numpy as np
import sympy as sp
import cxroots as cx
import scipy.special as spsp
import matplotlib.pyplot as plt
import matplotlib.colors as clr
import matplotlib.cm as cm
import ipywidgets as widgets
from IPython.display import display
from IPython.display import Markdown
import traceback
from IPython.display import HTML

HTML('''
The raw code of P3Delta Online is by default hidden for easier reading.
To toggle on/off the raw code, click <a href="javascript:code_toggle()">here</a>.''')

```

```python
# -----------------------------------------------------------------------------
# Global variable use in function, do not touch !
# -----------------------------------------------------------------------------

a_coeff_mid_generic = []
b_coeff_mid_generic = []
a_coeff_mid_co = []
b_coeff_mid_co = []
a_coeff_crrid_generic = []
b_coeff_crrid_generic = []
computedtau = 0
computeds0 = 0
liste_s0_sensitivity_max = 0
liste_s0_sensitivity_min = 0
liste_tau_sensitivity_max = 0
liste_tau_sensitivity_min = 0

# -----------------------------------------------------------------------------
# Computation function for back-end
# -----------------------------------------------------------------------------

def compute_root_spectrum_mid_generic(n, m, s0, value_tau):
    global a_coeff_mid_generic
    global b_coeff_mid_generic
    s = sp.symbols('s')
    tau = sp.symbols('tau')
    a = sp.symbols(["a{:d}".format(i) for i in range(n)], real=True)
    alpha = sp.symbols(["alpha{:d}".format(i) for i in range(m + 1)],
                       real=True)
    polynomial = s ** n + np.array(a).dot([s ** i for i in range(n)])
    delayed = np.array(alpha).dot([s ** i for i in range(m + 1)]) * sp.exp(
        -s * tau)
    q = polynomial + delayed
    sysderivatif = [q]
    for i in range(n + m + 1):
        dernierederivee = sysderivatif[-1]
        sysderivatif.append(dernierederivee.diff(s))
    sol = sp.linsolve(sysderivatif[:-1], alpha + a).args[0]
    solnum = sol.subs({s: s0})
    solnum = solnum.subs({tau: value_tau})
    a_num = list(solnum[m + 1:])
    a_coeff_mid_generic = a_num.copy()
    a_coeff_mid_generic.append(1)
    alpha_num = list(solnum[:m + 1])
    b_coeff_mid_generic = alpha_num.copy()
    qnumerique = s ** n + np.array(a_num).dot([s ** i for i in range(n)]) + \
        np.array(alpha_num).dot([s ** i for i in range(m + 1)]) * \
        sp.exp(-s * tau)
    qnumerique = qnumerique.subs(tau, value_tau)
    sysrootfinding = [qnumerique, qnumerique.diff(s)]
    sysfunc = [sp.lambdify(s, i) for i in sysrootfinding]
    rect = cx.Rectangle([-100, 10], [-100, 100])
    roots = rect.roots(sysfunc[0], sysfunc[1], rootErrTol=1e-5, absTol=1e-5,
                       M=n + m + 1)
    xroot = np.real(roots[0])
    yroot = np.imag(roots[0])
    return xroot, yroot, qnumerique

def factorization_integral_latex(n, m, s0, tau):
    factor = str(tau ** (m + 1) / spsp.factorial(m))
    parenthesis = "(s + " + str(-s0) + ")"
    power = "^{" + str(n + m + 1) + "}"
    return r"\$" + "\\Delta(s) = " + factor + parenthesis + power + \
           r"\int_0^1 t^{" + str(m) + r"} (1 - t)^{" + str(n) + \
           "} e^{-" + str(tau) + "t" + parenthesis + \
           "} \\mathrm{d}t" + "$"

def factorization_1f1_latex(n, m, s0, tau):
    factor = str(
        tau ** (m + 1) * spsp.factorial(n) / spsp.factorial(n + m + 1))
    parenthesis = "(s + " + str(-s0) + ")"
    power = "^{" + str(n + m + 1) + "}"
    return r"\$" + "\\Delta(s) = " + factor + parenthesis + power + \
           r" {}_1 F_1(" + str(m + 1) + r", " + str(n + m + 2) + ", -" + \
           str(tau) + parenthesis + ")" + "$"

# -----------------------------------------------------------------------------
def compute_admissibilite(n, m, a_coeff_mid_co):
    s = sp.symbols('s')
    tau = sp.symbols('tau')
    a = sp.symbols(["a{:d}".format(i) for i in range(n)], real=True)
    alpha = sp.symbols(["alpha{:d}".format(i) for i in range(m + 1)],
                       real=True)
    avalue = a_coeff_mid_co
    polynomial = s ** n + np.array(a).dot([s ** i for i in range(n)])
    delayed = np.array(alpha).dot([s ** i for i in range(m + 1)]) * sp.exp(
        -s * tau)
    q = polynomial + delayed
    sysderivatif = [q]
    for i in range(m + 1):
        dernierederivee = sysderivatif[-1]
        sysderivatif.append(dernierederivee.diff(s))
    sol = sp.linsolve(sysderivatif[:-1], alpha).args[0]
    polyadm = sysderivatif[-1].subs(
        {alph: alphacoef for alph, alphacoef in zip(alpha, sol)})
    polyadm = polyadm.subs({asymb: aval for asymb, aval in zip(a, avalue)})
    polyadm = sp.simplify(polyadm)
    s0range = np.arange(-10, 0, 0.01)
    taurange = np.arange(0, 10, 0.01)
    return s0range, taurange, polyadm, s, tau, q, avalue, sysderivatif, \
        alpha, a, sol

def compute_root_spectrum_mid_co(n, m, value_tau):
    global a_coeff_mid_co
    global b_coeff_mid_co
    s = sp.symbols('s')
    tau = sp.symbols('tau')
    a = sp.symbols(["a{:d}".format(i) for i in range(n)], real=True)
    alpha = sp.symbols(["alpha{:d}".format(i) for i in range(m + 1)],
                       real=True)
    polynomial = s ** n + np.array(a).dot([s ** i for i in range(n)])
    delayed = np.array(alpha).dot([s ** i for i in range(m + 1)]) * sp.exp(
        -s * tau)
    q = polynomial + delayed
    sysderivatif = [q]
    for i in range(n + m + 1):
        dernierederivee = sysderivatif[-1]
        sysderivatif.append(dernierederivee.diff(s))
    a_num = a_coeff_mid_co.copy()
    a_coeff_mid_co.append(1)
    alpha_num = b_coeff_mid_co.copy()
    qnumerique = s ** n + np.array(a_num).dot([s ** i for i in range(n)]) + \
        np.array(alpha_num).dot([s ** i for i in range(m + 1)]) * \
        sp.exp(-s * tau)
    qnumerique = qnumerique.subs(tau, value_tau)
    sysrootfinding = [qnumerique, qnumerique.diff(s)]
    sysfunc = [sp.lambdify(s, i) for i in sysrootfinding]
    rect = cx.Rectangle([-100, 10], [-100, 100])
    roots = rect.roots(sysfunc[0], sysfunc[1], rootErrTol=1e-5, absTol=1e-5,
                       M=n + m + 1)
    xroot = np.real(roots[0])
    yroot = np.imag(roots[0])
    return xroot, yroot, qnumerique

def getroots(m, q, delay, avalue, alphavalue, xwindow, ywindow, s, a, alpha,
             tau):
    derivees = [q, q.diff(s)]
    for i in range(len(derivees)):
        derivees[i] = derivees[i].subs(
            {ai: ai_num for ai, ai_num in zip(a, avalue)})
        derivees[i] = derivees[i].subs(
            {alphai: alphai_num for alphai, alphai_num in
             zip(alpha, alphavalue)})
        derivees[i] = derivees[i].subs({tau: delay})
    func = [sp.lambdify(s, i) for i in derivees]
    rect = cx.Rectangle(xwindow, ywindow)
    roots = rect.roots(func[0], func[1], rootErrTol=1e-5, absTol=1e-5, M=m + 2)
    xroot, yroot = np.real(roots[0]), np.imag(roots[0])
    return xroot, yroot, func

def solve_tau_connu(tau_val, acoef, q, m, s, sysderivatif, alpha, a, tau):
    sys = [q]
    for i in range(m + 1):
        dernierederivee = sys[-1]
        sys.append(dernierederivee.diff(s))
    sol = sp.linsolve(sysderivatif[:-1], alpha).args[0]
    finaleq = sysderivatif[-1].subs(
        {alph: alphacoef for alph, alphacoef in zip(alpha, sol)})
    finaleq = finaleq.subs({asymb: aval for asymb, aval in zip(a, acoef)})
    sols0 = finaleq.subs({tau: tau_val})
    sols0 = sp.solve(sols0)
    sols0eval = [i.evalf() for i in sols0]
    try:
        solution = max([i for i in sols0eval if i < 0])
    except Exception:
        traceback.print_exc()
    return solution

def compute_sensibilite(value_tau, q, m, s, sysderivatif, alpha, a, tau):
    global a_coeff_mid_co
    tau_nominal = value_tau
    step = 1e-2
    nbit = 10
    values = [0] + [-step * i for i in range(1, nbit + 1)] + [step * i for i in
                                                              range(1,
                                                                    nbit + 1)]
    values.sort()
    tau_sens = []
    s0_sens = []
    for value in values:
        tau_sens.append(tau_nominal + value)
        s0_sens.append(solve_tau_connu(tau_sens[-1], a_coeff_mid_co, q, m, s,
                                       sysderivatif, alpha, a, tau))
    normaliser = clr.Normalize(min(tau_sens), max(tau_sens))
    colormapper = cm.ScalarMappable(norm=normaliser,
                                    cmap=plt.get_cmap("rainbow"))
    colormapper.set_array(tau_sens)
    return tau_sens, s0_sens, colormapper

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

def compute_crrid_generic_root(n, m, s0, d, value_tau):
    global a_coeff_crrid_generic
    global b_coeff_crrid_generic
    slist = [s0 - i * d for i in range(n + m + 1)]
    s = sp.symbols('s')
    tau = sp.symbols('tau')
    a = sp.symbols(["a{:d}".format(i) for i in range(n)], real=True)
    alpha = sp.symbols(["alpha{:d}".format(i) for i in range(m + 1)],
                       real=True)
    polynomial = s ** n + np.array(a).dot([s ** i for i in range(n)])
    delayed = np.array(alpha).dot([s ** i for i in range(m + 1)]) * sp.exp(
        -s * tau)
    q = polynomial + delayed
    sys = [q] * (n + m + 1)
    for i in range(n + m + 1):
        sys[i] = sys[i].subs({s: slist[i]})
        sys[i] = sys[i].subs({tau: value_tau})
    sol = sp.linsolve(sys, alpha + a).args[0]
    solnum = sol.subs({tau: value_tau})
    a_num = list(solnum[m + 1:])
    a_coeff_crrid_generic = a_num.copy()
    a_coeff_crrid_generic.append(1)
    alpha_num = list(solnum[:m + 1])
    b_coeff_crrid_generic = alpha_num.copy()
    qnumerique = s ** n + np.array(a_num).dot([s ** i for i in range(n)]) + \
        np.array(alpha_num).dot(
            [s ** i for i in range(m + 1)]) * sp.exp(-s * tau)
    qnumerique = qnumerique.subs(tau, value_tau)
    sysrootfinding = [qnumerique, qnumerique.diff(s)]
    sysfunc = [sp.lambdify(s, i) for i in sysrootfinding]
    rect = cx.Rectangle([-100, 10], [-100, 100])
    roots = rect.roots(sysfunc[0], sysfunc[1], rootErrTol=1e-5, absTol=1e-5,
                       M=n + m + 1)
    xroot = np.real(roots[0])
    yroot = np.imag(roots[0])
    return xroot, yroot, qnumerique

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

def explicit_euler(n, tau, acoef, bcoef, t_final, n_iter, init_type,
                   init_args):
    while acoef.size < n:
        acoef = np.append(acoef, 0)
    a0 = np.diag(np.ones(n - 1), 1)
    a0[-1, :] = -1 * acoef
    while bcoef.size < n:
        bcoef = np.append(bcoef, 0)
    a1 = np.zeros((n, n))
    a1[-1, :] = -1 * bcoef
    dt = t_final / (n_iter - 1)
    npast = int(np.ceil(tau / dt))
    dt = tau / npast
    time = np.linspace(start=float(tau), stop=t_final, num=n_iter)
    sol = np.zeros((n, time.size))
    sol[:, :(npast + 1)] = initial_solution(init_type, init_args, n,
                                            time[:(npast + 1)])
    for i in range(npast, time.size - 1):
        sol[:, i + 1] = sol[:, i] + dt * (
                a0.dot(sol[:, i]) + a1.dot(sol[:, i - npast]))
    return time, sol

def initial_solution(init_type, init_args, n, t):
    x = sp.symbols('x')
    if init_type == 'Constant':
        y0 = np.zeros((n, t.size))
        y0[0, :] = init_args
        return y0

    elif init_type == 'Polynomial':

        degree = init_args[0]
        p = np.array(init_args[1:]).dot([x ** i for i in range(degree + 1)])
        derivees = []
        y0 = np.zeros((n, t.size))
        for i in range(n):
            if i == 0:
                derivees.append(p)
            else:
                derivees.append(derivees[-1].diff(x))
            for j in range(t.size):
                y0[i, j] = derivees[i].subs({x: t[j]})
        return y0

    elif init_type == 'Exponential':
        gain = init_args[0]
        exponant = init_args[1]
        e = gain * sp.exp(exponant * x)
        derivees = []
        y0 = np.zeros((n, t.size))
        for i in range(n):
            if i == 0:
                derivees.append(e)
            else:
                derivees.append(derivees[-1].diff(x))
            for j in range(t.size):
                y0[i, j] = derivees[i].subs({x: t[j]})
        return y0

    elif init_type == 'Trigonometric':
        ampl = init_args[0]
        freq = init_args[1]
        phase = init_args[2]
        tt = ampl * sp.sin(freq * x + phase)
        derivees = []
        y0 = np.zeros((n, t.size))
        for i in range(n):
            if i == 0:
                derivees.append(tt)
            else:
                derivees.append(derivees[-1].diff(x))
            for j in range(t.size):
                y0[i, j] = derivees[i].subs({x: t[j]})
        return y0
    else:
        pass

def initialsolution(degn, delay, acoef, bcoef, t_final, n_iter, init_type,
                    init_args):
    acoef = np.array(acoef[:-1])
    bcoef = np.array(bcoef)
    time, sol = explicit_euler(degn, delay, acoef, bcoef, t_final, n_iter,
                               init_type, init_args)
    return time, sol

# -----------------------------------------------------------------------------
# Output
# -----------------------------------------------------------------------------

output_compute_analysis_mid_generic = widgets.Output()
output_time_simulation_computation_mid_generic = widgets.Output()

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

output_admissibilite_computation_mid_co = widgets.Output()
output_compute_analysis_mid_co = widgets.Output()
output_sensibilite_computation_mid_co = widgets.Output()
output_time_simulation_computation_mid_co = widgets.Output()

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

output_compute_analysis_mid_crrid_generic = widgets.Output()
output_time_simulation_computation_1_crrid_generic = widgets.Output()

# -----------------------------------------------------------------------------
# User Interface input
# -----------------------------------------------------------------------------

slider_n_mid_generic = widgets.IntSlider(min=0, max=7, step=1,
                                         description='n :', value=2)

def slider_n_mid_generic_eventhandler(change):
    slider_m_mid_generic.max = change.new
    text_error_mid_generic.value = "Be aware ! n should be higher " \
                                   "or equal to m ! "

slider_n_mid_generic.observe(slider_n_mid_generic_eventhandler, names='value')

slider_m_mid_generic = widgets.IntSlider(min=0, max=7, step=1,
                                         description='m :', value=1)

floatext_s0_mid_generic = widgets.BoundedFloatText(min=-10.00, max=0.00,
                                                   step=0.01,
                                                   description='s0 :',
                                                   value=-5.00, disabled=False)
floattext_tau_mid_generic = widgets.BoundedFloatText(min=0.50, max=2.00,
                                                     step=0.01,
                                                     description='tau :',
                                                     value=1.00,
                                                     disabled=False)

button_ready_to_compute_mid_generic = widgets.Button(
    description='Compute analysis', layout=widgets.Layout(width='auto'),
    disabled=False, button_style='warning',
    icon='check-circle')
intprogress_1_mid_generic = widgets.IntProgress(
    layout=widgets.Layout(width='auto'), value=0, min=0, max=10,
    description='Loading  ', style={'bar_color': '#ff9800'},
    orientation='horizontal')
layout_validate_1_mid_generic = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_compute_mid_generic,
    center=None,
    right_sidebar=intprogress_1_mid_generic,
    footer=None)

text_error_mid_generic = widgets.Text(
    value='It is all good',
    placeholder='Information may appear here',
    description='',
    disabled=True,
    layout=widgets.Layout(
        width='auto'))

constant_constant_mid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Constant :', disabled=False)

p = 9
items_floattext_polynomial_coef_mid_generic = [
    widgets.BoundedFloatText(step=0.1, description='a' + str(i), value=1,
                             disabled=False,
                             layout=widgets.Layout(max_width='800px')) for i in
    range(p - 1)]
box_0_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0]],
    layout=widgets.Layout(max_width='auto'))
box_1_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1]],
    layout=widgets.Layout(max_width='auto'))
box_2_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2]],
    layout=widgets.Layout(max_width='auto'))
box_3_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2],
     items_floattext_polynomial_coef_mid_generic[3]],
    layout=widgets.Layout(max_width='auto'))
box_4_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2],
     items_floattext_polynomial_coef_mid_generic[3],
     items_floattext_polynomial_coef_mid_generic[4]],
    layout=widgets.Layout(max_width='auto'))
box_5_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2],
     items_floattext_polynomial_coef_mid_generic[3],
     items_floattext_polynomial_coef_mid_generic[4],
     items_floattext_polynomial_coef_mid_generic[5]],
    layout=widgets.Layout(max_width='auto'))
box_6_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2],
     items_floattext_polynomial_coef_mid_generic[3],
     items_floattext_polynomial_coef_mid_generic[4],
     items_floattext_polynomial_coef_mid_generic[5],
     items_floattext_polynomial_coef_mid_generic[6]],
    layout=widgets.Layout(max_width='auto'))
box_7_floattext_polynomial_coef_mid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_mid_generic[0],
     items_floattext_polynomial_coef_mid_generic[1],
     items_floattext_polynomial_coef_mid_generic[2],
     items_floattext_polynomial_coef_mid_generic[3],
     items_floattext_polynomial_coef_mid_generic[4],
     items_floattext_polynomial_coef_mid_generic[5],
     items_floattext_polynomial_coef_mid_generic[6],
     items_floattext_polynomial_coef_mid_generic[7]],
    layout=widgets.Layout(max_width='auto'))

stacked = widgets.Stacked([box_0_floattext_polynomial_coef_mid_generic,
                           box_1_floattext_polynomial_coef_mid_generic,
                           box_2_floattext_polynomial_coef_mid_generic,
                           box_3_floattext_polynomial_coef_mid_generic,
                           box_4_floattext_polynomial_coef_mid_generic,
                           box_5_floattext_polynomial_coef_mid_generic,
                           box_6_floattext_polynomial_coef_mid_generic,
                           box_7_floattext_polynomial_coef_mid_generic])
polynomial_degree_mid_generic = widgets.Dropdown(description='degree : ',
                                                 options=['0', '1', '2', '3',
                                                          '4', '5', '6', '7'])
widgets.jslink((polynomial_degree_mid_generic, 'index'),
               (stacked, 'selected_index'))
stacked_2_simulation_time_mid_generic = widgets.VBox(
    [polynomial_degree_mid_generic, stacked])
polynomial_mid_generic = widgets.HBox([stacked_2_simulation_time_mid_generic],
                                      layout=widgets.Layout(width='auto'))

exponential_exponant_mid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Exponant :', disabled=False)
exponential_gain_mid_generic = widgets.BoundedFloatText(value=1, step=1,
                                                        description='Gain :',
                                                        disabled=False)
exponential_mid_generic = widgets.HBox(
    [exponential_exponant_mid_generic, exponential_gain_mid_generic],
    layout=widgets.Layout(width='auto'))

trigonometric_amplitude_mid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Amplitude :', disabled=False)
trigonometric_frequency_mid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Frequency :', disabled=False)
trigonometric_phase_mid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Phase :', disabled=False)
trigonometric_mid_generic = widgets.HBox(
    [trigonometric_amplitude_mid_generic, trigonometric_frequency_mid_generic,
     trigonometric_phase_mid_generic], layout=widgets.Layout(width='auto'))

stacked_mid_generic = widgets.Stacked(
    [constant_constant_mid_generic, polynomial_mid_generic,
     exponential_mid_generic, trigonometric_mid_generic])
dropdown_mid_generic = widgets.Dropdown(
    options=['Constant', 'Polynomial', 'Exponential', 'Trigonometric'])

widgets.jslink((dropdown_mid_generic, 'index'),
               (stacked_mid_generic, 'selected_index'))

stacked_simulation_time_mid_generic = widgets.VBox(
    [dropdown_mid_generic, stacked_mid_generic])

floattext_simulation_time_mid_generic = widgets.BoundedFloatText(
    min=10.0,
    max=60.0,
    step=0.1,
    description='Simulation time :',
    value=10.0,
    disabled=False,
    style={
        'description_width': 'initial'})

button_ready_to_simulation_time_mid_generic = widgets.Button(
    description='Compute time simulation', layout=widgets.Layout(width='auto'),
    disabled=False, button_style='danger', icon='check-circle')
intprogress_2_mid_generic = widgets.IntProgress(
    layout=widgets.Layout(width='auto'), value=0, min=0, max=10,
    description='Loading  ', style={'bar_color': '#f84434'},
    orientation='horizontal')
layout_validate_2_mid_generic = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_simulation_time_mid_generic,
    center=None,
    right_sidebar=intprogress_2_mid_generic,
    footer=None)

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

slider_n_mid_co = widgets.IntSlider(min=1, max=7, step=1, description='n :',
                                    value=2)

def slider_n_mid_co_eventhandler(change):
    slider_m_mid_co.max = change.new

slider_n_mid_co.observe(slider_n_mid_co_eventhandler, names='value')

slider_m_mid_co = widgets.IntSlider(min=0, max=7, step=1, description='m :',
                                    value=1)

button_confirm_degrees_mid_co = widgets.Button(description='Confirm degrees',
                                               layout=widgets.Layout(
                                                   width='18%'),
                                               disabled=False,
                                               button_style='warning',
                                               icon='angle-double-right')

box_number = 9
items_inttext_a_mid_co = [
    widgets.BoundedIntText(min=-10, max=10, step=1, description='', value=0,
                           disabled=True) for i in range(box_number - 1)]

sub_list_1 = []
sub_list_2 = []

for k in range(0, 4):
    sub_list_1.append(items_inttext_a_mid_co[k])

for k in range(4, len(items_inttext_a_mid_co)):
    sub_list_2.append(items_inttext_a_mid_co[k])

inttext_a_1_mid_co = widgets.Box(children=sub_list_1,
                                 layout=widgets.Layout(width='auto',
                                                       max_width='81%',
                                                       height=''))
inttext_a_2_mid_co = widgets.Box(children=sub_list_2,
                                 layout=widgets.Layout(width='auto',
                                                       max_width='81%',
                                                       height=''))

button_confirm_system_coeff_mid_co = widgets.Button(
    description='Confirm coefficients', layout=widgets.Layout(width='16%'),
    disabled=False, button_style='warning', icon='angle-double-right')

togglebuttons_mid_co = widgets.ToggleButtons(options=['tau', 's0'],
                                             disabled=False, button_style='',
                                             tooltips=['tau is known',
                                                       's0 is known'])
togglebuttons_mid_co.style.button_width = 'auto'

def togglebuttons_mid_co_eventhandler(change):
    if change.new == 's0':
        floattext_s0_tau_mid_co.min, floattext_s0_tau_mid_co.max = \
            liste_s0_sensitivity_min, liste_s0_sensitivity_max
        floattext_s0_tau_mid_co.step = 0.001
        floattext_s0_tau_mid_co.value = -1.506
        floattext_s0_tau_mid_co.description = 's0 : '
    if change.new == 'tau':
        floattext_s0_tau_mid_co.max, floattext_s0_tau_mid_co.min = \
            liste_tau_sensitivity_max, liste_tau_sensitivity_min
        floattext_s0_tau_mid_co.step = 0.01
        floattext_s0_tau_mid_co.value = 0.73
        floattext_s0_tau_mid_co.description = 'tau : '

togglebuttons_mid_co.observe(togglebuttons_mid_co_eventhandler, names='value')

floattext_s0_tau_mid_co = widgets.BoundedFloatText(description='tau : ',
                                                   disabled=True)

button_ready_to_compute_mid_co = widgets.Button(description='Compute analysis',
                                                layout=widgets.Layout(
                                                    width='auto'),
                                                disabled=False,
                                                button_style='warning',
                                                icon='check-circle')
intprogress_1_mid_co = widgets.IntProgress(layout=widgets.Layout(width='auto'),
                                           value=0, min=0, max=10,
                                           description='Loading  ',
                                           style={'bar_color': '#ff9c28'},
                                           orientation='horizontal')
layout_validate_1_mid_co = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_compute_mid_co,
    center=None,
    right_sidebar=intprogress_1_mid_co,
    footer=None)

text_error_mid_co = widgets.Text(value='It is all good',
                                 placeholder='Information may appear here',
                                 description='', disabled=True,
                                 layout=widgets.Layout(width='auto'))

constant_constant_mid_co = widgets.BoundedFloatText(value=1, step=1,
                                                    description='Constant :',
                                                    disabled=False)

p = 9
items_floattext_polynomial_coef_mid_co = [
    widgets.BoundedFloatText(step=0.1, description='a' + str(i), value=1,
                             disabled=False,
                             layout=widgets.Layout(max_width='800px')) for i in
    range(p - 1)]
box_0_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0]],
    layout=widgets.Layout(max_width='auto'))
box_1_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1]],
    layout=widgets.Layout(max_width='auto'))
box_2_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2]],
    layout=widgets.Layout(max_width='auto'))
box_3_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2],
     items_floattext_polynomial_coef_mid_co[3]],
    layout=widgets.Layout(max_width='auto'))
box_4_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2],
     items_floattext_polynomial_coef_mid_co[3],
     items_floattext_polynomial_coef_mid_co[4]],
    layout=widgets.Layout(max_width='auto'))
box_5_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2],
     items_floattext_polynomial_coef_mid_co[3],
     items_floattext_polynomial_coef_mid_co[4],
     items_floattext_polynomial_coef_mid_co[5]],
    layout=widgets.Layout(max_width='auto'))
box_6_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2],
     items_floattext_polynomial_coef_mid_co[3],
     items_floattext_polynomial_coef_mid_co[4],
     items_floattext_polynomial_coef_mid_co[5],
     items_floattext_polynomial_coef_mid_co[6]],
    layout=widgets.Layout(max_width='auto'))
box_7_floattext_polynomial_coef_mid_co = widgets.VBox(
    [items_floattext_polynomial_coef_mid_co[0],
     items_floattext_polynomial_coef_mid_co[1],
     items_floattext_polynomial_coef_mid_co[2],
     items_floattext_polynomial_coef_mid_co[3],
     items_floattext_polynomial_coef_mid_co[4],
     items_floattext_polynomial_coef_mid_co[5],
     items_floattext_polynomial_coef_mid_co[6],
     items_floattext_polynomial_coef_mid_co[7]],
    layout=widgets.Layout(max_width='auto'))

stacked_mid_co = widgets.Stacked([box_0_floattext_polynomial_coef_mid_co,
                                  box_1_floattext_polynomial_coef_mid_co,
                                  box_2_floattext_polynomial_coef_mid_co,
                                  box_3_floattext_polynomial_coef_mid_co,
                                  box_4_floattext_polynomial_coef_mid_co,
                                  box_5_floattext_polynomial_coef_mid_co,
                                  box_6_floattext_polynomial_coef_mid_co,
                                  box_7_floattext_polynomial_coef_mid_co])
polynomial_degree_mid_co = widgets.Dropdown(description='degree : ',
                                            options=['0', '1', '2', '3', '4',
                                                     '5', '6', '7'])

widgets.jslink((polynomial_degree_mid_co, 'index'),
               (stacked_mid_co, 'selected_index'))
stacked_2_simulation_time_mid_co = widgets.VBox(
    [polynomial_degree_mid_co, stacked_mid_co])
polynomial_mid_co = widgets.HBox([stacked_2_simulation_time_mid_co],
                                 layout=widgets.Layout(width='auto'))

exponential_exponant_mid_co = widgets.BoundedFloatText(
    value=1, step=1, description='Exponant :', disabled=False)
exponential_gain_mid_co = widgets.BoundedFloatText(value=1, step=1,
                                                   description='Gain :',
                                                   disabled=False)
exponential_mid_co = widgets.HBox(
    [exponential_exponant_mid_co, exponential_gain_mid_co],
    layout=widgets.Layout(width='auto'))

trigonometric_amplitude_mid_co = widgets.BoundedFloatText(
    value=1, step=1, description='Amplitude :', disabled=False)
trigonometric_frequency_mid_co = widgets.BoundedFloatText(
    value=1, step=1, description='Frequency :', disabled=False)
trigonometric_phase_mid_co = widgets.BoundedFloatText(value=1, step=1,
                                                      description='Phase :',
                                                      disabled=False)
trigonometric_mid_co = widgets.HBox(
    [trigonometric_amplitude_mid_co, trigonometric_frequency_mid_co,
     trigonometric_phase_mid_co], layout=widgets.Layout(width='auto'))

stacked_mid_co = widgets.Stacked(
    [constant_constant_mid_co, polynomial_mid_co, exponential_mid_co,
     trigonometric_mid_co])
dropdown_mid_co = widgets.Dropdown(
    options=['Constant', 'Polynomial', 'Exponential', 'Trigonometric'])

widgets.jslink((dropdown_mid_co, 'index'), (stacked_mid_co, 'selected_index'))

stacked_simulation_time_mid_co = widgets.VBox(
    [dropdown_mid_co, stacked_mid_co])

floattext_simulation_time_mid_co = widgets.BoundedFloatText(
    min=10.0,
    max=60.0,
    step=0.1,
    description='Simulation time :',
    value=10.0,
    disabled=False,
    style={
        'description_width': 'initial'})

button_ready_to_simulation_time_mid_co = widgets.Button(
    description='Compute simulation', layout=widgets.Layout(width='auto'),
    disabled=False, button_style='danger', icon='check-circle')
intprogress_3_mid_co = widgets.IntProgress(layout=widgets.Layout(width='auto'),
                                           value=0, min=0, max=10,
                                           description='Loading  ',
                                           style={'bar_color': '#f84434'},
                                           orientation='horizontal')
layout_validate_2_mid_co = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_simulation_time_mid_co,
    center=None,
    right_sidebar=intprogress_3_mid_co,
    footer=None)

button_ready_to_sensitivity_mid_co = widgets.Button(
    description='Compute sensitivity (optional)',
    layout=widgets.Layout(width='auto'), disabled=False, button_style='info',
    icon='check-circle')
intprogress_4_mid_co = widgets.IntProgress(layout=widgets.Layout(width='auto'),
                                           value=0, min=0, max=10,
                                           description='Loading  ',
                                           style={'bar_color': '#10acd8'},
                                           orientation='horizontal')
layout_validate_3_mid_co = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_sensitivity_mid_co,
    center=None,
    right_sidebar=intprogress_4_mid_co,
    footer=None)

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

slider_n_crrid_generic = widgets.IntSlider(min=1, max=7, step=1,
                                           description='n :', value=2)

def slider_n_crrid_generic_eventhandler(change):
    slider_m_crrid_generic.max = change.new

slider_n_crrid_generic.observe(slider_n_crrid_generic_eventhandler,
                               names='value')
slider_m_crrid_generic = widgets.IntSlider(min=0, max=7, step=1,
                                           description='m :', value=1)
slider_d_crrid_generic = widgets.IntSlider(min=0, max=5, step=1,
                                           description='d :', value=2)

floattext_s0_crrid_generic = widgets.BoundedFloatText(min=-10.00, max=0.00,
                                                      step=0.01,
                                                      description='s0 :',
                                                      value=-1.00,
                                                      disabled=False)
floattext_tau_crrid_generic = widgets.BoundedFloatText(min=0.50, max=2.00,
                                                       step=0.01,
                                                       description='tau :',
                                                       value=1.00,
                                                       disabled=False)

button_ready_to_compute_mid_crrid_generic = widgets.Button(
    description='Compute analysis', layout=widgets.Layout(width='auto'),
    disabled=False, button_style='warning',
    icon='check-circle')
intprogress_1_mid_crrid_generic = widgets.IntProgress(
    layout=widgets.Layout(width='auto'), value=0, min=0, max=10,
    description='Loading  ', style={'bar_color': '#ff9800'},
    orientation='horizontal')
layout_validate_1_mid_crrid_generic = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_compute_mid_crrid_generic,
    center=None,
    right_sidebar=intprogress_1_mid_crrid_generic,
    footer=None)

text_error_crrid_generic = widgets.Text(
    value='It is all good',
    placeholder='Information may appear here',
    description='',
    disabled=True,
    layout=widgets.Layout(
        width='auto'))

constant_constant_crrid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Constant :', disabled=False)

p = 9
items_floattext_polynomial_coef_crrid_generic = [
    widgets.BoundedFloatText(step=0.1, description='a' + str(i), value=1,
                             disabled=False,
                             layout=widgets.Layout(max_width='800px')) for i in
    range(p - 1)]
box_0_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0]],
    layout=widgets.Layout(max_width='auto'))
box_1_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1]],
    layout=widgets.Layout(max_width='auto'))
box_2_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2]],
    layout=widgets.Layout(max_width='auto'))
box_3_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2],
     items_floattext_polynomial_coef_crrid_generic[3]],
    layout=widgets.Layout(max_width='auto'))
box_4_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2],
     items_floattext_polynomial_coef_crrid_generic[3],
     items_floattext_polynomial_coef_crrid_generic[4]],
    layout=widgets.Layout(max_width='auto'))
box_5_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2],
     items_floattext_polynomial_coef_crrid_generic[3],
     items_floattext_polynomial_coef_crrid_generic[4],
     items_floattext_polynomial_coef_crrid_generic[5]],
    layout=widgets.Layout(max_width='auto'))
box_6_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2],
     items_floattext_polynomial_coef_crrid_generic[3],
     items_floattext_polynomial_coef_crrid_generic[4],
     items_floattext_polynomial_coef_crrid_generic[5],
     items_floattext_polynomial_coef_crrid_generic[6]],
    layout=widgets.Layout(max_width='auto'))
box_7_floattext_polynomial_coef_crrid_generic = widgets.VBox(
    [items_floattext_polynomial_coef_crrid_generic[0],
     items_floattext_polynomial_coef_crrid_generic[1],
     items_floattext_polynomial_coef_crrid_generic[2],
     items_floattext_polynomial_coef_crrid_generic[3],
     items_floattext_polynomial_coef_crrid_generic[4],
     items_floattext_polynomial_coef_crrid_generic[5],
     items_floattext_polynomial_coef_crrid_generic[6],
     items_floattext_polynomial_coef_crrid_generic[7]],
    layout=widgets.Layout(max_width='auto'))

stacked = widgets.Stacked([box_0_floattext_polynomial_coef_crrid_generic,
                           box_1_floattext_polynomial_coef_crrid_generic,
                           box_2_floattext_polynomial_coef_crrid_generic,
                           box_3_floattext_polynomial_coef_crrid_generic,
                           box_4_floattext_polynomial_coef_crrid_generic,
                           box_5_floattext_polynomial_coef_crrid_generic,
                           box_6_floattext_polynomial_coef_crrid_generic,
                           box_7_floattext_polynomial_coef_crrid_generic])
polynomial_degree_crrid_generic = widgets.Dropdown(description='degree : ',
                                                   options=['0', '1', '2', '3',
                                                            '4', '5', '6',
                                                            '7'])
widgets.jslink((polynomial_degree_crrid_generic, 'index'),
               (stacked, 'selected_index'))
stacked_2_simulation_time_crrid_generic = widgets.VBox(
    [polynomial_degree_crrid_generic, stacked])
polynomial_crrid_generic = widgets.HBox(
    [stacked_2_simulation_time_crrid_generic],
    layout=widgets.Layout(width='auto'))

exponential_exponant_crrid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Exponant :', disabled=False)
exponential_gain_crrid_generic = widgets.BoundedFloatText(value=1, step=1,
                                                          description='Gain :',
                                                          disabled=False)
exponential_crrid_generic = widgets.HBox(
    [exponential_exponant_crrid_generic, exponential_gain_crrid_generic],
    layout=widgets.Layout(width='auto'))

trigonometric_amplitude_crrid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Amplitude :', disabled=False)
trigonometric_frequency_crrid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Frequency :', disabled=False)
trigonometric_phase_crrid_generic = widgets.BoundedFloatText(
    value=1, step=1, description='Phase :', disabled=False)
trigonometric_crrid_generic = widgets.HBox(
    [trigonometric_amplitude_crrid_generic,
     trigonometric_frequency_crrid_generic, trigonometric_phase_crrid_generic],
    layout=widgets.Layout(width='auto'))

stacked_crrid_generic = widgets.Stacked(
    [constant_constant_crrid_generic, polynomial_crrid_generic,
     exponential_crrid_generic, trigonometric_crrid_generic])
dropdown_crrid_generic = widgets.Dropdown(
    options=['Constant', 'Polynomial', 'Exponential', 'Trigonometric'])

widgets.jslink((dropdown_crrid_generic, 'index'),
               (stacked_crrid_generic, 'selected_index'))

stacked_simulation_time_crrid_generic = widgets.VBox(
    [dropdown_crrid_generic, stacked_crrid_generic])

floattext_simulation_time_crrid_generic = widgets.BoundedFloatText(
    min=10.0,
    max=60.0,
    step=0.1,
    description='Simulation time :',
    value=10.0,
    disabled=False,
    style={
        'description_width': 'initial'})

button_ready_to_simulation_time_crrid_generic = widgets.Button(
    description='Compute time simulation', layout=widgets.Layout(width='auto'),
    disabled=False, button_style='danger', icon='check-circle')
intprogress_2_crrid_generic = widgets.IntProgress(
    layout=widgets.Layout(width='auto'), value=0, min=0, max=10,
    description='Loading  ', style={'bar_color': '#f84434'},
    orientation='horizontal')
layout_validate_2_crrid_generic = widgets.AppLayout(
    header=None,
    left_sidebar=button_ready_to_simulation_time_crrid_generic,
    center=None,
    right_sidebar=intprogress_2_crrid_generic,
    footer=None)

# -----------------------------------------------------------------------------
# Computation function for front-end
# -----------------------------------------------------------------------------

def computation_1_mid_generic(n, m, s0, value_tau):
    output_compute_analysis_mid_generic.clear_output()

    with output_compute_analysis_mid_generic:

        display(Markdown("<h3>Spectrum distribution</h3>"))
        fig, ax = plt.subplots(figsize=(9, 5))
        ax.set_title("Spectrum distribution")
        ax.set_xlabel(r"$\Re(s)$")
        ax.set_ylabel(r"$\Im(s)$")
        ax.axhline(linewidth=2, color='black', zorder=2)
        ax.axvline(linewidth=2, color='black', zorder=2)
        ax.axvspan(0, 100, alpha=0.5, color='red', zorder=2,
                   label='Unstable system')
        ax.grid()
        line, = ax.plot([], [], 'o', color='steelblue', zorder=3,
                        label='Roots')
        xr, yr, eq = compute_root_spectrum_mid_generic(n, m, s0, value_tau)
        xmax = max(xr)
        plt.xlim([-10, 1])
        line.set_data(xr, yr)
        xmax_round = round(xmax, 2)
        line2, = ax.plot([], [], '*', markersize=20, color='#ff9900', zorder=3,
                         label='Rightmost root (' + str(
                             xmax_round) + ';' + '0' + ')')
        line2.set_data(xmax, 0)
        ax.legend(loc='upper right')
        ax.relim()
        fig.canvas.draw()

        display(Markdown("<h3>Parameters</h3>"))
        print("n = ", slider_n_mid_generic.value, ", m = ",
              slider_m_mid_generic.value, ", s0 = ",
              floatext_s0_mid_generic.value, ", tau = ",
              floattext_tau_mid_generic.value)
        a_coeff_chain = ""
        b_coeff_chain = ""
        i = 0
        for a_coeff in a_coeff_mid_generic:
            a_coeff_chain = a_coeff_chain + "a" + str(i) + " = " + str(
                a_coeff) + ", "
            i = i + 1
        print(a_coeff_chain)
        i = 0
        for b_coeff in b_coeff_mid_generic:
            b_coeff_chain = b_coeff_chain + "b" + str(i) + " = " + str(
                b_coeff) + ", "
            i = i + 1
        print(b_coeff_chain)

        display(Markdown("<h3>Ouput equation</h3>"))
        print("Fp = ", eq)
        display(Markdown("<h3>Factorized integral equation</h3>"))
        factorization_integral_latex_equation_mid_generic = \
            factorization_integral_latex(n, m, s0, value_tau)
        display(Markdown(factorization_integral_latex_equation_mid_generic))
        display(Markdown("<h3>Hypergeometric factorization</h3>"))
        factorization_1f1_latex_equation_mid_generic = factorization_1f1_latex(
            n, m, s0, value_tau)
        display(Markdown(factorization_1f1_latex_equation_mid_generic))

def button_ready_to_compute_clicked_mid_generic(self):
    n_mid_generic = slider_n_mid_generic.value
    m_mid_generic = slider_m_mid_generic.value
    s0_mid_generic = floatext_s0_mid_generic.value
    tau_mid_generic = floattext_tau_mid_generic.value
    computation_1_mid_generic(n_mid_generic, m_mid_generic, s0_mid_generic,
                              tau_mid_generic)

button_ready_to_compute_mid_generic.on_click(
    button_ready_to_compute_clicked_mid_generic)

def computation_2_mid_generic(degn, delay, acoef, bcoef, t_final, n_iter,
                              init_type, init_args):
    output_time_simulation_computation_mid_generic.clear_output()

    with output_time_simulation_computation_mid_generic:
        display(Markdown("<h3>Time simulation plot</h3>"))

        time_initsolution, sol_initsolution = initialsolution(degn, delay,
                                                              acoef, bcoef,
                                                              t_final, n_iter,
                                                              init_type,
                                                              init_args)
        fig, ax = plt.subplots(figsize=(9, 5))
        plt.plot(time_initsolution, sol_initsolution[0, :], '#f84434')
        ax.grid()
        plt.xlabel(r"$t$ (in second)", fontsize=12)
        plt.ylabel(r"$y(t)$")
        plt.title("Time simulation")

def button_ready_to_simulation_time_clicked_mid_generic(self):
    degn = slider_n_mid_generic.value
    delay = floattext_tau_mid_generic.value
    acoef = a_coeff_mid_generic
    bcoef = b_coeff_mid_generic
    t_final = floattext_simulation_time_mid_generic.value
    n_iter = 100000
    init_type = dropdown_mid_generic.value

    if init_type == 'Constant':
        init_args = [constant_constant_mid_generic.value]

    elif init_type == 'Polynomial':
        init_args = [int(polynomial_degree_mid_generic.value)]

        for i in range(int(polynomial_degree_mid_generic.value) + 1):
            init_args.append(
                items_floattext_polynomial_coef_mid_generic[i].value)

    elif init_type == 'Exponential':
        init_args = [exponential_gain_mid_generic.value,
                     exponential_exponant_mid_generic.value]

    elif init_type == 'Trigonometric':
        init_args = [trigonometric_amplitude_mid_generic.value,
                     trigonometric_frequency_mid_generic.value,
                     trigonometric_phase_mid_generic.value]

    computation_2_mid_generic(degn, delay, acoef, bcoef, t_final, n_iter,
                              init_type, init_args)

button_ready_to_simulation_time_mid_generic.on_click(
    button_ready_to_simulation_time_clicked_mid_generic)

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

def computation_1_mid_co(n, m, a_coeff_mid_co):
    global liste_s0_sensitivity_max
    global liste_s0_sensitivity_min
    global liste_tau_sensitivity_max
    global liste_tau_sensitivity_min

    output_admissibilite_computation_mid_co.clear_output()

    with output_admissibilite_computation_mid_co:
        s0range, taurange, polyadm, s, tau, q, avalue, sysderivatif, alpha, \
            a, sol = compute_admissibilite(n, m, a_coeff_mid_co)

        s0range = np.arange(-16, 0, 0.01)
        taurange = np.arange(0, 10, 0.01)

        func = sp.lambdify([s, tau], polyadm)

        display(Markdown("<h3>Admissibility plot</h3>"))

        fig, ax = plt.subplots(figsize=(9, 5))
        x, y = np.meshgrid(s0range, taurange)
        z = func(x, y)

        cs = ax.contour(x, y, z, [0], colors='#ff9c28')
        ax.grid()

        plt.xlabel(r"$s_0$")
        plt.ylabel(r"$\tau$")
        plt.title("Admissibility plot")

        p = cs.collections[0].get_paths()[0]
        v = p.vertices
        liste_s0_sensitivity = v[:, 0].copy()
        liste_tau_sensitivity = v[:, 1].copy()

        liste_s0_sensitivity_max = round(np.max(liste_s0_sensitivity), 5)
        liste_s0_sensitivity_min = round(np.min(liste_s0_sensitivity), 5)
        liste_tau_sensitivity_max = round(np.max(liste_tau_sensitivity), 5)
        liste_tau_sensitivity_min = round(np.min(liste_tau_sensitivity), 5)

        display(Markdown("<h3>s0 and tau limits</h3>"))
        display(Markdown(
            r"\$" + str(liste_s0_sensitivity_min) + "\\leq s_0 \\leq " + str(
                liste_s0_sensitivity_max) + "$"))
        display(Markdown(
            r"\$" + str(liste_tau_sensitivity_min) + "\\leq tau \\leq " + str(
                liste_tau_sensitivity_max) + "$"))

        floattext_s0_tau_mid_co.max = liste_tau_sensitivity_max
        floattext_s0_tau_mid_co.min = liste_tau_sensitivity_min
        floattext_s0_tau_mid_co.step = 0.01
        floattext_s0_tau_mid_co.value = 0.73
        floattext_s0_tau_mid_co.description = 'tau : '
        floattext_s0_tau_mid_co.disabled = False

def computation_2_mid_co(n, m, user_choice, user_value):
    output_compute_analysis_mid_co.clear_output()

    global a_coeff_mid_co
    global b_coeff_mid_co
    global computedtau
    global computeds0

    with output_compute_analysis_mid_co:

        display(Markdown("<h3>Spectrum distribution</h3>"))

        s0range, taurange, polyadm, s, tau, q, avalue, sysderivatif, alpha, \
            a, sol = compute_admissibilite(
                n, m, a_coeff_mid_co)

        finaleq = sysderivatif[-1].subs(
            {alph: alphacoef for alph, alphacoef in zip(alpha, sol)})
        finaleq = finaleq.subs({asymb: aval for asymb, aval in zip(a, avalue)})

        if user_choice == 'tau':

            value_tau = user_value
            sols0 = finaleq.subs({tau: value_tau})
            sols0 = sp.solve(sols0)
            computeds0 = sols0[1]
            alpha_num = sol.subs(
                {asymb: aval for asymb, aval in zip(a, avalue)})
            alpha_num = alpha_num.subs({s: computeds0})
            alpha_num = alpha_num.subs({tau: value_tau})
            alpha_num_eval = [i.evalf() for i in alpha_num]
            b_coeff_mid_co = alpha_num_eval.copy()
            xr, yr, eq = compute_root_spectrum_mid_co(n, m, value_tau)

        elif user_choice == 's0':

            value_s0 = user_value
            soltau = finaleq.subs({s: value_s0})
            soltau = sp.solve(soltau)
            computedtau = soltau[0]
            alpha_num = sol.subs(
                {asymb: aval for asymb, aval in zip(a, avalue)})
            alpha_num = alpha_num.subs({tau: computedtau})
            alpha_num = alpha_num.subs({s: value_s0})
            alpha_num_eval = [i.evalf() for i in alpha_num]
            b_coeff_mid_co = alpha_num_eval.copy()
            xr, yr, eq = compute_root_spectrum_mid_co(n, m, computedtau)

        fig, ax = plt.subplots(figsize=(9, 5))
        ax.set_title("Spectrum distribution")
        ax.set_xlabel(r"$\Re(s)$")
        ax.set_ylabel(r"$\Im(s)$")
        ax.axhline(linewidth=2, color='black', zorder=2)
        ax.axvline(linewidth=2, color='black', zorder=2)
        ax.axvspan(0, 100, alpha=0.5, color='red', zorder=2,
                   label='Unstable system')
        ax.grid()
        line, = ax.plot([], [], 'o', color='steelblue', zorder=3,
                        label='Roots')
        xmax = max(xr)
        plt.xlim([-10, 1])
        line.set_data(xr, yr)
        xmax_round = round(xmax, 2)
        line2, = ax.plot([], [], '*', markersize=20, color='#ff9900', zorder=3,
                         label='Rightmost root (' + str(
                             xmax_round) + ';' + '0' + ')')
        line2.set_data(xmax, 0)
        ax.legend(loc='upper right')
        ax.relim()
        fig.canvas.draw()

        display(Markdown("<h3>Parameters</h3>"))

        if user_choice == 'tau':
            print("n = ", slider_n_mid_co.value, ", m = ",
                  slider_m_mid_co.value, ", s0 = ", computeds0, ", tau = ",
                  user_value)
        elif user_choice == 's0':
            print("n = ", slider_n_mid_co.value, ", m = ",
                  slider_m_mid_co.value, ", s0 = ", user_value, ", tau = ",
                  computedtau)

        a_coeff_chain = ""
        b_coeff_chain = ""
        i = 0
        for a_coeff in a_coeff_mid_co:
            a_coeff_chain = a_coeff_chain + "a" + str(i) + " = " + str(
                a_coeff) + ", "
            i = i + 1
        print(a_coeff_chain)
        i = 0
        for b_coeff in b_coeff_mid_co:
            b_coeff_chain = b_coeff_chain + "b" + str(i) + " = " + str(
                b_coeff) + ", "
            i = i + 1
        print(b_coeff_chain)

        display(Markdown("<h3>Ouput equation</h3>"))
        print("Fp = ", eq)

def computation_3_mid_co(n, m, user_choice, user_value):
    output_sensibilite_computation_mid_co.clear_output()

    global a_coeff_mid_co
    global b_coeff_mid_co
    global computedtau

    with output_sensibilite_computation_mid_co:

        s0range, taurange, polyadm, s, tau, q, avalue, sysderivatif, alpha, \
            a, sol = compute_admissibilite(n, m, a_coeff_mid_co)

        if user_choice == 'tau':

            value_tau = user_value

        elif user_choice == 's0':

            if type(computedtau).__name__ == "Add":
                value_tau = computedtau.as_real_imag()[0].evalf(10)
            elif type(computedtau).__name__ == "Float":
                value_tau = float(computedtau.evalf(10))
            value_tau = round(value_tau, 5)

        tau_sens, s0_sens, colormapper = compute_sensibilite(value_tau, q, m,
                                                             s, sysderivatif,
                                                             alpha, a, tau)

        display(Markdown("<h3>Sensitivity plot</h3>"))
        fig, ax = plt.subplots(figsize=(9, 5))
        alpha_sens = b_coeff_mid_co.copy()
        for i in range(len(tau_sens)):
            xroot, yroot, func = getroots(1, q, tau_sens[i], avalue,
                                          alpha_sens, [-5, 5], [-5, 5], s, a,
                                          alpha, tau)
            ax.scatter(xroot, yroot, c=len(xroot) * [
                colormapper.to_rgba(float(tau_sens[i]))])

        ax.grid()
        ax.set_axisbelow(True)
        fig.colorbar(colormapper)

        ax.set_xlabel(r"$Re(s)$")
        ax.set_ylabel(r"$Im(s)$")
        ax.set_title(
            r"Sensitivity plot, $\tau \in $" +
            f"[{tau_sens[0]}, {tau_sens[-1]}]")
        tau_sens.sort()

def computation_4_mid_co(degn, delay, acoef, bcoef, t_final, n_iter, init_type,
                         init_args):
    output_time_simulation_computation_mid_co.clear_output()

    with output_time_simulation_computation_mid_co:
        display(Markdown("<h3>Time simulation plot</h3>"))
        time_initsolution, sol_initsolution = initialsolution(degn, delay,
                                                              acoef, bcoef,
                                                              t_final, n_iter,
                                                              init_type,
                                                              init_args)
        fig, ax = plt.subplots(figsize=(9, 5))
        plt.plot(time_initsolution, sol_initsolution[0, :], '#f84434')
        ax.grid()
        plt.xlabel(r"$t$ (in second)", fontsize=12)
        plt.ylabel(r"$y(t)$")
        plt.title("Time simulation")

def button_confirm_system_coeff_clicked_mid_co(self):
    global a_coeff_mid_co

    n_mid_co = slider_n_mid_co.value
    m_mid_co = slider_m_mid_co.value
    a_coeff_mid_co = []

    for i in range(slider_n_mid_co.value):
        a_coeff_mid_co.append(items_inttext_a_mid_co[i].value)

    computation_1_mid_co(n_mid_co, m_mid_co, a_coeff_mid_co)

def button_confirm_degrees_clicked_mid_co(self):
    for k in range(len(items_inttext_a_mid_co)):
        items_inttext_a_mid_co[k].value = 0
        items_inttext_a_mid_co[k].disabled = True
        items_inttext_a_mid_co[k].description = ''

    for i in range(slider_n_mid_co.value):
        items_inttext_a_mid_co[i].value = 1
        items_inttext_a_mid_co[i].disabled = False
        items_inttext_a_mid_co[i].description = "a" + str(i)

    items_inttext_a_mid_co[slider_n_mid_co.value].value = 1
    items_inttext_a_mid_co[slider_n_mid_co.value].disabled = True
    items_inttext_a_mid_co[slider_n_mid_co.value].description = "a" + str(
        slider_n_mid_co.value)

def button_ready_to_compute_clicked_mid_co(self):
    global a_coeff_mid_co

    n_mid_co = slider_n_mid_co.value
    m_mid_co = slider_m_mid_co.value
    user_choice = togglebuttons_mid_co.value
    user_value = floattext_s0_tau_mid_co.value

    computation_2_mid_co(n_mid_co, m_mid_co, user_choice, user_value)

def button_ready_to_sensitivity_clicked_mid_co(self):
    n_mid_co = slider_n_mid_co.value
    m_mid_co = slider_m_mid_co.value

    user_choice = togglebuttons_mid_co.value

    user_value = floattext_s0_tau_mid_co.value

    computation_3_mid_co(n_mid_co, m_mid_co, user_choice, user_value)

def button_ready_to_simulation_time_clicked_mid_co(self):
    degn = slider_n_mid_co.value

    if togglebuttons_mid_co.value == 's0':
        delay = computedtau

    elif togglebuttons_mid_co.value == 'tau':
        delay = floattext_s0_tau_mid_co.value

    acoef = a_coeff_mid_co
    bcoef = b_coeff_mid_co
    t_final = floattext_simulation_time_mid_co.value
    n_iter = 100000
    init_type = dropdown_mid_co.value

    if init_type == 'Constant':
        init_args = [constant_constant_mid_co.value]

    elif init_type == 'Polynomial':
        init_args = [int(polynomial_degree_mid_co.value)]

        for i in range(int(polynomial_degree_mid_co.value) + 1):
            init_args.append(items_floattext_polynomial_coef_mid_co[i].value)

    elif init_type == 'Exponential':
        init_args = [exponential_gain_mid_co.value,
                     exponential_exponant_mid_co.value]

    elif init_type == 'Trigonometric':
        init_args = [trigonometric_amplitude_mid_co.value,
                     trigonometric_frequency_mid_co.value,
                     trigonometric_phase_mid_co.value]

    computation_4_mid_co(degn, delay, acoef, bcoef, t_final, n_iter, init_type,
                         init_args)

button_ready_to_simulation_time_mid_co.on_click(
    button_ready_to_simulation_time_clicked_mid_co)

button_ready_to_compute_mid_co.on_click(button_ready_to_compute_clicked_mid_co)

button_ready_to_sensitivity_mid_co.on_click(
    button_ready_to_sensitivity_clicked_mid_co)

button_confirm_degrees_mid_co.on_click(button_confirm_degrees_clicked_mid_co)

button_confirm_system_coeff_mid_co.on_click(
    button_confirm_system_coeff_clicked_mid_co)

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

def computation_1_crrid_generic(n, m, s0, d, value_tau):
    output_compute_analysis_mid_crrid_generic.clear_output()

    with output_compute_analysis_mid_crrid_generic:

        display(Markdown("<h3>Spectrum distribution</h3>"))
        fig, ax = plt.subplots(figsize=(9, 5))
        ax.set_title("Spectrum distribution with equidistributed rightmost "
                     "spectral values")
        ax.set_xlabel(r"Re$(s)$")
        ax.set_ylabel(r"Im$(s)$")
        ax.axhline(linewidth=2, color='black', zorder=2)
        ax.axvline(linewidth=2, color='black', zorder=2)
        ax.axvspan(0, 100, alpha=0.5, color='red', zorder=2,
                   label='Unstable system')
        ax.grid()
        line, = ax.plot([], [], 'o', color='steelblue', zorder=3,
                        label='Roots')
        line2, = ax.plot([], [], '*', markersize=15, color='green', zorder=3,
                         label='Rightmost root')
        xr, yr, eq = compute_crrid_generic_root(n, m, s0, d, value_tau)
        plt.xlim([-10, 1])
        line.set_data(xr, yr)
        xmax2 = max(xr)
        line2.set_data(xmax2, 0)
        ax.legend(loc='upper right')
        ax.relim()
        fig.canvas.draw()

        display(Markdown("<h3>Parameters</h3>"))
        print("n = ", slider_n_crrid_generic.value, ", m = ",
              slider_m_crrid_generic.value, ", s0 = ",
              floattext_s0_crrid_generic.value, ", tau = ",
              floattext_tau_crrid_generic.value)
        a_coeff_chain = ""
        b_coeff_chain = ""
        i = 0
        for a_coeff in a_coeff_crrid_generic:
            a_coeff_chain = a_coeff_chain + "a" + str(i) + " = " + str(
                a_coeff) + ", "
            i = i + 1
        print(a_coeff_chain)
        i = 0
        for b_coeff in b_coeff_crrid_generic:
            b_coeff_chain = b_coeff_chain + "b" + str(i) + " = " + str(
                b_coeff) + ", "
            i = i + 1
        print(b_coeff_chain)

        display(Markdown("<h3>Ouput equation</h3>"))
        print("Fp = ", eq)

def button_ready_to_compute_clicked_mid_crrid_generic(self):
    n_crrid_generic = slider_n_crrid_generic.value
    m_crrid_generic = slider_m_crrid_generic.value
    s0_crrid_generic = floattext_s0_crrid_generic.value
    d_crrid_generic = slider_d_crrid_generic.value
    tau_crrid_generic = floattext_tau_crrid_generic.value

    computation_1_crrid_generic(n_crrid_generic, m_crrid_generic,
                                s0_crrid_generic, d_crrid_generic,
                                tau_crrid_generic)

button_ready_to_compute_mid_crrid_generic.on_click(
    button_ready_to_compute_clicked_mid_crrid_generic)

def computation_2_crrid_generic(degn, delay, acoef, bcoef, t_final, n_iter,
                                init_type, init_args):
    output_time_simulation_computation_1_crrid_generic.clear_output()

    with output_time_simulation_computation_1_crrid_generic:
        display(Markdown("<h3>Time simulation plot</h3>"))
        time_initsolution, sol_initsolution = initialsolution(degn, delay,
                                                              acoef, bcoef,
                                                              t_final, n_iter,
                                                              init_type,
                                                              init_args)
        fig, ax = plt.subplots(figsize=(9, 5))
        plt.plot(time_initsolution, sol_initsolution[0, :], '#f84434')
        ax.grid()
        plt.xlabel(r"$t$ (in second)", fontsize=12)
        plt.ylabel(r"$y(t)$")
        plt.title("Time simulation")

def button_ready_to_simulation_time_clicked_crrid_generic(self):
    degn = slider_n_crrid_generic.value
    delay = floattext_tau_crrid_generic.value
    acoef = a_coeff_crrid_generic
    bcoef = b_coeff_crrid_generic
    t_final = floattext_simulation_time_crrid_generic.value
    n_iter = 100000
    init_type = dropdown_crrid_generic.value

    if init_type == 'Constant':
        init_args = [constant_constant_crrid_generic.value]

    elif init_type == 'Polynomial':
        init_args = [int(polynomial_degree_crrid_generic.value)]

        for i in range(int(polynomial_degree_crrid_generic.value) + 1):
            init_args.append(
                items_floattext_polynomial_coef_crrid_generic[i].value)

    elif init_type == 'Exponential':
        init_args = [exponential_gain_crrid_generic.value,
                     exponential_exponant_crrid_generic.value]

    elif init_type == 'Trigonometric':
        init_args = [trigonometric_amplitude_crrid_generic.value,
                     trigonometric_frequency_crrid_generic.value,
                     trigonometric_phase_crrid_generic.value]

    computation_2_crrid_generic(degn, delay, acoef, bcoef, t_final, n_iter,
                                init_type, init_args)

button_ready_to_simulation_time_crrid_generic.on_click(
    button_ready_to_simulation_time_clicked_crrid_generic)

# -----------------------------------------------------------------------------
# User Interface output
# -----------------------------------------------------------------------------

title_analysis = widgets.HTML(
    "<h2 class = 'text-center'><font color='#ff9c28'>"
    "Analysis</font><size='10'>")
title_time_simulation = widgets.HTML(
    "<h2 class = 'text-center'><font color='#f84434'>"
    "Time simulation</font><size='10'>")
title_sensitivity = widgets.HTML(
    "<h2 class = 'text-center'><font color='#10acd8'>"
    "Sensitivity</font><size='10'>")

description_label_mid_generic = widgets.Label(
    'Insert degree of polynomial n, degree of delay polynomial m, rightmost '
    'root s0 and delay tau, then push the button to compute : ')
separator_label = widgets.Label(
    '_________________________________________________________________________'
    '___________________________________________________________')
time_simulation_label = widgets.Label(
    'Choose initial condition and insert time simulation t :')

description_label_mid_co = widgets.Label(
    'Insert degree of polynomial n and degree of delay polynomial m, '
    'then push the button to confirm : ')
description_label_2_mid_co = widgets.Label(
    'Insert values of system coefficients, then push the button to confirm '
    'and draw the admissibility plot : ')
description_label_3_mid_co = widgets.Label(
    'Select between rightmost root s0 or delay tau using the admissibility '
    'plot, then push the button to compute : ')
description_label_crrid_generic = widgets.VBox([widgets.Label(
    'Insert degree of polynomial n, degree of delay polynomial m, rightmost '
    'root s0, step d between each value of s0 and delay tau, then push the '
    'button to compute :')],
    layout=widgets.Layout(
        width='100%',
        display='flex',
        align_items='center'))

box_input_mid_generic = widgets.HBox(
    [slider_n_mid_generic, slider_m_mid_generic, floatext_s0_mid_generic,
     floattext_tau_mid_generic])
box_input_mid_co = widgets.HBox(
    [slider_n_mid_co, slider_m_mid_co, button_confirm_degrees_mid_co])
box_input_2_mid_co = widgets.HBox([inttext_a_1_mid_co],
                                  layout=widgets.Layout(width='auto'))
box_input_3_mid_co = widgets.HBox(
    [inttext_a_2_mid_co, button_confirm_system_coeff_mid_co],
    layout=widgets.Layout(width='auto'))
box_input_4_mid_co = widgets.HBox(
    [togglebuttons_mid_co, floattext_s0_tau_mid_co],
    layout=widgets.Layout(width='auto'))
box_input_1_crrid_generic = widgets.HBox(
    [slider_n_crrid_generic, slider_m_crrid_generic, slider_d_crrid_generic])
box_input_2_crrid_generic = widgets.HBox(
    [floattext_s0_crrid_generic, floattext_tau_crrid_generic])

accordion_1_mid_generic = widgets.Accordion(
    children=[output_compute_analysis_mid_generic],
    titles=('Output analysis', ''))
accordion_2_mid_generic = widgets.Accordion(
    children=[output_time_simulation_computation_mid_generic],
    titles=('Output time simulation', ''))
accordion_1_mid_generic.selected_index = 0
accordion_2_mid_generic.selected_index = 0

accordion_1_mid_co = widgets.Accordion(
    children=[output_admissibilite_computation_mid_co],
    titles=('Output admissibility', ''))
accordion_1_mid_co.selected_index = 0
accordion_2_mid_co = widgets.Accordion(
    children=[output_compute_analysis_mid_co], titles=('Output analysis', ''))
accordion_2_mid_co.selected_index = 0
accordion_3_mid_co = widgets.Accordion(
    children=[output_sensibilite_computation_mid_co],
    titles=('Output sensitivity', ''))
accordion_3_mid_co.selected_index = 0
accordion_4_mid_co = widgets.Accordion(
    children=[output_time_simulation_computation_mid_co],
    titles=('Output time simulation', ''))
accordion_4_mid_co.selected_index = 0

accordion_1_crrid_generic = widgets.Accordion(
    children=[output_compute_analysis_mid_crrid_generic],
    titles=('Output analysis', ''))
accordion_1_crrid_generic.selected_index = 0
accordion_2_crrid_generic = widgets.Accordion(
    children=[output_time_simulation_computation_1_crrid_generic],
    titles=('Output time simulation', ''))
accordion_2_crrid_generic.selected_index = 0

box_mid_generic = widgets.VBox(
    [text_error_mid_generic, title_analysis, description_label_mid_generic,
     box_input_mid_generic, layout_validate_1_mid_generic,
     accordion_1_mid_generic, separator_label, title_time_simulation,
     time_simulation_label, stacked_simulation_time_mid_generic,
     floattext_simulation_time_mid_generic, layout_validate_2_mid_generic,
     accordion_2_mid_generic])
box_mid_co = widgets.VBox(
    [text_error_mid_co, title_analysis, description_label_mid_co,
     box_input_mid_co, description_label_2_mid_co, box_input_2_mid_co,
     box_input_3_mid_co, accordion_1_mid_co, description_label_3_mid_co,
     box_input_4_mid_co, layout_validate_1_mid_co, accordion_2_mid_co,
     separator_label, title_sensitivity, layout_validate_3_mid_co,
     accordion_3_mid_co, separator_label, title_time_simulation,
     time_simulation_label, stacked_simulation_time_mid_co,
     floattext_simulation_time_mid_co, layout_validate_2_mid_co,
     accordion_4_mid_co])
box_crrid_generic = widgets.VBox(
    [text_error_crrid_generic, title_analysis, description_label_crrid_generic,
     box_input_1_crrid_generic, box_input_2_crrid_generic,
     layout_validate_1_mid_crrid_generic, accordion_1_crrid_generic,
     separator_label, title_time_simulation, time_simulation_label,
     stacked_simulation_time_crrid_generic,
     floattext_simulation_time_crrid_generic, layout_validate_2_crrid_generic,
     accordion_2_crrid_generic])

tab = widgets.Tab()
tab.children = [box_mid_generic, box_mid_co, box_crrid_generic]
tab.titles = ('MID Generic', 'MID Control oriented', 'CRRID Generic')

file = open("images/header_p3d_online.png", "rb")
image = file.read()
header_image = widgets.Image(value=image, format='png', width=975, height=25)

dashboard = widgets.VBox([header_image, tab])
display(dashboard)
```

<style>div.output_scroll { height: 55em; }</style>

    VBox(children=(Image(value=b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x07\x80\x00\x00\x01,\x08\x02\x00\x00\…

```python

```


# Generalized Constrained Spherical Deconvolution

Dmipy's Constrained Spherical Deconvolution (CSD) implementation completely generalizes the Multi-Shell-Multi-Tissue (MSMT) CSD implementation of *(Jeurissen et al. 2014)* to
- Any number of compartments,
- Any acquisition scheme parameters,
- Being able to use voxel-varying convolution kernels (e.g. as estimated by spherical mean models).

## Short Summary of CSD:
Constrained Spherical Deconvolution (CSD) *(Tournier et al. 2007)* is one of the most well-known ways to estimate Fiber Orientation Distributions (FODs) that describe the orientation of the white matter tissue. In short, CSD's formulation states that any composition of oriented white matter (dispersion/crossings) can be described as the spherical convolution of a *positive* probability density on the sphere and a convolution kernel $K$ that describes one parallel axon bundle. With some abuse of notation:

$$
\begin{align}
\begin{aligned}
 E_{\textrm{CSD}}= \overbrace{\operatorname{FOD}(\textbf{c})}^{\textrm{Fiber Distribution}}\,*_{\mathbb{S}^2}\,\overbrace{K(\cdot)}^{\textrm{Convolution Kernel}}\quad\textrm{subject to}\quad \operatorname{FOD}(\textbf{c}) > 0.
\end{aligned}
\end{align}$$

Here, the FOD is described in terms of a truncated even spherical harmonics series $FOD=\sum_{l=0}^{lmax}\sum_{m=-l}^l\textbf{c}_{lm}Y_{lm}$, with $l$ and $m$ describing the order and moment of the spherical harmonic *(Descoteaux et al. 2006)*. Furthermore, $*_{\mathbb{S}^2}$ describes a spherical convolution on the $\mathbb{S}^2$ sphere, and $K$ describes the *rotational* harmonics describing a single axon bundle. The kernel $K$ is typically estimated from the data *(Tournier et al. 2007, Tax et al. 2014)*, and CSD is therefore considered a *model-free* approach.

## Multi-Shell Single-Compartment CSD
When using a single convolution kernel (voxel-varying or not), Dmipy's uses the classical (faster) optimizer proposed by *(Tournier et al. 2007)* to estimate the spherical harmonics of the FOD. The single- and multi-shell implementations of CSD estimate a positive and smooth FOD as follows:

\begin{equation}
\textbf{f}^* = argmin_f \overbrace{\|\textbf{Af} - \textbf{b}\|^2}^{\textrm{Data Term}} + \lambda_{\textrm{pos}}\overbrace{\|\textbf{Lf}\|^2}^{\textrm{Positivity}} + \lambda_{\textrm{lb}}\overbrace{\|\textbf{Rf}\|^2}^{\textrm{smoothness}},
\end{equation}

where $\textbf{f}$ is SH coefficients of length $N_c$. $\textbf{A}$ of size $N_{data}\times N_c$ is observation matrix that maps spherical harmonics coefficients to signal values, $\textbf{b}$ is array of measured DWIs, and $\textrm{L}$ maps $\textbf{f}$ on the sphere and penalizes negative values, and $\textbf{R}$ is the Laplace-Beltrami operator following *(Descoteaux et al. 2006)*.

The observation matrix $\textbf{A}$ is - *for each shell separately* - formed by the product of a spherical-harmonics transform (SHT) matrix with a diagonal matrix with prepared rotational harmonics of the convolution kernel, meaning they are multiplied by $\sqrt{(4 * \pi) / (2 * l + 1)}$.

The positivity matrix $\textbf{L}$ is a spherical harmonics transform (SHT) matrix that maps spherical harmonics coefficients to real values on the sphere, for which rows mapping to positive FOD values are zeroed out.

The smoothness Laplace-Beltrami matrix $\textbf{R}$ is a diagonal matrix with values $l(l+1)$.

The minimum of this equation is found by taking the derivative w.r.t. $\textbf{f}$ and setting is to zero

\begin{equation}
0 = 2\textbf{A}^T(\textbf{Af}^* - \textbf{b}) + 2\lambda_{\textrm{pos}} \textbf{L}^T\textbf{L}\textbf{f}^* + 2\lambda_{\textrm{lb}} \textbf{R}^T\textbf{R}\textbf{f}^*
\end{equation}

\begin{equation}
\textbf{A}^T\textbf{b} = (\textbf{A}^T\textbf{A} + \lambda_{\textrm{pos}} \textbf{L}^T\textbf{L} + \lambda_{\textrm{lb}} \textbf{R}^T\textbf{R})\textbf{f}^*
\end{equation}

\begin{equation}
\textbf{f}^* = (\textbf{A}^T\textbf{A} + \lambda_{\textrm{pos}} \textbf{L}^T\textbf{L} + \lambda_{\textrm{lb}} \textbf{R}^T\textbf{R})^{-1}\textbf{A}^T\textbf{b}
\end{equation}

In the CSD optimization following *(Tournier et al. 2007)*, this equation is solved iteratively by starting with a lower spherical harmonics order FOD (typically 4), and and updating the positivity constraint matrix until convergence.

## Multi-Shell Multi-Compartment CSD

The multi-compartment implementation of CSD (MC-CSD) follows the work of *(Jeurissen et al. 2014)* and is exactly the same as the single-compartment one, but has some constraints:
- When estimating volume fractions, MC-CSD can only estimate the FODs for one anisotropic kernel and one or more isotropic kernels.
- An additional constraint must be added that the volume fractions add up to one and are positive.

For every isotropic compartment, one extra line on $\textbf{A}$ is added that maps the isotropic signal attenuation to the data positions. Similarly, one extra coefficient is added at the end of $\textbf{f}$, which represents the isotropic compartment's only $l=0, m=0$ coefficient. The volume fractions of each compartment are defined by their respects $l=0, m=0$ coefficients, multiplied by the sphere's jacobian $2 * \sqrt{\pi}$. Using cvxpy, the optimization is cast as

\begin{equation}
\textbf{f}^* = argmin_f \overbrace{\|\textbf{Af} - \textbf{b}\|^2}^{\textrm{Data Term}} + \lambda_{\textrm{lb}}\overbrace{\|\textbf{Rf}\|^2}^{\textrm{smoothness}},\quad\textrm{subject to}\quad \sum vf=1, vf>0, FOD>0
\end{equation}

## Voxel-Varying Convolution Kernels
Just like in previous models, it is possible to set voxel-varying initial conditions when fitting models. This can be done by hand, or particularly, a fitted `MultiCompartmentSphericalMeanModel` can call the `MultiCompartmentSphericalHarmonicsModel` with all its parameters fixed voxel-wise to the spherical mean fitted parameters. See e.g. the [Spherical Mean Technique](http://nbviewer.jupyter.org/github/AthenaEPI/dmipy/blob/master/examples/example_spherical_mean_technique.ipynb) or [Multi-Compartment Microscopic Diffusion Imaging examples](http://nbviewer.jupyter.org/github/AthenaEPI/mipy/blob/master/examples/example_multi_compartment_spherical_mean_technique.ipynb) examples.

## References
- Tournier, J-Donald, Fernando Calamante, and Alan Connelly. "Robust determination of the fibre orientation distribution in diffusion MRI: non-negativity constrained super-resolved spherical deconvolution." Neuroimage 35.4 (2007): 1459-1472.
- Descoteaux, Maxime, et al. "Regularized, fast, and robust analytical Q‐ball imaging." Magnetic resonance in medicine 58.3 (2007): 497-510.
- Tax, Chantal MW, et al. "Recursive calibration of the fiber response function for spherical deconvolution of diffusion MRI data." Neuroimage 86 (2014): 67-80.
- Jeurissen, Ben, et al. "Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data." NeuroImage 103 (2014): 411-426.


# Model project: Cournot competition

In the following project, we searh to model how two competing firms determine the optimal amount of a homogenous good to produce in Cournot competition.

We are assuming the following points throughout the assignment:
- There are two firms (1 and 2), who produce the same good (homogenous) (in the extension, we observe three firms)
- No collusion
- The two firms simultaneously choose the quaintity to produce ($q_1$ and $q_2$).
- The total amount produced is $Q = q_1 + q_2$
- The market price is decreasing in the total quantity: $P(Q)=a-Q$
- Both firms have the same marginal cost $c$, where $0 \leq c<a$

```python
import numpy as np
from numpy import array

from scipy import linalg
from scipy import optimize
from scipy import interpolate
from scipy import optimize,arange
import scipy as sp

import ipywidgets as widgets
import sympy as sm

import matplotlib.pyplot as plt
%matplotlib inline
```

# Introduction to the economic model

We look for a Nash Equilibrium, where both firms choose their best response, as a reaction function to the other firms actions.

The profit functions for the respective firms are:

Profit for firm 1: $\pi_1(q_1,q_2) = q_1(P(q_1+q_2)  - c) $

Profit for firm 2: $\pi_2(q_1,q_2) = q_2(P(q_1+q_2) - c)$

The price function and the marginal cost are included, and we can thereby rewrite:

$\pi_1(q_1,q_2) = (a - q_1 - q_2 - c) * q_1$

$\pi_2(q_1,q_2) = (a - q_1 - q_2 - c) * q_2$

Since both firms want to maximize profit, we calculate the best response function, as the first order condition of the profit functions, where we solve for the quantity.

### $\frac{\partial \pi_1}{\partial q_1} = 0$ <=> $ q_1(q_2) = \frac{a-c-q_2}{2}$
### $\frac{\partial \pi_2}{\partial q_2} = 0$ <=> $ q_2(q_1) = \frac{a-c-q_1}{2}$

Given symmetry, we can now solve for the equilibirum quantities:

### $q_1^* = \frac{a-c-\frac{(a-c-q_1^*)}{2}}{2} <=> q_1^* = \frac{a-c}{3} = q_2^*$

# Defining the model

We firstly define the base of the model, which consists of the inverse demand function, the cost function, the profit function for firm 1 and 2, respectively.

```python
def p(q1,q2,a):
    demand = a - q1 - q2
    return demand

def cost_func(q,c):
    cost = q * c
    return cost

#Profit function for firm 1
def pi_1(q1,q2,a,c):
    profit1 = p(q1,q2,a) * q1 - cost_func(q1,c)
    return profit1

#Profit function for firm 2
def pi_2(q1,q2,a,c):
    profit2 = p(q1,q2,a) * q2 - cost_func(q2,c)
    return profit2
```

The firms have symmetrical best response function, as seen before, given that they are solving the same optimization problem, with identical cost and demand.

```python
# a: the lowest value
q0 = [0]

# b: defining the best responses
def BR_f1(q2,a,c):
    optimal_Q1 = optimize.minimize(lambda q0: -pi_1(q0,q2,a,c), q0).x[0]
    return optimal_Q1

def BR_f2(q1,a,c):
    optimal_Q2 = optimize.minimize(lambda q0: -pi_2(q1,q0,a,c), q0).x[0]
    return optimal_Q2
```

```python
def conditions(q,param):
    u = q[0] - BR_f1(q[1],param[0],param[1])
    y = q[1] - BR_f2(q[0],param[0],param[1])
    return [u,y]
```

```python
inital_values = [1,1]
param = [11,2]
```

```python
solver = optimize.fsolve(conditions,inital_values, args = (param))
print(f'the optimal parameter values are: {solver}')
```

    the optimal parameter values are: [3. 3.]

# Plotting

```python
# a: defining production levels for both firms.
production_level_f2 = np.arange(0,10,0.1)
production_level_f1 = []

# b: creating figure
for q2 in production_level_f2:
    q1 = round(BR_f1(q2,10,0),3)
    production_level_f1.append(q1)

plt.title("Best response for firm 1 (blue) and production level\n for firm 2 (orange), given production level for firm 2")
plt.ylabel("$q_1$")
plt.xlabel("$q_2$")
plt.plot(production_level_f2, production_level_f1)
plt.plot(production_level_f2, production_level_f2)
```

```python
costs = np.arange(0,5,0.1)
production_level_f1 = np.arange(0,5,0.1)
production_level_f2 = np.arange(0,5,0.1)

x = []
y = []

for q2 in production_level_f2:
    q1 = round(BR_f1(q2,10,0),3)
    x.append(q1)

for c, q1 in zip(costs,prod1):
    q2 = round(BR_f2(q1,10,c),3)
    y.append(q2)
```

```python
plt.title("Best response for firms with marginal cost $c = 0$ (blue) and for firms\nwith increasing marginal cost (orange) given produciton level of the other firm")
plt.ylabel("$BR(q_i)$")
plt.xlabel("$q_i$")
plt.plot(costs, x)
plt.plot(costs, y)
```

```python
# a: defining production levels
production_level_f1 = np.arange(0,5,0.1)
production_level_f2 = np.arange(0,5,0.1)

# b:
def f(marginal_cost_f1,marginal_cost_f2):

    x = []
    y = []

    for q2 in production_level_f2:
        q1 = round(BR_f1(q2,10,marginal_cost_f1),3)
        x.append(q1)

    for q1 in production_level_f1:
        q2 = round(BR_f2(q1,10,marginal_cost_f2),3)
        y.append(q2)

    plt.title("Best response for firm 1 with marginal cost $c_1$ (blue) and best response for\nfirm 2 with marginal cost $c_2$ (orange) given produciton level of the other firm")
    plt.ylabel("$BR(q_i)$")
    plt.xlabel("$q_i$")
    plt.plot(production_level_f2,x)
    plt.plot(production_level_f1,y)

widgets.interact(f,
    marginal_cost_f1 = widgets.FloatSlider(description="$c_1$",min=0,max=5),
    marginal_cost_f2 = widgets.FloatSlider(description="$c_2$",min=0,max=5),
 )
```

    interactive(children=(FloatSlider(value=0.0, description='$c_1$', max=5.0), FloatSlider(value=0.0, description…

    <function __main__.f(marginal_cost_f1, marginal_cost_f2)>

In the illustration above it is evident that if the two firms have identical marginal cost, the best response functions are completely identical (laying on top of eachother, graphically).
If firm 1 has a higher marginal cost, their best response function is lower than firm 2.

# Extension: Three firms

We want to understand what the optimal production levels are if a third firm is to enter. We focus on the case where firms are symmetric, with the same cost function.

In the following we define the inverse demand function, cost function, and three profit functions.

```python
# a: defining cost function and inverse demand function
def p(q1,q2,q3,a):
    demand = a - q1 - q2 - q3
    return demand

def cost(q,c):
    cost = q * c
    return cost
# b: defining profitfunctions

def pi_f1(q1,q2,q3,a,c):
    return p(q1,q2,q3,a) * q1 - cost(q1,c)

def pi_f2(q1,q2,q3,a,c):
    return p(q1,q2,q3,a) * q2 - cost(q2,c)

def pi_f3(q1,q2,q3,a,c):
    return p(q1,q2,q3,a) * q3 - cost(q3,c)
```

We further define the tre reaction functions, where firm i will optimize relative to the two other firms.

```python
q0 = [0]

def BR1(q2,q3,a,c):
    optimal_q1 = optimize.minimize(lambda q0: -pi_f1(q0,q2,q3,a,c), q0).x[0]
    return optimal_q1

def BR2(q1,q3,a,c):
    optimal_q2 = optimize.minimize(lambda q0: -pi_f2(q1,q0,q3,a,c), q0).x[0]
    return optimal_q2

def BR3(q1,q2,a,c):
    optimal_q3 = optimize.minimize(lambda q0: -pi_f3(q1,q2,q0,a,c), q0).x[0]
    return optimal_q3
```

```python
def con(q,param):
    u = q[0] - BR1(q[1],q[2],param[0],param[1])
    y = q[1] - BR2(q[0],q[2],param[0],param[1])
    z = q[2] - BR3(q[0],q[1],param[0],param[1])
    return [u,y,z]
```

```python
#initial values
inital_values = [1,1,1]

# Vector with parameters [a,c]:
param = [11,2]
```

```python
solver = optimize.fsolve(con,inital_values, args = (param))
print(f'optimal quantity: {solver}')
```

    optimal quantity: [2.24999997 2.25000001 2.25000001]

Inclusion of an additional firm, reduces the optimal quantity, which as we saw before was 3 for both firms.

# Conclusion

Two firms with identical demand functions and marginal cost, are symmetrical, and thereby set the same equilibrium quantities. We have learned that the marginal cost (when rising), has a negative effect on the best response functions and thereby breaks with the symmetry of the two firms. It is therefore evident that rising or lowering marginal cost has an effect on the equilibrium quantity.

Int the extension, we see that when a third firm is to enter the competition, it also does have an effect on the equilibrium quantitiy. When a third firm enters, assuming symmetry, it is evident that the optimal production level falls - in this instance from 3 to 2.5 units.


# ***Introduction to Radar Using Python and MATLAB***
## Andy Harrison - Copyright (C) 2019 Artech House
<br/>

# Single Pulse Ambiguity Function
***

Referring to Section 8.6.1, the complex envelope of a single unmodulated pulse is expressed as (Equation 8.59)

\begin{equation}\label{eq:single_unmodulated_pulse}
    s(t) = \text{rect}\bigg[ \frac{t}{\tau_p} \bigg],
\end{equation}

\noindent Using (Equation 8.59) and (Equation 8.53) allows the ambiguity function to be written as (Equation 8.61)

\begin{equation}
    \big| \chi(\tau, f) \big|^2  = \Bigg| \int\limits_{-\infty}^{\infty} \text{rect}\bigg[ \frac{t}{\tau_p}\bigg] \, \text{rect}\bigg[ \frac{t - \tau}{\tau_p} \bigg] e^{j 2 \pi f t}\, dt\Bigg|^2.
\end{equation}

Carrying out the integration gives (Equation 8.62)

\begin{equation}
    \big| \chi(\tau, f) \big|^2  = (\tau_p - |\tau|)^2\, \Big|\text{sinc}\left[ f (\tau_p - |\tau|) \right]  \Big|^2 \quad \text{for  } |\tau| \le \tau_p.
\end{equation}
***

Begin by getting the library path

```python
import lib_path
```

Set the pulsewidth (s)

```python
pulsewidth = 1e-3
```

Generate the time delay (s) using the `linspace` routine from `scipy`

```python
from numpy import linspace

# Set the time delay

time_delay = linspace(-pulsewidth, pulsewidth, 5000)
```

Calculate the ambiguity function for the single pulse

```python
from Libs.ambiguity.ambiguity_function import single_pulse

from numpy import finfo

ambiguity = single_pulse(time_delay, finfo(float).eps, pulsewidth)
```

Plot the zero-Doppler cut using the `matplotlib` routines

```python
from matplotlib import pyplot as plt

# Set the figure size

plt.rcParams["figure.figsize"] = (15, 10)

# Plot the ambiguity function

plt.plot(time_delay, ambiguity, '')

# Set the x and y axis labels

plt.xlabel("Time (s)", size=12)

plt.ylabel("Relative Amplitude", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Single Pulse Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

Set the Doppler mismatch frequencies using the `linspace` routine

```python
doppler_frequency = linspace(-2.0 / pulsewidth, 2.0 / pulsewidth, 1000)
```

Calculate the ambiguity function for the single pulse

```python
ambiguity = single_pulse(finfo(float).eps, doppler_frequency, pulsewidth)
```

Display the zero-range cut for the single pulse

```python
plt.plot(doppler_frequency, ambiguity, '')

# Set the x and y axis labels

plt.xlabel("Doppler (Hz)", size=12)

plt.ylabel("Relative Amplitude", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Single Pulse Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

Set the time delay and Doppler mismatch frequency and create the two-dimensional grid using the `meshgrid` routine from `scipy`

```python
from numpy import meshgrid

# Set the time delay

time_delay = linspace(-pulsewidth, pulsewidth, 1000)

# Set the Doppler mismatch

doppler_frequency =linspace(-2.0 / pulsewidth, 2.0 / pulsewidth, 1000)

# Create the grid

t, f = meshgrid(time_delay, doppler_frequency)
```

Calculate the ambiguity function for the single pulse

```python
ambiguity = single_pulse(t, f, pulsewidth)
```

Display the two-dimensional contour plot for the single pulse ambiguity function

```python
# Plot the ambiguity function

plt.contour(t, f, ambiguity, 20, cmap='jet', vmin=-0.2, vmax=1.0)

# Set the x and y axis labels

plt.xlabel("Time (s)", size=12)

plt.ylabel("Doppler (Hz)", size=12)

# Turn on the grid

plt.grid(linestyle=':', linewidth=0.5)

# Set the plot title and labels

plt.title('Single Pulse Ambiguity Function', size=14)

# Set the tick label size

plt.tick_params(labelsize=12)
```

```python

```