```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import itertools
from collections import defaultdict
import copy
from image_matrix_helper import compute_master_list, imshow_list, rgb_map
import random
import time

nb_start = time.time()
```

## Hints at Non-Equilibrium Behavior

In this notebook, we again simulate the systems presented in `search_and_combinatorics.ipynb`, but we do so in order to see how the number of correctly placed particles evolves towards its equilibrium value over the course of the simulation.

We will use much of the code from that section so we copy it here without additional explanation

#### Parameter function definitions

```python
# helper function definitions
gamma_func = lambda E0, Ev, T: 4*np.sqrt(2)*np.exp(E0/T)*(Ev/T)**(3/2)
delta_func = lambda Del, T: np.exp(Del/T)
phi_func = lambda x, z, gamma, delta: x*(1+ 1/(z*gamma))/(1-delta)
```

#### Microstate transitions

```python
## dissociation operator
def trans_dissoc(free_objs, bound_objs):

        # indices of non-empty
        indxs = [i for i, x in enumerate(bound_objs) if x != "-"]

        # random choice for bound object
        random_indx = random.choice(indxs)

        ## new state vector
        free_objs_new = copy.deepcopy(free_objs)
        bound_objs_new = copy.deepcopy(bound_objs)

        # putting empty slot
        bound_objs_new[random_indx] = '-'
        # appending previously bound object to free objects
        free_objs_new.append(bound_objs[random_indx])

        return free_objs_new, bound_objs_new

## association operator
def trans_assoc(free_objs, bound_objs):

        # random element to associate
        elem = random.choice(free_objs)

        # indices of empty spaces
        indxs = [i for i, x in enumerate(bound_objs) if x == "-"]

        # random choice for empty space
        random_indx = random.choice(indxs)

        ## new state vector
        free_objs_new = copy.deepcopy(free_objs)
        bound_objs_new = copy.deepcopy(bound_objs)

        ## state
        free_objs_new.remove(elem)
        bound_objs_new[random_indx] = elem

        return free_objs_new, bound_objs_new

## permutation operator
def trans_perm(free_objs, bound_objs):

    Ncomp = len(bound_objs)
    i1 = int(random.choice(range(Ncomp)))
    i2 = int(random.choice(range(Ncomp)))

    ## new omega vector
    bound_objs_new = copy.deepcopy(bound_objs)
    bound_objs_new[i2] = bound_objs[i1]
    bound_objs_new[i1] = bound_objs[i2]

    return free_objs, bound_objs_new
```

#### Logarithm of Botlzmann factor

The logarithm of the Botlzmann factor for a microstate (i.e., the temperature normalized negative energy of the microstate) is defined as

\begin{equation}
\beta E(\boldsymbol{k}, \boldsymbol{m}) = \sum_{i=1}^R(m_i \ln \delta_i + k_i \ln \gamma_i).
\label{eq:sim_en}
\end{equation}

```python
def log_boltz(free_objs, bound_objs, mstr_vec, deltas, gammas, name_key):

    elem_set = list(set(mstr_vec))
    count_dict = dict()
    for elem in elem_set:
        count_dict[elem] = bound_objs.count(elem)

    bind_log_factor = 0
    for elem in elem_set:
        key = name_key[elem]
        bind_log_factor += count_dict[elem]*np.log(gammas[key])

    corr_log_factor = 0
    for j in range(len(bound_objs)):
        if bound_objs[j] == mstr_vec[j]:
            elem = bound_objs[j]
            key = name_key[elem]
            corr_log_factor+=np.log(deltas[key])

    return bind_log_factor+corr_log_factor
```

#### Function to count the number of correctly bound particles

```python
def m_calc(bound_objs, mstr_vec):

    num = 0
    for k in range(len(mstr_vec)):
        if mstr_vec[k] == bound_objs[k]:
            num += 1
    return num
```

#### Metropolis Hastings algorithm

```python
### Metropolis Monte Carlo Algorithm

## loads uniform random sampling
runif = np.random.rand

def met_assembly_grid(Niter, free_objs, bound_objs, mstr_vec, deltas, gammas, name_key, only_physical_trans = False):
    '''
    #################################################################
    # function to sample using Metropolis
    #
    # n_iter:  number of iterations
    # initial_state: initial state for the start position for our chain
    # gamma: energy cost for incorrect component
    # temp: temperature
    ##################################################################
    '''

    # Initialize state values
    free_objs_vals = [0]*(Niter+1)
    bound_objs_vals = [0]*(Niter+1)

    # Set initial values
    free_objs_vals[0] = free_objs[:]
    bound_objs_vals[0] = bound_objs[:]

    # Initialize acceptance counts
    # We can use this to tune our number of steps
    accepted = 0

    # debugging code
    debug_assoc, debug_dissoc, debug_perm = 0, 0, 0

    for i in range(Niter):

        # get current monomer and dimer states
        current_free_objs = copy.deepcopy(free_objs_vals[i])
        current_bound_objs = copy.deepcopy(bound_objs_vals[i])

        N_free = len(current_free_objs)
        N_bound = len(current_bound_objs)-len(current_free_objs)

        u_trans = runif()

        # only allow for physical transitions
        if only_physical_trans:
            u1, u2 = 1/2, 1
        # includes permutation transition
        else:
            u1, u2 = 1/3, 2/3

        if u_trans < u1: #first type of transition; monomer association

            if N_free < 1:

                log_alpha = np.log(1e-15)

            else:
                # proposed new monomer and dimer states
                new_free_objs, new_bound_objs = trans_assoc(current_free_objs, current_bound_objs)

                # transition elements
                log_init = log_boltz(current_free_objs, current_bound_objs, mstr_vec, deltas, gammas, name_key)
                log_final = log_boltz(new_free_objs, new_bound_objs, mstr_vec, deltas, gammas, name_key)

                # weight
                num = N_free*N_free
                den = N_bound+1

                # Log-acceptance rate
                log_alpha = log_final-log_init+np.log(num/den)

        elif u1 <= u_trans < u2: #second type of transition; bound monomer dissociation

            if N_bound <1:

                log_alpha = np.log(1e-15)

            else:

                # proposed new monomer and dimer states
                new_free_objs, new_bound_objs = trans_dissoc(current_free_objs, current_bound_objs)

                # transition elements
                log_init = log_boltz(current_free_objs, current_bound_objs, mstr_vec, deltas, gammas, name_key)
                log_final = log_boltz(new_free_objs, new_bound_objs, mstr_vec, deltas, gammas, name_key)

                # weight
                num = N_bound
                den = (N_free+1)*(N_free+1)

                # Log-acceptance rate
                log_alpha = log_final-log_init+np.log(num/den)

        elif u2 <= u_trans: #third type of transition; switching bounded elements

            if N_bound <2:

                log_alpha = np.log(1e-15)

            else:

                # proposed new monomer and dimer states
                new_free_objs, new_bound_objs = trans_perm(current_free_objs, current_bound_objs)

                # transition elements
                log_init = log_boltz(current_free_objs, current_bound_objs, mstr_vec, deltas, gammas, name_key)
                log_final = log_boltz(new_free_objs, new_bound_objs, mstr_vec, deltas, gammas, name_key)

                # Log-acceptance rate
                log_alpha = log_final-log_init

        # Sample a uniform random variate
        u = runif()

        # Test proposed value
        if np.log(u) < log_alpha:
            # Accept
            free_objs_vals[i+1] = new_free_objs
            bound_objs_vals[i+1] = new_bound_objs

            #log_current_prob = log_proposed_prob
            accepted += 1
        else:
            # Stay put
            free_objs_vals[i+1] = free_objs_vals[i]
            bound_objs_vals[i+1] = bound_objs_vals[i]

    # return our samples and the number of accepted steps
    return free_objs_vals, bound_objs_vals, accepted
```

#### Image grid for completely correct configuration

```python
# defining master_list
master_list =compute_master_list()
# testing plot
imshow_list(master_list, title = 'Completely Correct Configuration');
# defining Nelems
Nelems = np.zeros(8)
key_list = list(rgb_map.keys())[:-1]
name_key_ = dict()
for j in range(len(key_list)):
    name_key_[key_list[j]] = j
    Nelems[j] = master_list.count(key_list[j])
```

```python
# displaying copy-number counts of the various elements
Nelems
```

    array([ 9.,  9., 10.,  5.,  7.,  6.,  3., 51.])

#### Simulating system

```python
# whether to only include physical transitions
only_physical_trans = True

# starting time
t0_start = time.time()

# setting parameter dictionary
param_dict = {'Search Limited': {'Del_bar':7.7501 ,
                                 'sigma_D':2.0,
                                 'E0_bar':3.0,
                                 'sigma_E':1.0},
              'Combinatorics Limited':{'Del_bar': 4.75,
                                 'sigma_D': 2.0,
                                 'E0_bar': 16.0,
                                 'sigma_E':3.0},
              'Indeterminate': {'Del_bar': 6.75,
                                 'sigma_D': 2.0,
                                 'E0_bar': 10.75,
                                 'sigma_E': 3.0}, }

# dictionary that contains both physical and unphysical transitions
m_full_data_dict = defaultdict(dict)

num_trajs = 10 # number of trajectories to plot for each data set

for bool_ in [True, False]:

    # defining whether to include only physical transitions
    only_physical_trans = bool_

    # empty list of list of trajectories
    bound_list_dict = {'Search Limited': [[]]*num_trajs,
                  'Combinatorics Limited':[[]]*num_trajs,
                  'Indeterminate':[[]]*num_trajs }

    # number of steps for MC algortihm
    Nmc = 10000

    # initial monomer and dimer states;
    # system in microstate of all correct dimers
    random.seed(0)
    free_objs_0 = random.sample(master_list, len(master_list))
    bound_objs_0 = ['-']*len(master_list)
    mstr_vec = copy.deepcopy(master_list)

    # make copy of initial monomer and dimer states
    free_objs_copy = copy.deepcopy(free_objs_0)
    bound_objs_copy = copy.deepcopy(bound_objs_0)

    # temperature set
    T0 = 0.5

    for type_ in list(bound_list_dict.keys()):

        # start time for particular type
        t0 = time.time()

        # getting parameter values
        dict_vals = param_dict[type_]

        # drawing energy values
        np.random.seed(24)
        R=8
        Del_bar, sigma_D = dict_vals['Del_bar'], dict_vals['sigma_D']
        Dels = np.random.randn(R)*sigma_D+Del_bar
        E0_bar, sigma_E = dict_vals['E0_bar'], dict_vals['sigma_E']
        E0s = np.random.randn(R)*sigma_E+E0_bar
        Evs = np.ones(R)*0.001

        # defining helper functions
        gammas_ = gamma_func(E0s, Evs, T0)
        deltas_ = delta_func(Dels, T0)

        for k in range(num_trajs):
            # metroplois generator
            _, bound_list_dict[type_][k], _ = met_assembly_grid(Nmc,
                                                                free_objs_copy,
                                                                bound_objs_copy,
                                                                mstr_vec,
                                                                deltas_,
                                                                gammas_,
                                                                name_key_,
                                                               only_physical_trans=only_physical_trans)

            t_prelim = time.time()
            print("Temperature Run:",str(k+1),"; Current Time:", round(t_prelim-t0,2),"secs")

        t1 = time.time()
        print(f"\nTotal Simulation Run Time for {type_}: {round(t1-t0,2)} secs")
        print("----------\n")

    # copying data
    m_full_data_dict[only_physical_trans] = bound_list_dict
    t2 = time.time()
    print("------------------------------\n------------------------------")
    print(f"Total Simulation Run Time for Only Physical Trans = {only_physical_trans}: {round(t2-t0_start,2)} secs")

t3 = time.time()
print("------------------------------\n------------------------------")
print(f"Total Simulation Run Time for all: {round(t2-t0_start,2)} secs")
```

    Temperature Run: 1 ; Current Time: 3.77 secs
    Temperature Run: 2 ; Current Time: 7.26 secs
    Temperature Run: 3 ; Current Time: 10.92 secs
    Temperature Run: 4 ; Current Time: 15.27 secs
    Temperature Run: 5 ; Current Time: 20.33 secs
    Temperature Run: 6 ; Current Time: 24.63 secs
    Temperature Run: 7 ; Current Time: 29.11 secs
    Temperature Run: 8 ; Current Time: 32.96 secs
    Temperature Run: 9 ; Current Time: 36.75 secs
    Temperature Run: 10 ; Current Time: 41.17 secs

    Total Simulation Run Time for Search Limited: 41.17 secs
    ----------

    Temperature Run: 1 ; Current Time: 2.07 secs
    Temperature Run: 2 ; Current Time: 4.06 secs
    Temperature Run: 3 ; Current Time: 6.02 secs
    Temperature Run: 4 ; Current Time: 8.54 secs
    Temperature Run: 5 ; Current Time: 10.64 secs
    Temperature Run: 6 ; Current Time: 12.97 secs
    Temperature Run: 7 ; Current Time: 14.92 secs
    Temperature Run: 8 ; Current Time: 16.97 secs
    Temperature Run: 9 ; Current Time: 19.03 secs
    Temperature Run: 10 ; Current Time: 21.1 secs

    Total Simulation Run Time for Combinatorics Limited: 21.1 secs
    ----------

    Temperature Run: 1 ; Current Time: 2.9 secs
    Temperature Run: 2 ; Current Time: 5.13 secs
    Temperature Run: 3 ; Current Time: 7.3 secs
    Temperature Run: 4 ; Current Time: 9.52 secs
    Temperature Run: 5 ; Current Time: 11.68 secs
    Temperature Run: 6 ; Current Time: 13.88 secs
    Temperature Run: 7 ; Current Time: 16.02 secs
    Temperature Run: 8 ; Current Time: 18.37 secs
    Temperature Run: 9 ; Current Time: 20.5 secs
    Temperature Run: 10 ; Current Time: 22.7 secs

    Total Simulation Run Time for Indeterminate: 22.7 secs
    ----------

    ------------------------------
    ------------------------------
    Total Simulation Run Time for Only Physical Trans = True: 84.98 secs
    Temperature Run: 1 ; Current Time: 4.43 secs
    Temperature Run: 2 ; Current Time: 8.98 secs
    Temperature Run: 3 ; Current Time: 13.4 secs
    Temperature Run: 4 ; Current Time: 18.18 secs
    Temperature Run: 5 ; Current Time: 22.89 secs
    Temperature Run: 6 ; Current Time: 27.97 secs
    Temperature Run: 7 ; Current Time: 33.13 secs
    Temperature Run: 8 ; Current Time: 37.85 secs
    Temperature Run: 9 ; Current Time: 42.39 secs
    Temperature Run: 10 ; Current Time: 46.76 secs

    Total Simulation Run Time for Search Limited: 46.76 secs
    ----------

    Temperature Run: 1 ; Current Time: 3.67 secs
    Temperature Run: 2 ; Current Time: 7.15 secs
    Temperature Run: 3 ; Current Time: 10.9 secs
    Temperature Run: 4 ; Current Time: 14.87 secs
    Temperature Run: 5 ; Current Time: 18.39 secs
    Temperature Run: 6 ; Current Time: 21.97 secs
    Temperature Run: 7 ; Current Time: 25.6 secs
    Temperature Run: 8 ; Current Time: 29.24 secs
    Temperature Run: 9 ; Current Time: 32.66 secs
    Temperature Run: 10 ; Current Time: 36.52 secs

    Total Simulation Run Time for Combinatorics Limited: 36.52 secs
    ----------

    Temperature Run: 1 ; Current Time: 3.38 secs
    Temperature Run: 2 ; Current Time: 6.6 secs
    Temperature Run: 3 ; Current Time: 9.91 secs
    Temperature Run: 4 ; Current Time: 13.37 secs
    Temperature Run: 5 ; Current Time: 16.7 secs
    Temperature Run: 6 ; Current Time: 19.94 secs
    Temperature Run: 7 ; Current Time: 23.57 secs
    Temperature Run: 8 ; Current Time: 26.86 secs
    Temperature Run: 9 ; Current Time: 30.28 secs
    Temperature Run: 10 ; Current Time: 34.22 secs

    Total Simulation Run Time for Indeterminate: 34.22 secs
    ----------

    ------------------------------
    ------------------------------
    Total Simulation Run Time for Only Physical Trans = False: 202.48 secs
    ------------------------------
    ------------------------------
    Total Simulation Run Time for all: 202.48 secs

#### Plotting simulated values of $m/N$ for each system type

```python
# dictionary that contains both physical and unphysical transitions
m_t_full_data_dict = defaultdict(dict)

# dictionary for final values of m/N
m_t_dict = {'Search Limited': [[]]*num_trajs,
              'Combinatorics Limited':[[]]*num_trajs,
              'Indeterminate':[[]]*num_trajs }
```

```python
for bool_ in [True, False]:

    # defining whether to include only physical transitions
    only_physical_trans = bool_

    # getting list of bound states
    bound_list_dict = m_full_data_dict[only_physical_trans]

    # dictionary for final values of m/N
    m_t_dict = {'Search Limited': [[]]*num_trajs,
                  'Combinatorics Limited':[[]]*num_trajs,
                  'Indeterminate':[[]]*num_trajs }

    # filling in dictionary values for m/N
    for type_ in list(m_t_dict.keys()):
        m_t_dict[type_] = [[m_calc(elem, master_list)/(len(master_list)) for elem in bound_list_dict[type_][k]] for k in range(num_trajs)]

    # dictionary for values of m/N
    m_t_full_data_dict[bool_] = m_t_dict
```

```python
for bool_ in [True, False]:

    # getting dictionary value for particular transition type
    m_t_dict = m_t_full_data_dict[bool_]

    for type_ in list(m_t_dict.keys()):

        plt.figure(figsize = (7,5))
        ax = plt.subplot(111)

        for k in range(num_trajs):
            plt.plot(m_t_dict[type_][k])

            # plot formatting
            ax.set_xlabel(r'simulation time', fontsize = 18,labelpad=10)
            plt.xlim([0, 10000])
            plt.ylim([0,1.1])
            plt.ylabel(r'$m/N$', fontsize = 18)
            # plt.yaxis.set_label_coords(-0.1,.5)

            ax.axhline(y = 1.0, color = 'k', linestyle = 'dashed', linewidth = 2)

            # Hide the right and top spines
            ax.spines['right'].set_visible(False)
            ax.spines['top'].set_visible(False)

            # Only show ticks on the left and bottom spines
            ax.yaxis.set_ticks_position('left')
            ax.xaxis.set_ticks_position('bottom')

            # increase label size
            ax.tick_params(axis='both', which='major', labelsize=12)
            ax.tick_params(axis='both', which='minor', labelsize=12)
            # plt.legend(loc = 'best', fontsize = 12)

        plt.grid(alpha = 0.45)
        type_name = type_.replace(' ', '_').lower()
        print('Only Physical Transitions:', bool_)
        print('System Type:', type_)
        print()

    #     if only_physical_trans:
    #         suffix = 'phys'
    #     else:
    #         suffix = 'nonphys'
    #     plt.savefig(f'hints_neqbm_{suffix}_{type_name}.png', bbox_inches='tight', format = 'png')

        plt.show()
```

```python
print('Total Notebook Runtime: %.3f mins' % ((time.time()-nb_start)/60))
```

    Total Notebook Runtime: 3.502 mins

```python

```


# Aim of this notebook

* To construct the singular curve of universal type to finalize the solution of the optimal control problem

# Preamble

```python
from sympy import *
init_printing(use_latex='mathjax')

# Plotting
%matplotlib inline
## Make inline plots raster graphics
from IPython.display import set_matplotlib_formats
## Import modules for plotting and data analysis
import matplotlib.pyplot as plt
from matplotlib import gridspec,rc,colors
import matplotlib.ticker as plticker
# Parameters for seaborn plots
import seaborn as sns
clrs = sns.color_palette("Spectral", 6)
def set_plot_style(usetex=False):
    sns.set_style('white', {'axes.linewidth': 0.5})
    sns.set(style='white', font_scale=1.1,#context='paper',
            rc={'xtick.major.size': 6, 'ytick.major.size': 6, 'legend.fontsize': 14,
                'text.usetex': usetex, 'font.family': 'serif', 'font.serif': ['Verdana'],
                'text.latex.preamble': r"\usepackage{type1cm}"})
    plt.rcParams['xtick.major.size'] = 6
    plt.rcParams['xtick.major.width'] = 1
    plt.rcParams['ytick.major.size'] = 6
    plt.rcParams['ytick.major.width'] = 1
    plt.rcParams['xtick.bottom'] = True
    plt.rcParams['ytick.left'] = True

set_plot_style(True)

import pandas as pd
pd.set_option('mode.chained_assignment',None)

import numpy as np
from scipy.optimize import fsolve, root
from scipy.integrate import ode
backend = 'dopri5'
import warnings

# Timer
import time

from copy import deepcopy

from itertools import cycle
palette_size = 10;
clrs = sns.color_palette("Reds",palette_size)
iclrs = cycle(clrs) # iterated colors

clrs0 = sns.color_palette("Set1",palette_size)

# Suppress warnings
import warnings
warnings.filterwarnings("ignore")
```

# Parameter values

* Birth rate and const of downregulation are defined below in order to fit some experim. data

```python
d = .13 # death rate
α = .3 # low equilibrium point at expression of the main pathway (high equilibrium is at one)
θ = .45 # threshold value for the expression of the main pathway
κ = 40 # robustness parameter
```

* Symbolic variables - the list insludes μ & μbar, because they will be varied later

```python
σ, φ0, φ, x, μ, μbar = symbols('sigma, phi0, phi, x, mu, mubar')
```

* Main functions

```python
A = 1-σ*(1-θ)
Eminus = (α*A-θ)**2/2
ΔE = A*(1-α)*((1+α)*A/2-θ)
ΔEf = lambdify(σ,ΔE)
```

* Birth rate and cost of downregulation

```python
b = (0.1*(exp(κ*(ΔEf(1)))+1)-0.14*(exp(κ*ΔEf(0))+1))/(exp(κ*ΔEf(1))-exp(κ*ΔEf(0))) # birth rate
χ = 1-(0.14*(exp(κ*ΔEf(0))+1)-b*exp(κ*ΔEf(0)))/b
b, χ
```

$$\left ( 0.140168330860362, \quad 0.325961223954473\right )$$

```python
c_relative = 0.1
c = c_relative*(b-d)/b+(1-c_relative)*χ/(exp(κ*ΔEf(0))+1) # cost of resistance
c
```

$$0.00833519849448376$$

* Hamiltonian *H* and a part of it ρ that includes the control variable σ

```python
h = b*(χ/(exp(κ*ΔE)+1)*(1-x)+c*x)
H = -φ0 + φ*(b*(χ/(exp(κ*ΔE)+1)-c)*x*(1-x)+μ*(1-x)/(exp(κ*ΔE)+1)-μbar*exp(-κ*Eminus)*x) + h
ρ = (φ*(b*χ*x+μ)+b*χ)/(exp(κ*ΔE)+1)*(1-x)-φ*μbar*exp(-κ*Eminus)*x
ρ1 = (φ*(b*χ*x+μ)+b*χ)/(exp(κ*ΔE)+1)*(1-x)
ρ2 = φ*μbar*exp(-κ*Eminus)*x
n = b*(1-χ*(1-x)/(exp(κ*ΔE)+1)-c*x)-d
H, ρ, n
```

$$\left ( \phi \left(\frac{\mu \left(- x + 1\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1} - \bar{\mu} x e^{- 20 \left(- 0.165 \sigma - 0.15\right)^{2}} + x \left(-0.00116833086036159 + \frac{0.045689440686899}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}\right) \left(- x + 1\right)\right) - \phi_{0} + 0.00116833086036159 x + \frac{0.045689440686899 \left(- x + 1\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}, \quad - \bar{\mu} \phi x e^{- 20 \left(- 0.165 \sigma - 0.15\right)^{2}} + \frac{\left(- x + 1\right) \left(\phi \left(\mu + 0.045689440686899 x\right) + 0.045689440686899\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1}, \quad - 0.00116833086036159 x - \frac{0.140168330860362 \left(- 0.325961223954473 x + 0.325961223954473\right)}{e^{40 \left(- 0.385 \sigma + 0.7\right) \left(- 0.3575 \sigma + 0.2\right)} + 1} + 0.0101683308603616\right )$$

* Same but for no treatment (σ = 0)

```python
h0 = h.subs(σ,0)
H0 = H.subs(σ,0)
ρ0 = ρ.subs(σ,0)
H0, ρ0
```

$$\left ( \phi \left(0.00368423989943599 \mu \left(- x + 1\right) - 0.637628151621773 \bar{\mu} x - 0.001 x \left(- x + 1\right)\right) - \phi_{0} + 0.001 x + 0.000168330860361587, \quad - 0.637628151621773 \bar{\mu} \phi x + 0.00368423989943599 \left(- x + 1\right) \left(\phi \left(\mu + 0.045689440686899 x\right) + 0.045689440686899\right)\right )$$

* Machinery: definition of the Poisson brackets

```python
PoissonBrackets = lambda H1, H2: diff(H1,x)*diff(H2,φ)-diff(H1,φ)*diff(H2,x)
```

* Necessary functions and defining the right hand side of dynamical equations

```python
ρf = lambdify((x,φ,σ,μ,μbar),ρ)
ρ1f = lambdify((x,φ,σ,μ,μbar),ρ1)
ρ2f = lambdify((x,φ,σ,μ,μbar),ρ2)
ρ0f = lambdify((x,φ,μ,μbar),ρ0)
dxdτ = lambdify((x,φ,σ,μ,μbar),-diff(H,φ))
dφdτ = lambdify((x,φ,σ,μ,μbar),diff(H,x))
# dndτ = lambdify((x,φ,σ,μ,μbar),-n)
dVdτ = lambdify((x,σ),h)
dρdσ = lambdify((σ,x,φ,μ,μbar),diff(ρ,σ))
dδρdτ = lambdify((x,φ,σ,μ,μbar),-PoissonBrackets(ρ0-ρ,H))
def ode_rhs(t,state,μ,μbar):
    x, φ, V, δρ = state
    σs = [0,1]
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σstar = 1.;
    if ρf(x,φ,σstar,μ,μbar) < ρ0f(x,φ,μ,μbar):
        sgm = 0
    else:
        sgm = σstar
    return [dxdτ(x,φ,sgm,μ,μbar),dφdτ(x,φ,sgm,μ,μbar),dVdτ(x,sgm),dδρdτ(x,φ,σstar,μ,μbar)]
def σstarf(x,φ,μ,μbar):
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σstar = 1.;
    if ρf(x,φ,σstar,μ,μbar) < ρ0f(x,φ,μ,μbar):
        sgm = 0
    else:
        sgm = σstar
    return sgm
```

```python
def get_primary_field(name, experiment,μ,μbar):
    solutions = {}
    solver = ode(ode_rhs).set_integrator(backend)
    τ0 = experiment['τ0']
    tms = np.linspace(τ0,experiment['T_end'],1e3+1)
    for x0 in experiment['x0']:
        δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
        solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
        sol = []; k = 0;
        while (solver.t < experiment['T_end']) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
            solver.integrate(tms[k])
            sol.append([solver.t]+list(solver.y))
            k += 1
        solutions[x0] = {'solution': sol}
    for x0, entry in solutions.items():
        entry['τ'] = [entry['solution'][j][0] for j in range(len(entry['solution']))]
        entry['x'] = [entry['solution'][j][1] for j in range(len(entry['solution']))]
        entry['φ'] = [entry['solution'][j][2] for j in range(len(entry['solution']))]
        entry['V'] = [entry['solution'][j][3] for j in range(len(entry['solution']))]
        entry['δρ'] = [entry['solution'][j][4] for j in range(len(entry['solution']))]
    return solutions
def get_δρ_value(tme,x0,μ,μbar):
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    while (solver.t < tme) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tme)
        sol = [solver.t]+list(solver.y)
    return solver.y[3]
def get_δρ_ending(params,μ,μbar):
    tme, x0 = params
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    _k = 0; sol = []
    while (_k<len(tms)):# and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append(solver.y)
        _k += 1
    #print(sol)
    return(sol[0][3],(sol[1][3]-sol[0][3])/δτ)
def get_state(tme,x0,μ,μbar):
    solver = ode(ode_rhs).set_integrator(backend)
    δρ0 = ρ0.subs(x,x0).subs(φ,0)-ρ.subs(x,x0).subs(φ,0).subs(σ,1.)
    solver.set_initial_value([x0,0,0,δρ0],0.).set_f_params(μ,μbar)
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    _k = 0; sol = []
    while (solver.t < tms[-1]) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append(solver.y)
        _k += 1
    return(list(sol[0])+[(sol[1][3]-sol[0][3])/δτ])
```

# Machinery for the universal line

* To find the universal singular curve we need to define two parameters

```python
γ0 = PoissonBrackets(PoissonBrackets(H,H0),H)
γ1 = PoissonBrackets(PoissonBrackets(H0,H),H0)
```

* The dynamics

```python
dxdτSingExpr = -(γ0*diff(H0,φ)+γ1*diff(H,φ))/(γ0+γ1)
dφdτSingExpr = (γ0*diff(H0,x)+γ1*diff(H,x))/(γ0+γ1)
dVdτSingExpr = (γ0*h0+γ1*h)/(γ0+γ1)
σSingExpr = γ1*σ/(γ0+γ1)
```

* Machinery for Python: lambdify the functions above

```python
dxdτSing = lambdify((x,φ,σ,μ,μbar),dxdτSingExpr)
dφdτSing = lambdify((x,φ,σ,μ,μbar),dφdτSingExpr)
dVdτSing = lambdify((x,φ,σ,μ,μbar),dVdτSingExpr)
σSing = lambdify((x,φ,σ,μ,μbar),σSingExpr)
```

```python
def ode_rhs_Sing(t,state,μ,μbar):
    x, φ, V = state
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σstar = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σstar = 1.;
    return [dxdτSing(x,φ,σstar,μ,μbar),dφdτSing(x,φ,σstar,μ,μbar),dVdτSing(x,φ,σstar,μ,μbar)]
def get_universal_curve(end_point,tmax,Nsteps,μ,μbar):
    tms =  np.linspace(end_point[0],tmax,Nsteps);
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    _k = 0; sol = []
    while (solver.t < tms[-1]):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_σ_universal(tme,end_point,μ,μbar):
    δτ = 1.0e-8; tms = [tme,tme+δτ]
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    _k = 0; sol = []
    while (solver.t < tme+δτ):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    x, φ = sol[0][:2]
    sgm = fsolve(lambda σ: dxdτ(x,φ,σ,μ,μbar)-(sol[1][0]-sol[0][0])/δτ,θ/2)[0]
    return sgm
def get_state_universal(tme,end_point,μ,μbar):
    solver = ode(ode_rhs_Sing).set_integrator(backend)
    solver.set_initial_value(end_point[1:4],end_point[0]).set_f_params(μ,μbar)
    solver.integrate(tme)
    return [solver.t]+list(solver.y)
```

```python
def ode_rhs_with_σstar(t,state,μ,μbar):
    x, φ, V = state
    if (dρdσ(1.,x,φ,μ,μbar)<0) and (dρdσ(θ,x,φ,μ,μbar)>0):
        σ = fsolve(dρdσ,.8,args=(x,φ,μ,μbar,))[0]
    else:
        σ = 1.;
    return [dxdτ(x,φ,σ,μ,μbar),dφdτ(x,φ,σ,μ,μbar),dVdτ(x,σ)]
def ode_rhs_with_given_σ(t,state,σ,μ,μbar):
    x, φ, V = state
    return [dxdτ(x,φ,σ,μ,μbar),dφdτ(x,φ,σ,μ,μbar),dVdτ(x,σ)]
def get_trajectory_with_σstar(starting_point,tmax,Nsteps,μ,μbar):
    tms = np.linspace(starting_point[0],tmax,Nsteps)
    solver = ode(ode_rhs_with_σstar).set_integrator(backend)
    solver.set_initial_value(starting_point[1:],starting_point[0]).set_f_params(μ,μbar)
    sol = []; _k = 0;
    while solver.t < max(tms) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_trajectory_with_given_σ(starting_point,tmax,Nsteps,σ,μ,μbar):
    tms = np.linspace(starting_point[0],tmax,100)
    solver = ode(ode_rhs_with_given_σ).set_integrator(backend)
    solver.set_initial_value(starting_point[1:],starting_point[0]).set_f_params(σ,μ,μbar)
    sol = []; _k = 0;
    while solver.t < max(tms) and (solver.y[0]<=1.) and (solver.y[0]>=0.):
        solver.integrate(tms[_k])
        sol.append([solver.t]+list(solver.y))
        _k += 1
    return sol
def get_state_with_σstar(tme,starting_point,μ,μbar):
    solver = ode(ode_rhs_with_σstar).set_integrator(backend)
    solver.set_initial_value(starting_point[1:4],starting_point[0]).set_f_params(μ,μbar)
    solver.integrate(tme)
    return [solver.t]+list(solver.y)
def get_finalizing_point_from_universal_curve(tme,tmx,end_point,μ,μbar):
    unv_point = get_state_universal(tme,end_point,μ,μbar)
    return get_state_with_σstar(tmx,unv_point,μ,μbar)[1]
```

# Field of optimal trajectories as the solution of the Bellman equation

* μ & μbar are varied by *T* and *T*bar ($\mu=1/T$ and $\bar\mu=1/\bar{T}$)

```python
tmx = 180.
end_switching_curve = {'t': 9., 'x': .7}
# for Τ, Τbar in zip([28]*5,[14,21,28,35,60]):
Τ = 7.033; Τbar = 13.760
μ = 1./Τ; μbar = 1./Τbar
print("Parameters: μ = %.5f, μbar = %.5f"%(μ,μbar))
end_switching_curve['t'], end_switching_curve['x'] = fsolve(get_δρ_ending,(end_switching_curve['t'],end_switching_curve['x']),args=(μ,μbar),xtol=1.0e-12)
end_point = [end_switching_curve['t']]+get_state(end_switching_curve['t'],end_switching_curve['x'],μ,μbar)
print("Ending point for the switching line: τ = %.1f days, x = %.1f%%" % (end_point[0], end_point[1]*100))
print("Checking the solution - should give zero values: ")
print(get_δρ_ending([end_switching_curve['t'],end_switching_curve['x']],μ,μbar))
print("* Constructing the primary field")
primary_field1 = []
experiments = {
    'sol1': { 'T_end': tmx, 'τ0': 0., 'x0': list(np.linspace(0,end_switching_curve['x']-(1e-3),7)) } }
for name, values in experiments.items():
    primary_field1.append(get_primary_field(name,values,μ,μbar))
primary_field2 = []
experiments = {
    'sol1': { 'T_end': tmx, 'τ0': 0., 'x0': list(np.linspace(end_switching_curve['x']+(3e-6),1.,7)) } }
for name, values in experiments.items():
    primary_field2.append(get_primary_field(name,values,μ,μbar))
print("* Constructing the switching curve")
switching_curve = []
x0s = np.linspace(end_switching_curve['x'],1,21); _y = end_switching_curve['t']

for x0 in x0s:
    tme = fsolve(get_δρ_value,_y,args=(x0,μ,μbar))[0]
    if (tme>0):
        switching_curve = switching_curve+[[tme,get_state(tme,x0,μ,μbar)[0]]]
        _y = tme
print("* Constructing the universal curve")
universal_curve = get_universal_curve(end_point,tmx,25,μ,μbar)
print("* Finding the last characteristic")
#time0 = time.time()
tuniv = fsolve(get_finalizing_point_from_universal_curve,tmx-40.,args=(tmx,end_point,μ,μbar,))[0]
#print("The proccess to find the last characteristic took %0.1f minutes" % ((time.time()-time0)/60.))
univ_point = get_state_universal(tuniv,end_point,μ,μbar)
print("The last point on the universal line:")
print(univ_point)
last_trajectory = get_trajectory_with_σstar(univ_point,tmx,50,μ,μbar)
print("Final state:")
final_state = get_state_with_σstar(tmx,univ_point,μ,μbar)
print(final_state)
print("Fold-change in tumor size: %.2f"%(exp((b-d)*tmx-final_state[-1])))
```

    Parameters: μ = 0.14219, μbar = 0.07267
    Ending point for the switching line: τ = 8.6 days, x = 67.6%
    Checking the solution - should give zero values:
    (-1.255427622889058e-12, -4.318339200892129e-13)
    * Constructing the primary field
    * Constructing the switching curve
    * Constructing the universal curve
    * Finding the last characteristic
    The last point on the universal line:
    [219.0411945258855, 0.6760266405468816, -0.17824842987368056, 1.4329814651256367]
    Final state:
    [180.0, 0.9413837952922908, 39.34094743100057, 1.2323533064380234]
    Fold-change in tumor size: 1.82

```python
# Plotting
plt.rcParams['figure.figsize'] = (4.5, 3.2)
_k = 0
for solutions in primary_field1:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
_k = 0
for solutions in primary_field2:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
plt.plot([x[0] for x in switching_curve],[x[1] for x in switching_curve],linewidth=3,color="k",zorder=4,linestyle="dashed")
plt.plot([end_point[0]],[end_point[1]],marker='o',color="black",zorder=4)

plt.xlim([0,120]); plt.ylim([0,1]);
ytks = np.arange(0,1.2,.2)
plt.yticks(ytks,[int(y*100) for y in ytks])
plt.xlabel("Backward time (days)"); plt.ylabel("Fraction of resistant cells (\%)")
# plt.show()
plt.savefig("../figures/draft/Fig2-0.pdf",format='pdf',bbox_inches='tight')
```

```python
# Plotting
plt.rcParams['figure.figsize'] = (4.5, 3.2)
_k = 0
for solutions in primary_field1:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
_k = 0
for solutions in primary_field2:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
plt.plot([x[0] for x in switching_curve],[x[1] for x in switching_curve],linewidth=3,color="k",zorder=4,linestyle="dashed")
# plt.plot([end_point[0]],[end_point[1]],marker='o',color="black",zorder=4)
plt.plot([x[0] for x in universal_curve],[x[1] for x in universal_curve],linewidth=3,color="k",zorder=3)
for tend in [42,64,86,110]:
    tuniv = fsolve(get_finalizing_point_from_universal_curve,tend-20.,args=(tend,end_point,μ,μbar,))[0]
    univ_point = get_state_universal(tuniv,end_point,μ,μbar)
    trajectory = get_trajectory_with_σstar(univ_point,tend,50,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
    trajectory = get_trajectory_with_given_σ(univ_point,tend+20,100,0,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
plt.xlim([0,120]); plt.ylim([0,1]);
ytks = np.arange(0,1.2,.2)
plt.yticks(ytks,[int(y*100) for y in ytks])
plt.xlabel("Backward time (days)"); plt.ylabel("Fraction of resistant cells (\%)")
# plt.show()
plt.savefig("../figures/draft/Fig2-1.pdf",format='pdf',bbox_inches='tight')
```

```python
# Plotting
plt.rcParams['figure.figsize'] = (3.5, 2.5)

σs = np.linspace(0,1,1001)
plt.plot(σs,[ρf(.9,0,σ,μ,μbar) for σ in σs],linewidth=2,color="k")
σimx = np.argmax([ρf(.9,0,σ,μ,μbar) for σ in σs])
print(σimx)
plt.plot(σs[σimx],[ρf(.9,0,σs[σimx],μ,μbar)],'ro')
plt.plot(σs,[ρ1f(.9,0,σ,μ,μbar) for σ in σs],'g--',linewidth=1,zorder=-5)
plt.plot(σs,[ρ2f(.9,0,σ,μ,μbar) for σ in σs],'b--',linewidth=1,zorder=-5)
plt.ylim([-.0002,.0062]);
plt.savefig("../figures/draft/Fig3-A.pdf",format='pdf',bbox_inches='tight')
```

```python
fig, ax = plt.subplots()
for solution in primary_field2:
    k = 0
    for x0, entry in solution.items():
        if (k==0):
            print("Terminal point: ",entry['τ'][0],entry['x'][0],entry['φ'][0])
            kk = 16
            print(entry['τ'][kk],entry['x'][kk],entry['φ'][kk])
            ρyy = [ρf(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs]
            plt.plot(σs,ρyy,linewidth=2,color="k")
            σimx = np.argmax(ρyy)
            print(σimx)
            plt.plot(σs[σimx],ρyy[σimx],'ro')
            plt.plot(σs,[ρ1f(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs],'g--',linewidth=1,zorder=-5)
            plt.plot(σs,[-ρ2f(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs],'b--',linewidth=1,zorder=-5)
            plt.ylim([-.0002,.0062]);
            break
        k = k + 1
plt.savefig("../figures/draft/Fig3-B.pdf",format='pdf',bbox_inches='tight')
```

```python
fig, ax = plt.subplots()
for solution in primary_field2:
    k = 0
    for x0, entry in solution.items():
        if (k==0):
            print("Terminal point: ",entry['τ'][0],entry['x'][0],entry['φ'][0])
            kk = 70
            print(entry['τ'][kk],entry['x'][kk],entry['φ'][kk])
            ρyy = [ρf(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs]
            plt.plot(σs,ρyy,linewidth=2,color="k")
            σimx = np.argmax(ρyy)
            print(σimx)
            plt.plot(σs[σimx],ρyy[σimx],'ro')
            plt.plot(σs,[ρ1f(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs],'g--',linewidth=1,zorder=-5)
            plt.plot(σs,[-ρ2f(entry['x'][kk],entry['φ'][kk],σ,μ,μbar) for σ in σs],'b--',linewidth=1,zorder=-5)
            plt.ylim([-.0002,.0062]);
            break
        k = k + 1
plt.savefig("../figures/draft/Fig3-C.pdf",format='pdf',bbox_inches='tight')
```

```python
fig, ax = plt.subplots()
kk = 10
xu = universal_curve[kk][1]
φu = universal_curve[kk][2]
print("Point on the universal curve: ",universal_curve[kk][0],xu,φu)

ρyy = [ρf(xu,φu,σ,μ,μbar) for σ in σs]
plt.plot(σs,ρyy,linewidth=2,color="k")
σimx = np.argmax(ρyy)
print(σimx)
plt.plot(σs[σimx],ρyy[σimx],'ro')
plt.plot([0],ρyy[σimx],'ro')
plt.plot(σs,[ρ1f(xu,φu,σ,μ,μbar) for σ in σs],'g--',linewidth=1,zorder=-5)
plt.plot(σs,[-ρ2f(xu,φu,σ,μ,μbar) for σ in σs],'b--',linewidth=1,zorder=-5)
plt.ylim([-.0002,.0062]);
# ax.yaxis.set_major_formatter(plt.NullFormatter())
plt.savefig("../figures/draft/Fig3-D.pdf",format='pdf',bbox_inches='tight')
```

# Preparation for second figure

```python
# Plotting
plt.rcParams['figure.figsize'] = (6.75, 4.5)
_k = 0
for solutions in primary_field1:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
_k = 0
for solutions in primary_field2:
    for x0, entry in solutions.items():
        plt.plot(entry['τ'], entry['x'], '-', linewidth=1, color=clrs0[1])
        _k += 1
plt.plot([x[0] for x in switching_curve],[x[1] for x in switching_curve],linewidth=3,color=clrs0[0],zorder=4,linestyle="dashed")
plt.plot([end_point[0]],[end_point[1]],marker='o',color="black",zorder=4)
plt.plot([x[0] for x in universal_curve],[x[1] for x in universal_curve],linewidth=3,color=clrs0[0],zorder=3)
for tend in [80,110,140]:
    tuniv = fsolve(get_finalizing_point_from_universal_curve,tend-20.,args=(tend,end_point,μ,μbar,))[0]
    univ_point = get_state_universal(tuniv,end_point,μ,μbar)
    trajectory = get_trajectory_with_σstar(univ_point,tend,50,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
    trajectory = get_trajectory_with_given_σ(univ_point,tend+20,100,0,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
plt.xlim([0,120]); plt.ylim([0,1]);
plt.xlabel("Backward time (days)"); plt.ylabel("Fraction of resistant cells (\%)")
plt.show()
```

```python
plt.rcParams['figure.figsize'] = (6.75, 4.5)

_k = 0
for solutions in primary_field1:
    for x0, entry in solutions.items():
        if _k==5:
            sol = [[1,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in zip(entry['τ'],entry['x'],entry['φ'],entry['V'])]
        if _k==6:
            trajectory_thr = [[τ,x,φ,V] for τ,x,φ,V in zip(entry['τ'],entry['x'],entry['φ'],entry['V'])]
            sol += [[0,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in trajectory_thr]
            T0 = max([x[0] for x in trajectory_thr])
        _k += 1
#plt.plot(τ1, x1, '-', linewidth=1, color=clrs0[1])
#plt.plot(τthr, xthr, '--', linewidth=1, color=clrs0[1])
print(T0/30.)

plt.plot([end_point[0]],[end_point[1]],marker='o',color="black",zorder=4)
for tend in [180]:
    tuniv = fsolve(get_finalizing_point_from_universal_curve,tend-20.,args=(tend,end_point,μ,μbar,))[0]
    univ_point = get_state_universal(tuniv,end_point,μ,μbar)
    trajectory = get_trajectory_with_σstar(univ_point,tend,50,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
    sol += [[3,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in trajectory]
    universal_curve = get_universal_curve(end_point,univ_point[0],50,μ,μbar)
    plt.plot([x[0] for x in universal_curve],[x[1] for x in universal_curve],linewidth=3,color=clrs0[0],zorder=3)
    sol = [[3,τ,get_σ_universal(τ,end_point,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in universal_curve] + sol
    trajectory = get_trajectory_with_σstar([0,end_switching_curve['x'],0,0],end_point[0],50,μ,μbar)
    sol = [[3,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in trajectory] + sol
for tend in [124]:
    tuniv = fsolve(get_finalizing_point_from_universal_curve,tend-20.,args=(tend,end_point,μ,μbar,))[0]
    univ_point = get_state_universal(tuniv,end_point,μ,μbar)
#     trajectory = get_trajectory_with_σstar(univ_point,tend,50,μ,μbar)
    trajectory = get_trajectory_with_given_σ(univ_point,tend+20,200,0,μ,μbar)
    plt.plot([x[0] for x in trajectory],[x[1] for x in trajectory],linewidth=1,color=clrs0[4])
    sol += [[2,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in trajectory]
    universal_curve = get_universal_curve(end_point,univ_point[0],50,μ,μbar)
    plt.plot([x[0] for x in universal_curve],[x[1] for x in universal_curve],linewidth=3,color=clrs0[0],zorder=3)
    sol = [[2,τ,get_σ_universal(τ,end_point,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in universal_curve] + sol
    trajectory = get_trajectory_with_σstar([0,end_switching_curve['x'],0,0],end_point[0],150,μ,μbar)
    sol = [[2,τ,σstarf(x,φ,μ,μbar),x,exp((b-d)*τ-V)] for τ,x,φ,V in trajectory] + sol
plt.xlim([0,180]); plt.ylim([0,1]);
plt.xlabel("Backward time (days)"); plt.ylabel("Fraction of resistant cells (\%)")
plt.show()
```

```python
df_res = pd.DataFrame(sol,columns=['part','time','sigma','resistance','fold_change'])
df_res[:10]
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>part</th>
      <th>time</th>
      <th>sigma</th>
      <th>resistance</th>
      <th>fold_change</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>2</td>
      <td>0.000000</td>
      <td>1.0</td>
      <td>0.864904</td>
      <td>1</td>
    </tr>
    <tr>
      <th>1</th>
      <td>2</td>
      <td>0.057467</td>
      <td>1.0</td>
      <td>0.864165</td>
      <td>1.00021361997671</td>
    </tr>
    <tr>
      <th>2</th>
      <td>2</td>
      <td>0.114934</td>
      <td>1.0</td>
      <td>0.863419</td>
      <td>1.00042562008607</td>
    </tr>
    <tr>
      <th>3</th>
      <td>2</td>
      <td>0.172400</td>
      <td>1.0</td>
      <td>0.862665</td>
      <td>1.00063598356561</td>
    </tr>
    <tr>
      <th>4</th>
      <td>2</td>
      <td>0.229867</td>
      <td>1.0</td>
      <td>0.861905</td>
      <td>1.00084469350505</td>
    </tr>
    <tr>
      <th>5</th>
      <td>2</td>
      <td>0.287334</td>
      <td>1.0</td>
      <td>0.861137</td>
      <td>1.00105173284530</td>
    </tr>
    <tr>
      <th>6</th>
      <td>2</td>
      <td>0.344801</td>
      <td>1.0</td>
      <td>0.860363</td>
      <td>1.00125708437735</td>
    </tr>
    <tr>
      <th>7</th>
      <td>2</td>
      <td>0.402268</td>
      <td>1.0</td>
      <td>0.859580</td>
      <td>1.00146073074122</td>
    </tr>
    <tr>
      <th>8</th>
      <td>2</td>
      <td>0.459734</td>
      <td>1.0</td>
      <td>0.858791</td>
      <td>1.00166265442489</td>
    </tr>
    <tr>
      <th>9</th>
      <td>2</td>
      <td>0.517201</td>
      <td>1.0</td>
      <td>0.857994</td>
      <td>1.00186283776321</td>
    </tr>
  </tbody>
</table>
</div>

```python
pd.DataFrame(sol).to_csv('../figures/draft/Fig6-trjs_optimal.csv',index=False,header=False)
```

```python
df_res_ = df_res.loc[lambda df: df.part==3].sort_values('time').drop('part',axis=1)
df_res_[:10]
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>time</th>
      <th>sigma</th>
      <th>resistance</th>
      <th>fold_change</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>200</th>
      <td>0.000000</td>
      <td>1.0</td>
      <td>0.864904</td>
      <td>1</td>
    </tr>
    <tr>
      <th>201</th>
      <td>0.174746</td>
      <td>1.0</td>
      <td>0.862635</td>
      <td>1.00064453482234</td>
    </tr>
    <tr>
      <th>202</th>
      <td>0.349492</td>
      <td>1.0</td>
      <td>0.860299</td>
      <td>1.00127377272317</td>
    </tr>
    <tr>
      <th>203</th>
      <td>0.524238</td>
      <td>1.0</td>
      <td>0.857896</td>
      <td>1.00188722954509</td>
    </tr>
    <tr>
      <th>204</th>
      <td>0.698984</td>
      <td>1.0</td>
      <td>0.855423</td>
      <td>1.00248440812323</td>
    </tr>
    <tr>
      <th>205</th>
      <td>0.873730</td>
      <td>1.0</td>
      <td>0.852880</td>
      <td>1.00306479800551</td>
    </tr>
    <tr>
      <th>206</th>
      <td>1.048476</td>
      <td>1.0</td>
      <td>0.850263</td>
      <td>1.00362787517133</td>
    </tr>
    <tr>
      <th>207</th>
      <td>1.223222</td>
      <td>1.0</td>
      <td>0.847570</td>
      <td>1.00417310174885</td>
    </tr>
    <tr>
      <th>208</th>
      <td>1.397968</td>
      <td>1.0</td>
      <td>0.844800</td>
      <td>1.00469992573148</td>
    </tr>
    <tr>
      <th>209</th>
      <td>1.572714</td>
      <td>1.0</td>
      <td>0.841951</td>
      <td>1.00520778069368</td>
    </tr>
  </tbody>
</table>
</div>

```python
df_res_.to_csv('../figures/draft/Fig7-trj_optimal.csv',index=False,header=False)
```

```python

```

scripts/.ipynb_checkpoints/D-2. Field of optimal trajectories for different time horizons [Python]-checkpoint.ipynb


# Lab 9: Hardware-efficient trial states for variational quantum eigensolvers

In this lab, we want to find the ground state energy of the following two-qubit Hamiltonian representing molecular $H_2$ at its equilibrium distance. Note that we have already mapped the fermionic operators onto qubits here.

```
h2_hamiltonian = (-1.0523732)  II +
                 (0.39793742)  IZ +
                 (-0.3979374)  ZI +
                 (-0.0112801)  ZZ +
                 (0.18093119)  XX
```

Note that these co-efficients are functions of the interatomic distance between the atoms. A term like `IZ` is shorthand notation for a tensor product of two-qubit operators -- the identity operator ($I$) on one qubit and pauli-Z operator ($Z$) on the other qubit.

### Installing necessary packages

Before we begin, you will need to install some prerequisites into your environment. Run the cell below to complete these installations. At the end, the cell outputs will be cleared.

```python
!pip install -U -r grading_tools/requirements.txt

from IPython.display import clear_output
clear_output()
```

    Requirement already up-to-date: qiskit-ibmq-provider==0.7 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from -r grading_tools/requirements.txt (line 2)) (0.7.0)
    Requirement already satisfied, skipping upgrade: qiskit-terra==0.14.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.14.0)
    Requirement already satisfied, skipping upgrade: qiskit-aer==0.5.1 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.5.1)
    Requirement already satisfied, skipping upgrade: qiskit-ignis==0.3.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.3.0)
    Requirement already satisfied, skipping upgrade: qiskit-aqua==0.7.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.7.0)
    Requirement already satisfied, skipping upgrade: websockets<8,>=7 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (7.0)
    Requirement already satisfied, skipping upgrade: python-dateutil>=2.8.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2.8.1)
    Requirement already satisfied, skipping upgrade: requests>=2.19 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2.24.0)
    Requirement already satisfied, skipping upgrade: requests-ntlm>=1.1.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.1.0)
    Requirement already satisfied, skipping upgrade: nest-asyncio!=1.1.0,>=1.0.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.3.3)
    Requirement already satisfied, skipping upgrade: urllib3>=1.21.1 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.25.9)
    Requirement already satisfied, skipping upgrade: numpy>=1.13 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.19.0)
    Requirement already satisfied, skipping upgrade: jsonschema>=2.6 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (3.2.0)
    Requirement already satisfied, skipping upgrade: sympy>=1.3 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.6.1)
    Requirement already satisfied, skipping upgrade: psutil>=5 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (5.7.0)
    Requirement already satisfied, skipping upgrade: marshmallow-polyfield<6,>=5.7 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (5.9)
    Requirement already satisfied, skipping upgrade: marshmallow<4,>=3 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (3.6.1)
    Requirement already satisfied, skipping upgrade: retworkx>=0.3.2 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.3.4)
    Requirement already satisfied, skipping upgrade: ply>=3.10 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (3.11)
    Requirement already satisfied, skipping upgrade: dill>=0.3 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.3.2)
    Requirement already satisfied, skipping upgrade: python-constraint>=1.4 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.4.0)
    Requirement already satisfied, skipping upgrade: networkx>=2.2; python_version > "3.5" in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.4)
    Requirement already satisfied, skipping upgrade: scipy>=1.4 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.5.1)
    Requirement already satisfied, skipping upgrade: fastjsonschema>=2.10 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.14.4)
    Requirement already satisfied, skipping upgrade: pybind11>=2.4 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aer==0.5.1->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.5.0)
    Requirement already satisfied, skipping upgrade: cython>=0.27.1 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aer==0.5.1->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.29.20)
    Requirement already satisfied, skipping upgrade: setuptools>=40.1.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-ignis==0.3.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (47.3.1)
    Requirement already satisfied, skipping upgrade: docplex in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.14.186)
    Requirement already satisfied, skipping upgrade: scikit-learn>=0.20.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.23.1)
    Requirement already satisfied, skipping upgrade: dlx in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.0.4)
    Requirement already satisfied, skipping upgrade: cplex; python_version >= "3.6" and python_version < "3.8" in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (12.10.0.2)
    Requirement already satisfied, skipping upgrade: quandl in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (3.5.0)
    Requirement already satisfied, skipping upgrade: h5py in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.10.0)
    Requirement already satisfied, skipping upgrade: fastdtw in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.3.4)
    Requirement already satisfied, skipping upgrade: six>=1.5 in c:\users\codie\appdata\roaming\python\python37\site-packages (from python-dateutil>=2.8.0->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.12.0)
    Requirement already satisfied, skipping upgrade: chardet<4,>=3.0.2 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (3.0.4)
    Requirement already satisfied, skipping upgrade: certifi>=2017.4.17 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2020.6.20)
    Requirement already satisfied, skipping upgrade: idna<3,>=2.5 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from requests>=2.19->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2.9)
    Requirement already satisfied, skipping upgrade: cryptography>=1.3 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2.8)
    Requirement already satisfied, skipping upgrade: ntlm-auth>=1.0.2 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.5.0)
    Requirement already satisfied, skipping upgrade: importlib-metadata; python_version < "3.8" in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.6.0)
    Requirement already satisfied, skipping upgrade: attrs>=17.4.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (19.3.0)
    Requirement already satisfied, skipping upgrade: pyrsistent>=0.14.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from jsonschema>=2.6->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.16.0)
    Requirement already satisfied, skipping upgrade: mpmath>=0.19 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from sympy>=1.3->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.1.0)
    Requirement already satisfied, skipping upgrade: decorator>=4.3.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from networkx>=2.2; python_version > "3.5"->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (4.4.2)
    Requirement already satisfied, skipping upgrade: threadpoolctl>=2.0.0 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2.1.0)
    Requirement already satisfied, skipping upgrade: joblib>=0.11 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.15.1)
    Requirement already satisfied, skipping upgrade: more-itertools in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from quandl->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (8.4.0)
    Requirement already satisfied, skipping upgrade: inflection>=0.3.1 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from quandl->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (0.5.0)
    Requirement already satisfied, skipping upgrade: pandas>=0.14 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from quandl->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (1.0.5)
    Requirement already satisfied, skipping upgrade: cffi!=1.11.3,>=1.8 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (1.14.0)
    Requirement already satisfied, skipping upgrade: zipp>=0.5 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from importlib-metadata; python_version < "3.8"->jsonschema>=2.6->qiskit-terra==0.14.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (3.1.0)
    Requirement already satisfied, skipping upgrade: pytz>=2017.2 in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from pandas>=0.14->quandl->qiskit-aqua==0.7.0->qiskit==0.19->-r grading_tools/requirements.txt (line 1)) (2020.1)
    Requirement already satisfied, skipping upgrade: pycparser in c:\users\codie\appdata\local\programs\python\python37\lib\site-packages (from cffi!=1.11.3,>=1.8->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.7->-r grading_tools/requirements.txt (line 2)) (2.20)

# Hardware efficient trial states

We need to prepare a guess for the ground state of this Hamiltonian. For this exercise, we will construct a hardware efficient trial state of depth `d=2`, with CNOT (`CX`) gates for the entanglers. We will interleave each entangling layer with a set of $R_yR_z$ gates on each qubit (total 4 layers). For the first guess, we will use rotation angles of $\pi/2$ on all the $R_y$ and $R_z$ gates.

```python
from numpy import pi
from qiskit import QuantumCircuit, Aer, execute
from qiskit.visualization import plot_histogram
```

```python
def prepare_hets_circuit(depth, angle1, angle2):

    hets_circ = QuantumCircuit(depth)
    hets_circ.ry(angle1, 0)
    hets_circ.rz(angle1, 0)
    hets_circ.ry(angle1, 1)
    hets_circ.rz(angle1, 1)

    for ii in range(depth):
        hets_circ.cx(0,1)
        hets_circ.ry(angle2,0)
        hets_circ.rz(angle2,0)
        hets_circ.ry(angle2,1)
        hets_circ.rz(angle2,1)

    return hets_circ
```

```python
hets_circuit = prepare_hets_circuit(2, pi/2, pi/2)
hets_circuit.draw()
```

     ┌──────────┐┌──────────┐     ┌──────────┐┌──────────┐     ┌──────────┐»
q_0: ┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├»
     ├──────────┤├──────────┤┌─┴─┐├──────────┤├──────────┤┌─┴─┐├──────────┤»
q_1: ┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├»
     └──────────┘└──────────┘└───┘└──────────┘└──────────┘└───┘└──────────┘»
q_2: ──────────────────────────────────────────────────────────────────────»
                                                                           »
«     ┌──────────┐
«q_0: ┤ RZ(pi/2) ├
«     ├──────────┤
«q_1: ┤ RZ(pi/2) ├
«     └──────────┘
«q_2: ────────────
«

# Measuring expectation values

Next, we measure expectation values. **We will begin by measuring the ZZ expectation value, or $\langle ZZ \rangle$**. We will first create a copy of the `hets_circ` quantum circuit that we created above, and add measurements to it.

```python
def measure_zz_circuit(given_circuit):
    zz_meas = given_circuit.copy()
    zz_meas.measure_all()
    return zz_meas

zz_meas = measure_zz_circuit(hets_circuit)
zz_meas.draw()
```

        ┌──────────┐┌──────────┐     ┌──────────┐┌──────────┐     ┌──────────┐»
   q_0: ┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├»
        ├──────────┤├──────────┤┌─┴─┐├──────────┤├──────────┤┌─┴─┐├──────────┤»
   q_1: ┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├»
        └──────────┘└──────────┘└───┘└──────────┘└──────────┘└───┘└──────────┘»
meas_0: ══════════════════════════════════════════════════════════════════════»
                                                                              »
meas_1: ══════════════════════════════════════════════════════════════════════»
                                                                              »
«        ┌──────────┐ ░ ┌─┐
«   q_0: ┤ RZ(pi/2) ├─░─┤M├───
«        ├──────────┤ ░ └╥┘┌─┐
«   q_1: ┤ RZ(pi/2) ├─░──╫─┤M├
«        └──────────┘ ░  ║ └╥┘
«meas_0: ════════════════╩══╬═
«                           ║
«meas_1: ═══════════════════╩═
«

Next, let's execute this quantum circuit and see the measurement outcomes.

```python
simulator = Aer.get_backend('qasm_simulator')
```

```python
result = execute(zz_meas, backend = simulator, shots=10000).result()
counts = result.get_counts(zz_meas)

plot_histogram(counts)
```

We can analyze the counts and calculate the $\langle ZZ \rangle$ as follows:

```python
def measure_zz(given_circuit, num_shots = 10000):

    zz_meas = measure_zz_circuit(given_circuit)

    result = execute(zz_meas, backend = simulator, shots = num_shots).result()
    counts = result.get_counts(zz_meas)

    if '00' not in counts:
        counts['00'] = 0
    if '01' not in counts:
        counts['01'] = 0
    if '10' not in counts:
        counts['10'] = 0
    if '11' not in counts:
        counts['11'] = 0

    total_counts = counts['00'] + counts['11'] + counts['01'] + counts['10']
    zz = counts['00'] + counts['11'] - counts['01'] - counts['10']
    zz = zz / total_counts

    return zz
```

```python
zz = measure_zz(hets_circuit)
print("<ZZ> =", str(zz))
```

    <ZZ> = 0.0026

**What about $\langle ZI \rangle$ and  $\langle IZ \rangle$? Do these need new circuits?**

The answer is no, and they can be computed from the results above.

```python
def measure_zi(given_circuit, num_shots = 10000):

    zz_meas = measure_zz_circuit(given_circuit)

    result = execute(zz_meas, backend = simulator, shots = num_shots).result()
    counts = result.get_counts(zz_meas)

    if '00' not in counts:
        counts['00'] = 0
    if '01' not in counts:
        counts['01'] = 0
    if '10' not in counts:
        counts['10'] = 0
    if '11' not in counts:
        counts['11'] = 0

    total_counts = counts['00'] + counts['11'] + counts['01'] + counts['10']

    zi = counts['00'] - counts['11'] + counts['01'] - counts['10']
    zi = zi / total_counts

    return zi

def measure_iz(given_circuit, num_shots = 10000):

    zz_meas = measure_zz_circuit(given_circuit)

    result = execute(zz_meas, backend = simulator, shots = num_shots).result()
    counts = result.get_counts(zz_meas)

    if '00' not in counts:
        counts['00'] = 0
    if '01' not in counts:
        counts['01'] = 0
    if '10' not in counts:
        counts['10'] = 0
    if '11' not in counts:
        counts['11'] = 0

    total_counts = counts['00'] + counts['11'] + counts['01'] + counts['10']

    iz = counts['00'] - counts['11'] - counts['01'] + counts['10']
    iz = iz / total_counts

    return iz
```

```python
zi = measure_zi(hets_circuit)
print("<ZI> =", str(zi))

iz = measure_iz(hets_circuit)
print("<IZ> =", str(iz))
```

    <ZI> = 0.0154
    <IZ> = -0.0092

**Next, we measure $\langle XX \rangle$**

```python
def measure_xx_circuit(given_circuit):
    xx_meas = given_circuit.copy()

    ### WRITE YOUR CODE BETWEEN THESE LINES - START
    xx_meas.h(0)
    xx_meas.h(1)
    xx_meas.measure_all()
    ### WRITE YOUR CODE BETWEEN THESE LINES - END

    return xx_meas
```

```python
xx_meas = measure_xx_circuit(hets_circuit)
xx_meas.draw()
```

        ┌──────────┐┌──────────┐     ┌──────────┐┌──────────┐     ┌──────────┐»
   q_0: ┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├┤ RZ(pi/2) ├──■──┤ RY(pi/2) ├»
        ├──────────┤├──────────┤┌─┴─┐├──────────┤├──────────┤┌─┴─┐├──────────┤»
   q_1: ┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├┤ RZ(pi/2) ├┤ X ├┤ RY(pi/2) ├»
        └──────────┘└──────────┘└───┘└──────────┘└──────────┘└───┘└──────────┘»
meas_0: ══════════════════════════════════════════════════════════════════════»
                                                                              »
meas_1: ══════════════════════════════════════════════════════════════════════»
                                                                              »
«        ┌──────────┐┌───┐ ░ ┌─┐
«   q_0: ┤ RZ(pi/2) ├┤ H ├─░─┤M├───
«        ├──────────┤├───┤ ░ └╥┘┌─┐
«   q_1: ┤ RZ(pi/2) ├┤ H ├─░──╫─┤M├
«        └──────────┘└───┘ ░  ║ └╥┘
«meas_0: ═════════════════════╩══╬═
«                                ║
«meas_1: ════════════════════════╩═
«

```python
def measure_xx(given_circuit, num_shots = 10000):

    xx_meas = measure_xx_circuit(given_circuit)

    result = execute(xx_meas, backend = simulator, shots = num_shots).result()
    counts = result.get_counts(xx_meas)

    if '00' not in counts:
        counts['00'] = 0
    if '01' not in counts:
        counts['01'] = 0
    if '10' not in counts:
        counts['10'] = 0
    if '11' not in counts:
        counts['11'] = 0

    total_counts = counts['00'] + counts['11'] + counts['01'] + counts['10']
    xx = counts['00'] + counts['11'] - counts['01'] - counts['10']
    xx = xx / total_counts

    return xx

xx = measure_xx(hets_circuit)
print("<XX> =", str(xx))
```

    <XX> = -1.0

### Now we evaluate the energy of the trial state

```python
def get_energy(given_circuit, num_shots = 10000):

    zz = measure_zz(given_circuit, num_shots = num_shots)
    iz = measure_iz(given_circuit, num_shots = num_shots)
    zi = measure_zi(given_circuit, num_shots = num_shots)
    xx = measure_xx(given_circuit, num_shots = num_shots)

    energy = (-1.0523732)*1 + (0.39793742)*iz + (-0.3979374)*zi + (-0.0112801)*zz + (0.18093119)*xx

    return energy
```

```python
energy = get_energy(hets_circuit)
print("The energy of the trial state is", str(energy))
```

    The energy of the trial state is -1.2236736779439998

# Computing gradients

The objective of the next set of exercises is to compute the next set of angles to use for the trial state preparation. Create two quantum circuits using `prepare_hets_circuit(depth=2, angle2 = pi/2)` for measuring expectation values with values for `angle1 = pi/2 + 0.1*pi/2` and `angle1 = pi/2 - 0.1*pi/2`, and measure the energy of both trial states.

```python
hets_circuit_plus = None
hets_circuit_minus = None

### WRITE YOUR CODE BETWEEN THESE LINES - START
hets_circuit_plus = prepare_hets_circuit(2, pi/2 + 0.1*pi/2, pi/2)

hets_circuit_minus = prepare_hets_circuit(2, pi/2 - 0.1*pi/2, pi/2)
### WRITE YOUR CODE BETWEEN THESE LINES - END

energy_plus = get_energy(hets_circuit_plus, num_shots=100000)
energy_minus = get_energy(hets_circuit_minus, num_shots=100000)

print(energy_plus, energy_minus)
```

    -1.1615004820717998 -1.2806626926606

As you can see, one of these is certainly lower energy than the other, and is also lower energy than the case when `angle1 = pi/2`. This is a suitable next point for our iteration of a variational eigensolver.

# Submitting your solutions for grading

Now, grade your solution by running the cell below after filling in your name and email address. Always provide the same name and email as the one you used during registration to ensure consistency.

```python
name = 'Rohit Prasad'
email = 'rohitprasad231@gmail.com'

### Do not change the lines below
from grading_tools import grade

grade(answer=measure_xx_circuit(hets_circuit), name=name, email=email, labid='lab9', exerciseid='ex1')
grade(answer=hets_circuit_plus, name=name, email=email, labid='lab9', exerciseid='ex2')
grade(answer=hets_circuit_minus, name=name, email=email, labid='lab9', exerciseid='ex3')
```

    Grading...
    lab9/ex1 - 🎉 Correct
    🎊 Hurray! You have a new correct answer! Let's submit it.
    Submitting the answers for lab9...
    📝 Our records, so far, are:
    Correct answers: lab1:ex1, lab2:ex1, lab3:ex1, lab4:ex1, lab5:ex1, lab6:ex1a, lab6:ex1b, lab9:ex1
    Grading...
    lab9/ex2 - 🎉 Correct
    🎊 Hurray! You have a new correct answer! Let's submit it.
    Submitting the answers for lab9...
    📝 Our records, so far, are:
    Correct answers: lab1:ex1, lab2:ex1, lab3:ex1, lab4:ex1, lab5:ex1, lab6:ex1a, lab6:ex1b, lab9:ex1, lab9:ex2
    Grading...
    lab9/ex3 - 🎉 Correct
    🎊 Hurray! You have a new correct answer! Let's submit it.
    Submitting the answers for lab9...
    📝 Our records, so far, are:
    Correct answers: lab1:ex1, lab2:ex1, lab3:ex1, lab4:ex1, lab5:ex1, lab6:ex1a, lab6:ex1b, lab9:ex1, lab9:ex2, lab9:ex3

**Help us improve our educational tools by submitting your code**

If you would like to help us learn how to improve our educational materials and offerings, you can opt in to send us a copy of your Jupyter notebook. By executing the cell below, you consent to sending us the code in your Jupyter notebook. All of the personal information will be anonymized.

```python
from IPython.display import display, Javascript;display(Javascript('IPython.notebook.save_checkpoint();'));
from grading_tools import send_code;send_code('ex1.ipynb')
```

## Bonus 1

While this is not graded, explore whether the decision above would be easy if your execution ran different numbers of shots. In particular, measure `energy_plus` and `energy_minus` again with `100`, `1000` and `10000` shots to explore how easy or difficult this decision gets with each one.

```python
energy_plus_100, energy_plus_1000, energy_plus_10000 = 0, 0, 0
energy_minus_100, energy_minus_1000, energy_minus_10000 = 0, 0, 0

### WRITE YOUR CODE BETWEEN THESE LINES - START

### WRITE YOUR CODE BETWEEN THESE LINES - END

print(energy_plus_100, energy_minus_100, "difference = ", energy_minus_100 - energy_plus_100)
print(energy_plus_1000, energy_minus_1000, "difference = ", energy_minus_1000 - energy_plus_1000)
print(energy_plus_10000, energy_minus_10000, "difference = ", energy_minus_10000 - energy_plus_10000)
```

## Bonus 2

While this is not graded, diagonalize the Hamiltonian by writing down the matrices for the Pauli operators `I`, `X` and `Z`, and find the exact ground state energy.

```python
### WRITE YOUR CODE BETWEEN THESE LINES - START

### WRITE YOUR CODE BETWEEN THESE LINES - END
```


```python
import sympy
from phasor.utilities.ipynb.displays import *
from phasor.utilities.ipynb.ipy_sympy import *
import scipy.linalg

import numpy.testing as np_test
import declarative

from test_SVD import SVD_gen_check, gen_rand_unitary
from phasor.system import DAG_algorithm
from phasor.system import SRE_matrix_algorithms
from phasor.system import scisparse_algorithm

import timeit

asavefig.org_subfolder = 'plots'
```

    Populating the interactive namespace from numpy and matplotlib
    Sympy version:  1.0

```python
from functools import reduce
def SVD_compare_error(
    N = 10,
    length = 10,
    solver =  DAG_algorithm,
    N_in = None,
    N_out = None,
):
    U = gen_rand_unitary(N = N, length = length)
    V = gen_rand_unitary(N = N, length = length)

    seq = dict()
    req = dict()
    edge_map = dict()
    S_diags = []
    for idx in range(N):
        s_diag = 10**(-5 + 10 * np.random.random(length))
        edge_map[idx, idx] = s_diag
        S_diags.append(s_diag)
        seq[idx] = set([idx])
        req[idx] = set([idx])
    S = seq, req, edge_map
    condition = reduce(np.maximum, S_diags) / reduce(np.minimum, S_diags)

    M = SRE_matrix_algorithms.matrix_mult_sre(
        SRE_matrix_algorithms.matrix_mult_sre(U, S), V
    )

    SRE_matrix_algorithms.check_sre(M)
    sparsity = SRE_matrix_algorithms.SRE_count_sparsity(M)
    print("SPARSITY FRAC: ", sparsity)

    if N_in is None:
        inputs_set = set(range(N))
    else:
        inputs_set = set(range(N-N_in, N))
    if N_out is None:
        outputs_set = set(range(N))
    else:
        mid = N_out // 2
        outputs_set = set(range(0, mid)) | set(range(N-(N_out - mid), N))

    Mseq, Mreq, Medge_map = SRE_matrix_algorithms.copy_sre(M)
    print(solver)
    time_start = timeit.default_timer()
    sbunch = solver.inverse_solve_inplace(
        seq = Mseq,
        req = Mreq,
        edge_map = Medge_map,
        inputs_set = inputs_set,
        outputs_set = outputs_set,
        verbose = True,
        negative = False,
    )
    time_end = timeit.default_timer()
    b = declarative.Bunch(
        time = time_end - time_start,
        length = length,
    )
    b.update(sparsity)
    return b
```

```python

```

```python
mats = []
for N in [10, 30, 100, 300]:
    for length in [10, 100, 1000, 10000]:
        for inst in range(3):
            r = SVD_compare_error(
                N = N,
                length = length,
                N_in = 10,
                N_out = 10
            )
            mats.append(r)
```

    SPARSITY FRAC:  {'density_lin': 2.9, 'Nedges': 29, 'Nnodes': 10, 'density_sq': 0.29}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 10
    BADGUY STAGE, REMAINING 10
    SPARSITY FRAC:  {'density_lin': 1.7, 'Nedges': 17, 'Nnodes': 10, 'density_sq': 0.17}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 5
    BADGUY STAGE, REMAINING 5
    SPARSITY FRAC:  {'density_lin': 1.9, 'Nedges': 19, 'Nnodes': 10, 'density_sq': 0.19}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 7
    BADGUY STAGE, REMAINING 7
    SPARSITY FRAC:  {'density_lin': 2.0, 'Nedges': 20, 'Nnodes': 10, 'density_sq': 0.2}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 6
    BADGUY STAGE, REMAINING 6
    SPARSITY FRAC:  {'density_lin': 1.9, 'Nedges': 19, 'Nnodes': 10, 'density_sq': 0.19}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 7
    BADGUY STAGE, REMAINING 7
    SPARSITY FRAC:  {'density_lin': 3.6, 'Nedges': 36, 'Nnodes': 10, 'density_sq': 0.36}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 10
    BADGUY STAGE, REMAINING 10
    SPARSITY FRAC:  {'density_lin': 3.5, 'Nedges': 35, 'Nnodes': 10, 'density_sq': 0.35}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 8
    BADGUY STAGE, REMAINING 8
    SPARSITY FRAC:  {'density_lin': 2.3, 'Nedges': 23, 'Nnodes': 10, 'density_sq': 0.23}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 8
    BADGUY STAGE, REMAINING 8
    SPARSITY FRAC:  {'density_lin': 2.6, 'Nedges': 26, 'Nnodes': 10, 'density_sq': 0.26}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 8
    BADGUY STAGE, REMAINING 8
    SPARSITY FRAC:  {'density_lin': 2.3, 'Nedges': 23, 'Nnodes': 10, 'density_sq': 0.23}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 8
    BADGUY STAGE, REMAINING 8
    SPARSITY FRAC:  {'density_lin': 4.1, 'Nedges': 41, 'Nnodes': 10, 'density_sq': 0.41}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 9
    BADGUY STAGE, REMAINING 9
    SPARSITY FRAC:  {'density_lin': 5.6, 'Nedges': 56, 'Nnodes': 10, 'density_sq': 0.56}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 10
    TRIVIAL STAGE, REMAINING 9
    BADGUY STAGE, REMAINING 9
    SPARSITY FRAC:  {'density_lin': 2.466666666666667, 'Nedges': 74, 'Nnodes': 30, 'density_sq': 0.08222222222222222}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 16
    TRIVIAL STAGE, REMAINING 15
    BADGUY STAGE, REMAINING 15
    SPARSITY FRAC:  {'density_lin': 2.966666666666667, 'Nedges': 89, 'Nnodes': 30, 'density_sq': 0.09888888888888889}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 16
    TRIVIAL STAGE, REMAINING 15
    BADGUY STAGE, REMAINING 15
    SPARSITY FRAC:  {'density_lin': 3.4, 'Nedges': 102, 'Nnodes': 30, 'density_sq': 0.11333333333333333}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 26
    TRIVIAL STAGE, REMAINING 26
    BADGUY STAGE, REMAINING 26
    SPARSITY FRAC:  {'density_lin': 2.6666666666666665, 'Nedges': 80, 'Nnodes': 30, 'density_sq': 0.08888888888888889}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 18
    TRIVIAL STAGE, REMAINING 15
    BADGUY STAGE, REMAINING 15
    SPARSITY FRAC:  {'density_lin': 2.9, 'Nedges': 87, 'Nnodes': 30, 'density_sq': 0.09666666666666666}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 22
    TRIVIAL STAGE, REMAINING 21
    BADGUY STAGE, REMAINING 21
    SPARSITY FRAC:  {'density_lin': 3.1, 'Nedges': 93, 'Nnodes': 30, 'density_sq': 0.10333333333333333}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 22
    TRIVIAL STAGE, REMAINING 20
    BADGUY STAGE, REMAINING 20
    SPARSITY FRAC:  {'density_lin': 4.0, 'Nedges': 120, 'Nnodes': 30, 'density_sq': 0.13333333333333333}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 25
    TRIVIAL STAGE, REMAINING 25
    BADGUY STAGE, REMAINING 25
    SPARSITY FRAC:  {'density_lin': 5.4, 'Nedges': 162, 'Nnodes': 30, 'density_sq': 0.18}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 25
    TRIVIAL STAGE, REMAINING 25
    BADGUY STAGE, REMAINING 25
    SPARSITY FRAC:  {'density_lin': 3.3, 'Nedges': 99, 'Nnodes': 30, 'density_sq': 0.11}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 23
    TRIVIAL STAGE, REMAINING 20
    BADGUY STAGE, REMAINING 20
    SPARSITY FRAC:  {'density_lin': 3.8333333333333335, 'Nedges': 115, 'Nnodes': 30, 'density_sq': 0.12777777777777777}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 21
    TRIVIAL STAGE, REMAINING 20
    BADGUY STAGE, REMAINING 20
    SPARSITY FRAC:  {'density_lin': 3.433333333333333, 'Nedges': 103, 'Nnodes': 30, 'density_sq': 0.11444444444444445}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 25
    TRIVIAL STAGE, REMAINING 25
    BADGUY STAGE, REMAINING 25
    SPARSITY FRAC:  {'density_lin': 3.1666666666666665, 'Nedges': 95, 'Nnodes': 30, 'density_sq': 0.10555555555555556}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 23
    TRIVIAL STAGE, REMAINING 22
    BADGUY STAGE, REMAINING 22
    SPARSITY FRAC:  {'density_lin': 3.39, 'Nedges': 339, 'Nnodes': 100, 'density_sq': 0.0339}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 33
    TRIVIAL STAGE, REMAINING 31
    BADGUY STAGE, REMAINING 31
    SPARSITY FRAC:  {'density_lin': 2.88, 'Nedges': 288, 'Nnodes': 100, 'density_sq': 0.0288}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 45
    TRIVIAL STAGE, REMAINING 44
    BADGUY STAGE, REMAINING 44
    SPARSITY FRAC:  {'density_lin': 3.15, 'Nedges': 315, 'Nnodes': 100, 'density_sq': 0.0315}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 35
    TRIVIAL STAGE, REMAINING 34
    BADGUY STAGE, REMAINING 34
    SPARSITY FRAC:  {'density_lin': 3.53, 'Nedges': 353, 'Nnodes': 100, 'density_sq': 0.0353}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 57
    TRIVIAL STAGE, REMAINING 55
    BADGUY STAGE, REMAINING 55
    SPARSITY FRAC:  {'density_lin': 2.7, 'Nedges': 270, 'Nnodes': 100, 'density_sq': 0.027}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 32
    TRIVIAL STAGE, REMAINING 29
    BADGUY STAGE, REMAINING 29
    SPARSITY FRAC:  {'density_lin': 3.06, 'Nedges': 306, 'Nnodes': 100, 'density_sq': 0.0306}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 26
    TRIVIAL STAGE, REMAINING 24
    BADGUY STAGE, REMAINING 24
    SPARSITY FRAC:  {'density_lin': 3.04, 'Nedges': 304, 'Nnodes': 100, 'density_sq': 0.0304}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 47
    TRIVIAL STAGE, REMAINING 47
    BADGUY STAGE, REMAINING 47
    SPARSITY FRAC:  {'density_lin': 3.22, 'Nedges': 322, 'Nnodes': 100, 'density_sq': 0.0322}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 23
    TRIVIAL STAGE, REMAINING 21
    BADGUY STAGE, REMAINING 21
    SPARSITY FRAC:  {'density_lin': 3.66, 'Nedges': 366, 'Nnodes': 100, 'density_sq': 0.0366}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 57
    TRIVIAL STAGE, REMAINING 55
    BADGUY STAGE, REMAINING 55
    SPARSITY FRAC:  {'density_lin': 3.4, 'Nedges': 340, 'Nnodes': 100, 'density_sq': 0.034}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 34
    TRIVIAL STAGE, REMAINING 33
    BADGUY STAGE, REMAINING 33
    SPARSITY FRAC:  {'density_lin': 3.0, 'Nedges': 300, 'Nnodes': 100, 'density_sq': 0.03}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 19
    TRIVIAL STAGE, REMAINING 17
    BADGUY STAGE, REMAINING 17
    SPARSITY FRAC:  {'density_lin': 3.0, 'Nedges': 300, 'Nnodes': 100, 'density_sq': 0.03}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 11
    TRIVIAL STAGE, REMAINING 9
    BADGUY STAGE, REMAINING 9
    SPARSITY FRAC:  {'density_lin': 3.73, 'Nedges': 1119, 'Nnodes': 300, 'density_sq': 0.012433333333333333}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 123
    TRIVIAL STAGE, REMAINING 122
    BADGUY STAGE, REMAINING 122
    SPARSITY FRAC:  {'density_lin': 3.51, 'Nedges': 1053, 'Nnodes': 300, 'density_sq': 0.0117}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 148
    TRIVIAL STAGE, REMAINING 148
    BADGUY STAGE, REMAINING 148
    SPARSITY FRAC:  {'density_lin': 3.47, 'Nedges': 1041, 'Nnodes': 300, 'density_sq': 0.011566666666666666}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 137
    TRIVIAL STAGE, REMAINING 136
    BADGUY STAGE, REMAINING 136
    SPARSITY FRAC:  {'density_lin': 3.15, 'Nedges': 945, 'Nnodes': 300, 'density_sq': 0.0105}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 87
    TRIVIAL STAGE, REMAINING 85
    BADGUY STAGE, REMAINING 85
    SPARSITY FRAC:  {'density_lin': 3.816666666666667, 'Nedges': 1145, 'Nnodes': 300, 'density_sq': 0.012722222222222222}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 152
    TRIVIAL STAGE, REMAINING 152
    BADGUY STAGE, REMAINING 152
    SPARSITY FRAC:  {'density_lin': 3.3266666666666667, 'Nedges': 998, 'Nnodes': 300, 'density_sq': 0.01108888888888889}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 112
    TRIVIAL STAGE, REMAINING 111
    BADGUY STAGE, REMAINING 111
    SPARSITY FRAC:  {'density_lin': 4.113333333333333, 'Nedges': 1234, 'Nnodes': 300, 'density_sq': 0.013711111111111111}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 172
    TRIVIAL STAGE, REMAINING 172
    BADGUY STAGE, REMAINING 172
    SPARSITY FRAC:  {'density_lin': 3.3866666666666667, 'Nedges': 1016, 'Nnodes': 300, 'density_sq': 0.011288888888888888}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 36
    TRIVIAL STAGE, REMAINING 34
    BADGUY STAGE, REMAINING 34
    SPARSITY FRAC:  {'density_lin': 3.9833333333333334, 'Nedges': 1195, 'Nnodes': 300, 'density_sq': 0.013277777777777777}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 96
    TRIVIAL STAGE, REMAINING 93
    BADGUY STAGE, REMAINING 93
    SPARSITY FRAC:  {'density_lin': 3.816666666666667, 'Nedges': 1145, 'Nnodes': 300, 'density_sq': 0.012722222222222222}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 142
    TRIVIAL STAGE, REMAINING 137
    BADGUY STAGE, REMAINING 137
    SPARSITY FRAC:  {'density_lin': 3.216666666666667, 'Nedges': 965, 'Nnodes': 300, 'density_sq': 0.010722222222222222}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 73
    TRIVIAL STAGE, REMAINING 71
    BADGUY STAGE, REMAINING 71
    SPARSITY FRAC:  {'density_lin': 4.29, 'Nedges': 1287, 'Nnodes': 300, 'density_sq': 0.0143}
    <module 'phasor.system.DAG_algorithm' from '/home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py'>
    TRIVIAL STAGE, REMAINING 164
    TRIVIAL STAGE, REMAINING 161
    BADGUY STAGE, REMAINING 161

    /home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py:645: RuntimeWarning: invalid value encountered in true_divide
      u_mod_edge = edge + norm_orig * edge / abs(edge)
    /home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py:365: RuntimeWarning: invalid value encountered in greater
      rel_r_to_c = np.asarray(np.count_nonzero(normr > normc))
    /home/mcculler/local/home_sync/projects/phasor/phasor/system/DAG_algorithm.py:387: RuntimeWarning: invalid value encountered in greater_equal
      bignodes_r = np.array(rvec) >= 1./(len(req[node]))**.5

```python
axB = mplfigB(Nrows=1)
color_by_len = {
    10 : 'blue',
    100 : 'green',
    1000 : 'purple',
    10000 : 'red',
}
for r in mats:
    axB.ax0.scatter(
        r.Nnodes,
        r.time,
        color = color_by_len[r.length],
    )
axB.ax0.set_xscale('log')
axB.ax0.set_yscale('log')
axB.ax0.set_xlim(9, 400)
axB.save('timing_by_nodes_direct')
```

    figure: plots/timing_by_nodes_direct.png
    [[file:plots/timing_by_nodes_direct.png]]

```python
axB = mplfigB(Nrows=1)
for r in mats:
    axB.ax0.scatter(
        r.Nnodes,
        r.time / r.Nnodes,
        color = color_by_len[r.length],
    )
axB.ax0.set_xscale('log')
axB.ax0.set_yscale('log')
axB.ax0.set_xlim(9, 400)
axB.save('timing_by_nodes_relsq2')
```

    figure: plots/timing_by_nodes_relsq2.png
    [[file:plots/timing_by_nodes_relsq2.png]]

```python
axB = mplfigB(Nrows=1)
for r in mats:
    axB.ax0.scatter(
        r.Nnodes,
        r.time / r.length,
        color = color_by_len[r.length],
    )
axB.ax0.set_xscale('log')
axB.ax0.set_yscale('log')
axB.ax0.set_xlim(9, 400)
axB.save('timing_by_nodes_rellength')
```

    figure: plots/timing_by_nodes_rellength.png
    [[file:plots/timing_by_nodes_rellength.png]]