# Solutions to Mandelbrot Polynomials and Matrices Exercises

1: Write down as many questions as you can, about this section.

What is the area of the Mandelbrot set?  Does it even have an area? What is its perimeter? Does it even have a perimeter? What is its dimension?  What is the fastest way to reliably compute the Mandelbrot set and draw a picture of it?  Are there good free programs to do it?  Is there a three-dimensional version of the Mandelbrot set?  A four-dimensional version? What happens if you have a different power, say $z^3+c$? Or a fractional power, $z^{1/2} + c$? Is there a matrix version (it looks like the iteration $Z^2 + \mathbf{C}$ makes sense for matrices $\mathbf{C}$). What is the most important open question about the Mandelbrot set? Who proved the most about the Mandelbrot set? About Mandelbrot polynomials? What's the _best_ way to solve Mandelbrot polynomials? Are there "Mandelbrot rational functions?" What element or elements of the Mandelbrot set has the largest imaginary part? What kind of number is it/are they (rational, irrational, algebraic, transcendental)?

2:  Read the Wikipedia entry on the Mandelbrot set, and the entry on Mandelbrot.  Mandelbrot's _educational_ books and papers are amazing, and amazingly clearly written.  If you don't come back from this exercise, well, at least you left going in a good direction. (Our answer: welcome back!)

3: Prove by induction that the degree of the $n$th Mandelbrot polynomial $p_k(c)$ is $2^{n-1}$ for $n\ge 2$, and that the leading coefficient is $1$.

The base of the induction is that $p_2(c) = c^2+c$ which has degree $2^{2-1} = 2$ and leading coefficient $1$.  If we then suppose that the degree of $p_k(c)$ is $2^{k-1}$ and its leading coefficient is $1$, then since $p_{k+1} = p_k^2 + c$ we have deg $p_k = 2\cdot2^{k-1} = 2^k$ and the leading term is $c$ to this power so the leading coefficient is $1$.

4: Prove that $c=-2$ and $c=0$ are both in the Mandelbrot set. Show that all zeros of Mandelbrot polynomials give _periodic orbits_ under the Mandelbrot iteration and are thus in the Mandelbrot set.

a) If $c=0$ then all $z_k = 0$ thereafter, which is bounded; so $c=0$ is in the Mandelbrot set.  b) If $c=-2$ then $z_0 = 0$, $z_1 = -2$, $z_2 = -2 + 4 = 2$, $z_3 = 4 -2 = 2$, and indeed $z_k = 2$ for all $k\ge 2$.  So $c=-2$ is in the Mandelbrot set. c) If $z_k(\xi) = 0$, then $z_{k+1}(\xi) = z_k^2(\xi) + \xi = \xi$ and this is also $z_1(\xi)$; we are clearly repeating. Counting Python-style from zero, the entities $z_0$, $z_1$, $\ldots$, $z_{k-1}$ might have been different but $z_k = z_0$ so the cycle has period $k$.  Notice that if $m | k$ (that is, $m$ divides $k$) it _might_ be true that $z_m(\xi) = 0$ as well, in which case this point has a lower period as well.  Notice that all points of period $2$ are also of period $4$, period $6$, and so on; but that there are points of period $4$ which are not points of period $2$.  This means we can _factor_ Mandelbrot polynomials of composite order, that is, if their $k$ is a composite integer.

5: Is $i = \sqrt{-1}$ in the Mandelbrot set?

$z_0 = 0$, $z_1 = i$, $z_2 = i + i^2 = i -1$, $z_3 = (i-1)^2 + i = -1 -2i + 1 + i = -i $, $z_4 = -1 + i$, $z_5 = 1 -2i +i^2 + i = -i$ and we are repeating, so yes, $i$ is in the Mandelbrot set.

6: Prove that the Mandelbrot polynomials are _unimodal_.  At the time we write this, this question is open: solve it, and you could publish a paper with your proof ([Maple Transactions](https://www.mapletransactions.org) would be a good place).  The word "unimodal" just means that the size of the coefficients increases to a peak, and then decays again.  As in $z_4$, the peak might be attained by two coefficients, not just one.  Seriously, we don't know how to prove this.  We think it's true, though.

Seriously, we couldn't do this one.  Your Answer Goes Here.  We'd really like to see it.

7: Solve $z_3 = 0$ by hand, as follows.  First, divide out the visible root, $c=0$, to get the cubic equation $0 = 1 + c + 2c^2 + c^3$.  Then put $c = \xi - 2/3$ so $c^3 = (\xi -2/3)^3 = \xi^3 - 2\xi^2 + 4\xi/9 - 8/27$ which will transform the equation into a cubic in $\xi$ of the form $0 = q + p\xi + \xi^3$.  Then put $\xi = u + v$ (introducing two new variables seems like the opposite of progress, but trust us, it will help).  This transforms the equation to $(u+v)^3 + p(u+v) + q = 0$, or $u^3 + 3uv(u+v) + p(u+v) + q = 0$.  Show that if you can find $u$ and $v$ that simultaneously solve $3uv + p = 0$ and $u^3 + v^3 + q = 0$ then you can solve the original equation.  Do so.  Your formula at the end should give you three, and only three, roots. Write out your answer explicitly, and compare it with (say) the answer from Wolfram Alpha. There is an analogous formula for quartics, but even teaching this cubic formula is considered cruel and inhumane; well, it would be if you were to be tested on it.  But maybe a [public competition for solving cubics](https://en.wikipedia.org/wiki/Cubic_equation) might be entertaining for the spectators, as it seems to have been in 1530.  Quite amazingly, at least to him, RMC had recent recourse to this method in order to write a very fast program to evaluate the cube roots of an equation that depended on a parameter, except that the equation was of the form $x^3 + mx^2 + n=0$ (which was apparently the type that defeated Fior in the contest—but just solve for $1/x$ instead, and that puts it into the previous form).  All modern computer algebra systems know how to solve cubics and quartics in terms of radicals; quintics cannot, in general, be solved in terms of radicals, but they can be solved in terms of _elliptic functions_.  Now there's a rabbit hole for you.

We really did this one, but there's not much reason to type it out.  You can check your own answers by evaluating all three of them in floating-point and running the Mandelbrot iteration on them.  All three should have $z_3(c)$ approximately zero.

8: Prove by induction that the code MandelbrotWithDerivative computes the correct derivative of every Mandelbrot polynomial. Rather, that it _would_ compute the correct derivative if the underlying arithmetic were exact. You might need what is known as a _loop invariant_: something that is true before the loop starts, and is true at the start of every iteration, and at the end of every iteration (though not necessarily true after each intermediate step of the iteration). But this loop is simple enough that a direct induction also works.

```python
def MandelbrotWithDerivative(n: int, c):
    dz = 0*c
    z = 0*c # try to inherit the type of c
    for i in range(n):
        dz = 2*z*dz + 1 # A bit iffy to add constants to Polynomials in Python, but maybe it works. Yes! It does!
        z = z*z + c
    return(z,dz)
```

Before the loop begins, both $z = z_0 = 0$ and $dz = z_0'(c) = 0$.

A loop invariant just after the : is that z $= z_{i-1}(c)$ and dz $= z_{i-1}'(c)$.

After the statement
```python
dz = 2*z*dz + 1
```
we have dz = $z_{i}'(c) = 2z_{i-1}(c)\cdot z_{i-1}'(c)+1$ because $d/dc$ applied to $z_i = z_{i-1}^2 + c$ gives exactly that.

After the statement
```python
z = z*z + c
```
we have z $ = z_i(c)$ by definition.  So at the end of the loop, we have z $= z_{i}(c)$ and dz $= z_{i}'(c)$. Incrementing $i$ and going to the top of the loop we satisfy the loop invariant.

The loop terminates if i=n at the top, and the code returns $z_i(c)$ and $z_i'(c)$ where $i=n$.

9: Are the zeros of the derivatives of the Mandelbrot polynomials in the Mandelbrot set? We think so, but we haven't written out the proof.

We seem to remember a kind of "mean value theorem" for complex numbers that says that zeros of the derivative can be found in the convex hull of the zeros.  We also seem to remember that the Mandelbrot set is connected.  Something might be do-able along these lines.  As we said, we have not proved this (and it might not be true).

10: "A good numerical method gives you the exact solution to a nearby problem".  This is an informal definition of _numerical stability_.  An _algorithm_ is "numerically stable" if it gives you the exact solution, not to the problem you were trying to solve (say the polynomial $z(c) = 0$) but instead to a _nearby_ problem, say $z(c) - 10^{-16}i = 0$.  Some problems, however, are _sensitive to changes_ (in the vernacular of numerical analysis, _ill-conditioned_).  Consider the polynomial
$z(c) = \sum_{k=0}^d a_k c^k$
and a _perturbed_ (changed) polynomial
$z(c) + \Delta(c) = \sum_{k=0}^d a_k(1+s_k) c^k$
where the coefficients $a_k$ have all been changed by small relative amounts $s_k$.  Suppose further that all $|s_k| \le t$, some tiny number. By using the triangle inequality, show that
$
| \Delta(c) | \le K(c) t
$
where
$
K(c) = \sum_{k=0}^d |a_k| |c|^k
$
This number $K(c)$ serves as a kind of _condition number_ for the polynomial: the larger it is, the more sensitive the polynomial is.  Plot the condition numbers (on a log scale!) of the Mandelbrot polynomials $z_6(c)$ and $z_7(c)$ on $-2 \le c \le 0$.

The proof runs along the lines suggested in the exercise and [can be seen at this video](https://youtu.be/H8VwEQfRRvI).  For the graph, see Figure 2 in [Some Facts and Conjectures on Mandelbrot Polynomials](https://doi.org/10.5206/mt.v1i1.14037)

11: __Pseudozeros__.  The zeros of $z(c) - 10^{-k}\exp(i\theta)$ for $k=6$, say, represent a _curve_ in $c$ space (let $\theta$ vary from $-\pi$ to $\pi$ so the tiny perturbation $10^{-6}\exp(i\theta)$ turns a full circle in the complex plane). As $k$ is taken larger, one expects these curves to surround the zeros of $z(c)$ itself.  Graph some pseudozeros of a Mandelbrot polynomial.  Animate them.

```python
import numpy as np
import sympy as sym
import matplotlib.pyplot as plt
from numpy import linalg as LA
import cmath
import time
import scipy.linalg
```

```python
def p_mand(z, n):
    p = 0
    for i in range(n):
        p = z*p*p + 1
    return(p)
```

```python
# Exercise 10: Pseudozeros (with explicit perturbation and contours)
n = 5
N = 250
e = 0.001

z = sym.Symbol('z')
p5 = p_mand(z, n)
p5_coeffs = sym.Poly(p5, z).coeffs()

fig = plt.figure(figsize=(10, 8), dpi = 80)
plt.xlabel('Re(z)')
plt.ylabel('Im(z)')
for i in range(N):
  perturb_vector = (1 + np.random.rand(1, 2**(n-1))*e).tolist()[0]
  perturb_coeffs = [a*b for a,b in zip(p5_coeffs, perturb_vector)]
  pn_soln_perturbed = np.roots(perturb_coeffs)
  X = [np.real(x) for x in pn_soln_perturbed]
  Y = [np.imag(x) for x in pn_soln_perturbed]
  plt.scatter(X, Y, s = 1, c = 'black')

nx, ny = (1500, 1500)
x = np.linspace(-2.5, .5, nx)
y = np.linspace(-1.25, 1.25, ny)
[X, Y] = np.meshgrid(x, y, indexing='ij')
Z = np.zeros((nx, ny))

for i in range(nx):
  for j in range(ny):
    Z[i, j] = abs(p_mand(complex(X[i, j], Y[i, j]), n))/p_mand(abs(complex(X[i, j], Y[i, j])), n)

plt.contour(X, Y, Z, [e])
plt.show()
```

12: Write $\mathbf{C} = \mathbf{A}\mathbf{B}^{-1}$ and show that the generalized eigenvalues of $\mathbf{A}-\lambda\mathbf{B}$ are the simple eigenvalues of $\mathbf{C} - \lambda\mathbf{I}$.  Find $\mathbf{C}$ for the Mandelbrot matrices above, and code it up and compare the times to compute simple eigenvalues versus generalized eigenvalues. How high a degree can you get to?  We stopped at $N=16$, where the computation took about an hour and a half, and the residual was about $4.0e-5$.  The current record is held by [MPSolve](https://numpi.dm.unipi.it/software/mpsolve) with $k=23$, so over $4$ million roots (using multiple precision); we don't think eigenvalue methods can get that high, but we think [homotopy methods can](https://ir.lib.uwo.ca/etd/4028/), although they haven't yet.

We gave part of our answer in the question.  Here's some code.

```python
# This matrix gives a _simple_ eigenvalue problem for Mandelbrot polynomials: see problem 11 above.
def m_mand(n):
    M = np.array([-1])
    for i in range(2, n):
        temp = M
        d = 2**(i) - 1
        M = np.zeros((d, d))
        d = 2**(i-1) - 1
        M[0:d, 0:d] = temp
        M[d+1:, d+1:] = temp
        M[d, d-1] = -1
        M[d+1, d] = -1
        M[0, -1] = -1
    return(M)
```

```python
N = 12
times = []
for i in range(3, N+1):
    M = m_mand(i)
    tic = time.perf_counter()
    LA.eig(M)
    toc = time.perf_counter()
    times.append(toc - tic)

# Should also plot line of best fit and find slope
plt.scatter(np.power(2, list(range(2, N))) - 1, times)
plt.yscale('log')
plt.xscale('log')
plt.show()
```

13: __Pseudospectra__. Eigenvalues of perturbed matrices are called _pseudospectra_ (the set of eigenvalues themselves is called the _spectrum_).  $\Lambda_\varepsilon(\mathbf{A}) := \{z : \exists \mathbf{E} \backepsilon \mathrm{det}(z\mathbf{I} - (\mathbf{A}+\mathbf{E})) = 0 \& \|\mathbf{E}\| \le \varepsilon\}$. That definition means, in words, that the pseudospectrum at size $\varepsilon$ is the set of complex numbers $z$ that are the exact eigenvalues of matrices perturbed by another matrix $\mathbf{E}$ which is at most $\varepsilon$ in norm.  Check out the [Pseudospectra gateway](https://www.cs.ox.ac.uk/pseudospectra/) and then do some pseudospectral experiments with Mandelbrot matrices.

```python
# Pseudospectra of the simple eigenvalue problem, for problem 12
n = 7
M7 = m_mand(n)

nx, ny = (100, 100)
x = np.linspace(-2.5, 0.5, nx)
y = np.linspace(-1.25, 1.25, ny)
[X, Y] = np.meshgrid(x, y, indexing='ij')
Z = np.zeros((nx, ny))

for i in range(nx):
  for j in range(ny):
    _, temp, _ = scipy.linalg.svd(complex(X[i, j], Y[i, j])*np.eye(2**(n-1)-1) - M7)
    Z[i, j] = temp[-1]

fig = plt.figure(figsize=(10, 8), dpi = 80)
plt.contour(X, Y, Z, np.logspace(-5, 0, 15))
plt.show()
```

14: Other families of polynomials like the Mandelbrot polynomials can be defined: Fibonacci--Mandelbrot polynomials defined by $q_{n+1}(z) = z q_n(z) q_{n-1}(z) + 1$ and $q_0(z)=0$ and $q_1(z) = 1$, for instance; or Fibonacci--Narayana polynomials.  Define your own polynomials by recurrence relation, and find your own matrices whose eigenvalues are their roots, and draw their eigenvalues.  We have only done a few of these, and there are infinitely many to choose from.  It's possible your results will be completely new (and therefore publishable).

See [Eunice Chan's Master's Thesis](https://ir.lib.uwo.ca/etd/4028/)

15: The paper [Some Facts and Conjectures on Mandelbrot Polynomials](https://doi.org/10.5206/mt.v1i1.14037) contains a formula for the _Mandelbrot Generating Function_ MGF(k,c), which _analytically solves_ the Mandelbrot iteration $z_{n+1} = z_n^2 + c$, at least for $c$ _outside_ the Mandelbrot set.  This means that we could (say) take half an iteration—and find $z_{1/2}(c)$ or $z_{3/2}(c)$.  As with [Stand-Up Math's Video on Complex Fibonacci Numbers](https://youtu.be/ghxQA3vvhsk) these are likely to be _complex numbers_.  So far as we know, no-one (not even us) has computed these.  Do so, for (say) $c=1$ or $c=2$.  Plot the results.  This, also, might be publishable (again, [Maple Transactions](https://www.mapletransactions.org) might be a good place).

This could be very interesting.  We look forward to your solution!

16: A [companion matrix](https://en.wikipedia.org/wiki/Companion_matrix) for a polynomial (say $P(\lambda)$) is simply a matrix $C$ whose eigenvalues are the roots of $P(\lambda)$.  Every matrix has a characteristic polynomial whose roots are its eigenvalues; what's interesting is that this can be run the other way (it's a bit more complicated because "Frobenius companion matrices" as in the link above have special properties).  But companion matrices are not unique: any matrix similar to a companion matrix $\mathbf{S}\mathbf{C}\mathbf{S}^{-1}$ is also a companion matrix for that same $P(\lambda)$.  Suppose that you have a polynomial with _integer_ coefficients.  Then the Frobenius companion matrix has (those same) integers as entries, together with ones and zeros.  Therefore, out of all integer companion matrices for $P(\lambda)$, there must be one with _minimal height_: the height of the matrix is the size of the largest absolute value of any entry in the matrix.  Mandelbrot matrices as defined above have height $1$, which is the smallest possible for integer companions, and are therefore "minimal height".  Note that the size of the coefficients of the polynomial are exponential in the degree! So the height of the Frobenius companion matrix of Mandelbrot polynomials is likewise exponential.  Not much is known about [minimal height companion matrices](https://link.springer.com/article/10.1007/s11786-018-0364-2) (see also [our first paper](https://journals.uwyo.edu/index.php/ela/article/view/1781)); we do not have an algorithm for finding one for a given integer polynomial, for instance. We _believe_ that minimal height companion matrices are generally better-conditioned as far as their eigenvalues, but we have no proof.  Find a minimal height companion matrix for (say) $\lambda^3 + 5\lambda^2 + 3\lambda + 1$.  Find a general algorithm for computing minimal height companions.  Prove that they're useful.  If you succeed at this problem, you should certainly publish your results; a good venue might be the [Electronic Journal of Linear Algebra](https://journals.uwyo.edu/index.php/ela).

Similarly, we look forward to your solution!

book/Solutions/Solutions to Mandelbrot Exercises.ipynb


# Third Party Libraries With Rich Output

A number of third party libraries defined their own custom display logic. This gives their objcts rich output by default when used in the Notebook.

```python
from IPython.display import display
```

## Pandas

[Pandas](http://pandas.pydata.org/) is a data analysis library for Python. Its `DataFrame` objects have an HTML table representation in the Notebook.

```python
import pandas
```

Here is a small amount of stock data for APPL:

```python
%%writefile data.csv
Date,Open,High,Low,Close,Volume,Adj Close
2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50
2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26
2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48
2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99
2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12
2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53
```

    Writing data.csv

Read this as into a `DataFrame`:

```python
df = pandas.read_csv('data.csv')
```

And view the HTML representation:

```python
df
```

      Date
      Open
      High
      Low
      Close
      Volume
      Adj Close

      0
       2012-06-01
       569.16
       590.00
       548.50
       584.00
       14077000
       581.50

      1
       2012-05-01
       584.90
       596.76
       522.18
       577.73
       18827900
       575.26

      2
       2012-04-02
       601.83
       644.00
       555.00
       583.98
       28759100
       581.48

      3
       2012-03-01
       548.17
       621.45
       516.22
       599.55
       26486000
       596.99

      4
       2012-02-01
       458.41
       547.61
       453.98
       542.44
       22001000
       540.12

      5
       2012-01-03
       409.40
       458.24
       409.00
       456.48
       12949100
       454.53

## SymPy

[SymPy](http://sympy.org/) is a symbolic computing library for Python. Its equation objects have LaTeX representations that are rendered in the Notebook.

```python
from sympy.interactive.printing import init_printing
init_printing(use_latex='mathjax')
```

```python
from __future__ import division
import sympy as sym
from sympy import *
x, y, z = symbols("x y z")
k, m, n = symbols("k m n", integer=True)
f, g, h = map(Function, 'fgh')
```

```python
Rational(3,2)*pi + exp(I*x) / (x**2 + y)
```

$$\frac{3 \pi}{2} + \frac{e^{i x}}{x^{2} + y}$$

```python
a = 1/x + (x*sin(x) - 1)/x
a
```

$$\frac{1}{x} \left(x \sin{\left (x \right )} - 1\right) + \frac{1}{x}$$

```python
(1/cos(x)).series(x, 0, 6)
```

$$1 + \frac{x^{2}}{2} + \frac{5 x^{4}}{24} + \mathcal{O}\left(x^{6}\right)$$

## Vincent

[Vincent](https://vincent.readthedocs.org/en/latest/) is a visualization library that uses the [Vega](http://trifacta.github.io/vega/) visualization grammar to build [d3.js](http://d3js.org/) based visualizations in the Notebook and on http://nbviewer.jupyter.org. `Visualization` objects in Vincetn have rich HTML and JavaSrcript representations.

```python
import vincent
import pandas as pd
```

```python
import pandas.io.data as web
import datetime
all_data = {}
date_start = datetime.datetime(2010, 1, 1)
date_end = datetime.datetime(2014, 1, 1)
for ticker in ['AAPL', 'IBM', 'YHOO', 'MSFT']:
    all_data[ticker] = web.DataReader(ticker, 'yahoo', date_start, date_end)
price = pd.DataFrame({tic: data['Adj Close']
                      for tic, data in all_data.items()})
```

```python
vincent.initialize_notebook()
```

```python
line = vincent.Line(price[['AAPL', 'IBM', 'YHOO', 'MSFT']], width=600, height=300)
line.axis_titles(x='Date', y='Price')
line.legend(title='Ticker')
display(line)
```

IPython Kernel/Third Party Rich Output.ipynb


# Neutron transport in H slab

Please indicate your name below, since you will need to submit this notebook completed latest the day after the datalab.

Don't forget to save your progress during the datalab to avoid any loss due to crashes.

```python
name=''
```

In this experiment we will put together our previous knowledge to track neutrons in a homogeneous medium filled with liquid hydrogen. We will track neutrons in 3-dimensions (although investigate the spatial dependence of the flux in 1 dimension only, and we will consider that the space is infinite in $y$ and $z$ directions, and finite in $x$). This is not a very realistic problem, and it is also not a practically relevant one, however it provides an excellent opportunity to us to see how the pieces of neutron transport can be put together. Since the physics is very simple in this case, our approximations are more or less valid:
- there is only two reaction on H-1: scattering and capture
- there are no resonances in the H-1 cross sections.
- scattering can be considered elastic and isotropic in CoM almost for every neutron energy
- we consider that H atoms are at rest (ie. temperature is 0K), so we do not need to consider any upscattering.
- we even neglect any molecular bounds between H atoms.

We are not going to write too much code here, because in fact we have already done most of the necessary preparation during the previous datalabs.

We will consider that the density of the liquid is 0.07085 g/cm3, and there is a 3 MeV neutron point source placed at $x=0.0,y=0.0,z=0.0$.

Our goal will be to plot the trajectories of neutrons and to estimate the flux vs the x coordinate (although, one could argue that for a point source we should measure the flux vs the radial distance from the source since the problem is spherical).

```python
import numpy as np
import matplotlib.pyplot as plt
```

##  XS

### Microscopic  XS

First we obtain the microscopic cross sections for scattering and capture. We can see that the scattering is nearly constant at epithermal energies, and the capture reaction is also very smooth. We can also notice that most of the reactions are going to be scattering reactions.

```python
xsscatter=np.loadtxt('05b-xs_Hscatter.dat',skiprows=2)
Es=xsscatter[:,0]
XSs=xsscatter[:,1]

xscapture=np.loadtxt('05b-xs_Hcapture.dat',skiprows=2)
Ec=xscapture[:,0]
XSc=xscapture[:,1]

plt.figure()
plt.loglog(Es,XSs,label='scatter')
plt.loglog(Ec,XSc,label='capture')
plt.xlabel('Energy (eV)')
plt.ylabel('XS (barn)')
plt.legend()
plt.show()
```

### Macroscopic XS

Now, we will multiply with the atom density to obtain the macroscopic cross sections.

We will consider that our liquid is made of H atoms. Although, we know that it is not the case in reality.

```python
density = 0.07085 #g/cm3
A = 1
Numdens = density * 6.022E23 / A #let's ignore that it is H2 molecule

MXSc= XSc * Numdens*1e-24
MXSs= XSs * Numdens*1e-24
```

## Path to next collision

In the previous datalab we saw that

$\exp(-\Sigma_t x)$ is the probability that a neutron moves a distance dx without any interaction.

and

$\Sigma_t \exp(-\Sigma_t x)dx$ is the probability that the neutron has its interaction in dx.

So

$p(x)=\Sigma_t \exp(-\Sigma_t x)$

Thus

$F(x)=1-\exp(\Sigma_tx)$

If we take the inverse, to sample a random path

$x=-\frac{\ln(1-r)}{\Sigma_t}$

but if r is uniform over $[0,1)$, than $1-r$ is also uniform over $[0,1)$, so this simplifies to

$x=-\frac{\ln r}{\Sigma_t}$

**Note** speed is everything in MC calculations. Although here we don't tried to avoid every unecessary operation, but this example is just to highlight, that sometimes operations can be avoided with some reasoning.

So we can define the `distanceToCollision` function to sample a distance between two collision sites.

```python
def distanceToCollision(SigT,N=1):
    x=np.random.uniform(0,1,N)
    return -np.log(x)/SigT
```

Let's play a bit with this one. Between 1-10000 eV the scattering cross section is still more or less constant, and several order of magnitude larger than the capture cross section, so for these energies we do not expect to have large differences between mean free path. For faster energies (eg. 1 MeV) we see a longer distance.

```python
fig, axs = plt.subplots(2, 2, figsize=(10,7))
fig.subplots_adjust(hspace=.7)
fig.subplots_adjust(wspace=.3)
for i,E in enumerate([1e0,1e2,1e4,1e6]):
    SigS=np.interp(E,Es,MXSs)
    SigC=np.interp(E,Ec,MXSc)
    SigT=SigS+SigC
    print('Energy: {} eV, Total XS: {} 1/cm'.format(E,SigT))
    ds=distanceToCollision(SigT,10000)
    axs[int(i>=2), i%2].hist(ds,50)
    axs[int(i>=2), i%2].set_xlabel('distance between collision (cm)')
    axs[int(i>=2), i%2].set_ylabel('occurance')
    axs[int(i>=2), i%2].set_title(
        '@%.1e eV is \n Empirical mfp %.2f cm \n Theoretical mfp is %.2f cm '%(E,np.mean(ds),1/SigT))
```

## Reaction type

The probability of reaction $i$ happening at energy $E$ is

\begin{equation}
\frac{\Sigma_i(E)}{\Sigma_t(E)}
\end{equation}

In our example only two reactions might happen: scattering or capture, So a simple condition can be used to decide which happens. Note that the function would not necessarily require $\Sigma_t$ as input. We also saw that `np.random.choice` could handle this for us.

```python
def reactionType(SigS,SigC,SigT): #TODO: more generic for any number of reactions.
    x=np.random.uniform(0,1)
    if x < SigS/SigT:
        return 'scatter'
    else:
        return 'capture'
```

## Scattering and directions

During developing the theory of elastic scattering, we have assumed that scattering is isotropic in the Center-of-Mass frame, and we found a relation between the CM and LAB angles:

\begin{equation}
\tan \theta_L = \frac{\sin \theta_C}{\frac{1}{A}+\cos \theta_C}
\end{equation}

It was also derived how the outgoing neutron energy depends on the incoming energy and the scattering cosine:

\begin{equation}
E_f=\Big[\frac{(1+\alpha)+(1-\alpha)\cos \theta_C}{2}\Big]E_i
\end{equation}

Now we implement the `elasticScatter()` function which will sample the outgoing LAB energy for a neutron at a certain ingoing LAB energy.

```python
def elasticScatter(E):
    muC=np.random.uniform(-1,1)
    thetaC=np.arccos(muC)
    E=(((1+alpha)+(1-alpha)*muC)/2)*E
    thetaL=np.arctan2(np.sin(thetaC),((1/A)+muC))
    muL=np.cos(thetaL)
    return E, muL
```

When the neutron is born we assume it is being emitted isotropically from the source, thus we need a function which samples random directions. For the random directions we have to keep in my, that it is not theta which is uniformly distributed, but the cosine of the angle, as we saw during the previous datalab.

For transforming the directions of a neutron after scattering we can use the following formulae (from https://docs.openmc.org/en/v0.10.0/methods/physics.html), which is just based on coordinate transformation.

\begin{equation}
u' = \mu u + \frac{\sqrt{1 - \mu^2} ( uw \cos\phi - v \sin\phi )}{\sqrt{1 -
w^2}}
\end{equation}
\begin{equation}
v' = \mu v + \frac{\sqrt{1 - \mu^2} ( vw \cos\phi + u \sin\phi )}{\sqrt{1 -
w^2}}
\end{equation}
\begin{equation}
w' = \mu w - \sqrt{1 - \mu^2} \sqrt{1 - w^2} \cos\phi
\end{equation}

```python
def randomDir():
    mu=np.random.uniform(-1,1)
    theta=np.arccos(mu)
    phi=np.random.uniform(0,2*np.pi)

    u=np.sin(theta)*np.cos(phi)
    v=np.sin(theta)*np.sin(phi)
    w=np.cos(theta)
    return np.array([u,v,w])

def transformDir(u,v,w,mu):
    """
    transform coordinates according to openMC documentation.
    TODO: could be updated to receive a direction array

    Parameters
    ----------
    u : float
        Old x-direction
    v : float
        Old y-direction
    w : float
        Old z-direction
    mu : float
        Lab cosine of scattering angle
    """
    phi=np.random.uniform(0,2*np.pi)
    un=mu*u+(np.sqrt(1-mu**2)*(u*w*np.cos(phi)-v*np.sin(phi)))/(np.sqrt(1-w**2))
    vn=mu*v+(np.sqrt(1-mu**2)*(v*w*np.cos(phi)+u*np.sin(phi)))/(np.sqrt(1-w**2))
    wn=mu*w-np.sqrt(1-mu**2)*np.sqrt(1-w**2)*np.cos(phi)
    return np.array([un,vn,wn])
```

## Flux scoring

The flux can be interpreted as the total distance traveled by neutrons in a volume. That said, it can be estimated by summing all the distances traveled by neutrons in a volume (this is called *track-length estimator*). However, in our case we would like to obtain the space dependence of the flux, thus the knowledge of traveled disctance in slices of the slab would be required. For which one needs to know where a certain particle crosses the surface dividing volumes. This is not a difficult task (one only needs to find the intersection of a line and a plane/surface), and for heterogeneous geometries (ie. which are built of regions filled with different materials) it is anyhow required to keep track of such events.

However for the current demonstration we do not wish to implement such ray tracing methodology. For us the simpler choice is to use the definition of flux through the definition of reacton rates and use a *collision estimator*.

$\phi = \frac{1}{W} \sum_{i \in C} \frac{w_i}{\Sigma_t (E_i)}$,

where $w_i$ are the weights of the particles (at the time of the reaction), and $W$ is the total weight. This plays a role only in more advanced Monte Carlo methods. For us, $w_i=1$ always, thus $W$ will be the number of simulated particles. So in fact we will need to add $1/(N\Sigma_t)$ at every collision event, and in case we would like to get the spatial dependence of the flux, we can score this into some predefined space bins. The final value of the estimator will give the flux per source particle. If one is interested in the total physical flux, the results should be renormalized with the source rate.

# Main

Now it is time to put everything together! Let's break down neutron transport of one single neutron into a flowchart:

This needs to be repeated for the number of neutrons which allows us to get a converged estimate of the flux. The real value of MC based techniques is apparent: in case the trajectories are independent from each other, they can be evaluated in parallel (and to some extent this is true even when the trajectories are dependent such as for secondary neutrons emerging from fission events).

For the moment we will not bother with functions or classes (although clearly it would make a much better code, just think about the elegance how we interacted with the `Tree` objects), but only plainly put this algorithm into code.

We documented the program with comments below.

```python
from mpl_toolkits.mplot3d import Axes3D

N=500
D=100 #cm half width of the 1-dimensional slab
E0=3e6 #eV
A=1
alpha=(A-1)**2/(A+1)**2

#x coordinates and flux initialization
#we will score in 201 bins along the x-axis
x=np.linspace(-D,D,201)
flux=np.zeros(len(x))

#We create the canvas for a 3D plot of the trajectories
fig = plt.figure(figsize=plt.figaspect(1.0)*1.5) #Adjusts the aspect ratio and enlarges the figure (text does not enlarge)
ax = fig.gca(projection='3d')

for i in range(N): #for every neutron
    #we initializes lists to store the coordinates of the neutron along its trajectory
    Xs=[]
    Ys=[]
    Zs=[]

    #we initialize the neutron energy
    E=E0

    #we sample a random source location on a plane x=0.0, y and z is between -500 and 500. This is an arbitrary choice
    #we just picked a large number, considering that the slab is infinite in y and z direction.
    #this would be a planar source: coord=np.array([0.0,np.random.uniform(-500.0,500.0),np.random.uniform(-500.0,500.0)]) #plate
    coord=np.array([0.0,0.0,0.0]) #point source

    #we sample a random initial direction the neutron travels to
    direction=randomDir()

    #now we track the neutron until it dies. We could pick a slighly higher energy condition than 0.0.
    #in case more nuclides are present one needs an other step to sample nuclide
    while E>0.0:
        #GET the macroscopic cross sections at energy E
        SigS=np.interp(E,Es,MXSs)
        SigC=np.interp(E,Ec,MXSc)
        SigT=SigS+SigC
        #GET distance to collision
        dist=distanceToCollision(SigT)
        #STORE the locations for the trajectory
        Xs.append(coord[0])
        Ys.append(coord[1])
        Zs.append(coord[2])
        #COORDINATE OF NEXT COLLISION
        coord=coord + dist*direction
        #TYPE OF NEXT COLLISION
        rtype=reactionType(SigS,SigC,SigT)
        if np.abs(coord[0])>D:  #if the x coordinate is larger than the half width: leakage
            break
        elif rtype=='capture':
            #if neutron captured we break out from the while loop
            #and score to the flux estimator
            flux[np.digitize(coord[0],x)]=flux[np.digitize(coord[0],x)]+1/SigT/N
            break
        else:
            #if neutron scattered we score to the flux estimate
            #and calculate the direction after the scattering event
            flux[np.digitize(coord[0],x)]=flux[np.digitize(coord[0],x)]+1/SigT/N
            E,muL=elasticScatter(E)
            direction=transformDir(direction[0],direction[1],direction[2],muL)

    #we plot the trajectory of the given neutron. We could store the trajectory as well for later use
    #but we only do this for the first 100 neutrons, otherwise the figure would be to busy
    if i<100:
        ax.plot3D(Xs,Ys,Zs,label=str(i))

#once all the neutrons are tracked we plot the trajectories and we plot the flux
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
#plt.legend()
ax.azim = 113
ax.elev = 28
plt.show()

plt.figure()
plt.plot(x,flux)
plt.xlabel('x (cm)')
plt.ylabel('flux (per source neutron)')
plt.show()
```

## Experiments

Study what happens if you

- increase the number of particles in the simulation (increase it by one order of magnitude, for more the calculation will take ages).
- change the density of the medium
- change the initial energy of the neutrons

Conclude your findings!

```python

```


# Growth and the (optimal) ribosomal content of cells

#### What to improve/add

- add more data, particularly about ribosomal contant vs growth.
- introduce simpler consideration first, with scenario where cells consist only of ribosomes and RNAP. Consider different parts of translation and transcription (RNAP making rRNA, mRNA for ribosomes and RNAP), ribosomes translating proportional to mRNA abundance. Consider costs of mRNA synthesis. How important is it in terms of costs that several ribosomes use one mRNA to translate?

```python
#start python session
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sympy import *
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42
```

# Growth laws

Simple consideration why an approx. linear relation between ribosome abundance and growth rate means optimal allocation of ressources to ribosomes and other proteins. Arguments do not consider how the cell manage to obtain an optimal abundance of ribosomes, which is an obivous next step to think about more carefully.

In publications from Terry Hwa's lab together with Matt Scott and Stefan Klump similar considerations have been done, consider particularly Scott et al 2014. However I found the presentations in their manuscript not the easiest to follow since it first starts with the 'ribosome line' or other phenomenological relations as given (e.g. ribosome fraction increases proportional with growth rate). Only much later on model based rationalizations are given why these relation  might be useful for the cell. I think it is much easier to understand the relations (or growth-laws) when talking immediatly about nutrient levels and the limitation of translation. With the relations being the consequence of a regulation scheme ensuring an optimal allocation into ribosomes. Furthermore, I think the specific illustration of optimal ribosome allocation vs alternative simpler allocation strategies presented here is helpful to illustrate how important a proper allocation strategy is to maximize growth.

Note: To prevent the introduction of new notation I started followingg closely the notation used in Scott et al 2014. But I probably was not always consistent.

## Ribosomes make all proteins

To start, let's consider the synthesis of total protein mass $M$ by the ribosomes. The synthesis of proteins depends on the number of ribosomes $N_r$ and their translation speed (how many new AA are synthesized per time).

$$ \frac{dM}{dt}= k_R N_{r}$$

Here, we take 1AA as basic unit for protein mass, so there is no extra conversion factor (and the translation speed $k_R$ has the unit AA/time.

In a steady state where the composition of the cell is not changing, we thus have for the growth rate:

$$ \lambda=\frac{dM}{dt}/M=k_R N_{r}/M$$

With the total mass of ribosomes $M_R=N_R m_r$ we can rewrite:

$$ \lambda=\gamma M_R/M=\gamma\phi_R $$

with $\phi_R$ being the ribosomal protein mass fraction [proportional to the easier to masure fraction total RNA to total protein]. $\gamma\equiv k_R/m_R$ denotes the translation efficiency and has the unit of a rate (1/time). $m_R$ is 7459 AA for E.coli.

Note: It is still better to use the term efficiency instead of rate here  since it might be the consequence of something more complex and not a simple chemical process described by a constant reaction rate. For example, when using chloramphenicol what seems to happen is that some ribosomes are stopping completely while others are still translating with regular translation speed. A chloramphenicol depemendent $\gamma$ is describing the total effect of chloramphenical on translation and not necessarily the effect of chloramphenicol on single ribosomes.

This relation should in general be true in steady state, but does not necessarily give a linear relation between ribosom fraction and growth rate: For different growth rates (in different enviornments), the cellular composition is changing and thus translation efficiency $\gamma$ is also expected to change.

In particular we expect translation efficiency to go down if nutrient levels within the cells are falling too much (e.g. not sufficient charged tRNAs to ensure smooth translation). Let's simplify and consider only one type of nutrient source, like a pool of amino acids (or charged tRNAs) with concentration $a$. We expact a sigmodial dependence of translation efficiency when the availability of chareged tRNA varies. For example by a Michaelis Menten type:

$$\gamma=\gamma(a)=\gamma_{max}\cdot\frac{a}{a+a_0}$$

Ribosomes are translating full power for $a>>a_0$, but translation slows down substantially when $a$ falls below $a$.

Note: To have the units comparable we here take $a$ in the units of charged tRNA per protein, not per cell mass (Not a problem since protein per cell is not changing).
Note: Translation is not a simple chemical reaction and other functional forms might be more realistic, e.g. a hill curve with a steeper increase of translation around $a_0$. The advantage using a Michaelis Menten type is that we can analytically handle the steady state.
Note: a0 can be estimated from studies investigating how translation changes with charged tRNA levels in the cell. Particularly, there are studies where single tRNA species have been depleted and its effect on translation speed have been quantified.

Spoiler alert: The linear relation between growth rate and ribosome fraction, a hall-mark of the growth-laws' and obsered over a wide range of growth conditions only follows when a0 is low. In the following we  consider the nutrient levels in the cell in more detail and show that optimal growth rates are obtained when nutrient levels are maintained at sufficiently high levels.

## Nutrient levels within the cell

The nutrient levels within the cell (think of it as charged tRNA) is givene by a balance of nutrient uptake and consumption by the translating ribosomes.

$$\frac{d A}{dt}=J_{in}-\beta \frac{dM}{dt}$$

Here, $J_{in}$ describeds the nutrient influx (how nutrient uptake leads to new charged tRNA). Since we measure  theinternal nutrient pool in units of charged tRNA and protein mass in units of amino acid we can set the conversion factor $\beta$ translating the consumption of internal nutrients to newly translated protein mass to 1. For the concentration of nutrients within the cell, $a=A/M$ we get:

$$\frac{d a}{dt}=J_{in}/M-\frac{1}{M}\frac{dM}{dt}(\beta+a)=J_{in}/M-\lambda \beta-\lambda a$$

The last term describes dilution of the amino acid concentrations by cell growth. The influx of new nutrients depends on the abundance of proteins taking care of nutrient influx. If we more specifically describe by $M_a$ the proteins required for the uptake of nutrients and by $k_a$ the rate, then we have $J_{in}=k_a M_a$. Or $J_{in}/M=k_a\phi_N $ with $\phi_N=M_a/M$.

In steady state, the nutrient concentration is thus given by:

$$a=\beta\left(\frac{k_a\phi_N}{\beta\lambda}-1\right)\equiv \beta \left(\frac{\nu \phi_N}{\lambda}-1\right)$$

with the "nutrient efficiency" defined as $\nu=k_a/\beta$.

Note: Nutrient turnover in the cell is typically large compared to dilution and thus this relation often simplifies to the relation $\lambda=\nu \phi_N$. Thus, when changing enviornmental conditions without changing $\nu$ (like by chloramphenicol as in Scott et al) one sees a linear relation between ribosome fraction and growth rate. However, we are interested here in all kinds of possible steady states, including those were nutrient concentrations reach very high levels. We thus include the dilution term in our further considerations.

Note: In pincriple, nutrient levels can be controlled in addition by some negative feedback. Too high nutrient levels can for example lead to a decrease of protein activity for example (drop of $k_a$). We are not including this here as we only want to illustrate how optimal steady state growth rates are accomplished.

Note: It is important to realize that $M_k$ represents not only one type of enzyme (like a specific uptake protein) but stands for the a pool of enzymes needed to charge tRNA (including metabolic enzymes and what ever is needed to synthesize the AA). Accordingly, $k_a$ is not a simple enzymatic rate.

## Steady state growth rates

The steady state growth rates are a result of combinding the equations for nutrient levels and growth introduced above:
$$a=\beta \left(\frac{\nu \phi_N}{\lambda}-1\right)$$ and
$$\lambda=\gamma_{max}\frac{a}{a+a_0}\phi_R$$

For the case of translation showing a Miachelis Menten dependence on nutrient levels (as stated above) this is just the solution of a quadratic equation.

We can then consider specifically how growth is changing when allocation parameters (particularly the ribosomal content) are changing. We specifically consider the following three (simple) regulation scenarios and compare how well they compare with what the cell is doing across growth conditions.

### Scenario 1: Cell is adjusting the ribosome fraction to optimize growth.

We can solve the equations for different ribosome abundance levels $\phi_R$ and find the  abundance levels for which growth-rate becomes optimal (given the other parameters are fixed). Spoiler alert: this elegant regulation scheme matches very well with the observations.

### Scenario 2: Cell is maintaining a constant translation rate.

We can solve the equations for different ribosome abundance levels and find the ribosome abundance levels such that translation rate, $\gamma=\gamma_{max}\frac{a}{a+a_0}$, is constant.

### Scenario 3: Cell is maintaining a fixed ribosomal content.

Finally, we can solve the equations and consider how growth-rate is changing when ribosomal content is not adjusted but constant (not changing when other parameters are changing).

In the following we outline the code how to get these solutions, and then we analyze how growth is changing for the different scenarios and when the major physiological parameters (nutrient efficiency and translation speed) are varied.

```python
#find optimal ribosome allocation
#can be skiped since I copied the solution in the next cells (I calculated also by hand but got too lazy after a while)

#caluclation steady state solution for translation following a michaeelis mentend dependence on precursor levesl
#start symbols
symbolsToDelete = ('lam')
var('lam')
var('beta')
var('nu')
var('phiR')
var('gamma_max')
var('a_0')
var('phiO')
var('a_sol1')
var('a_sol2')
var('mpc')
#lambda*(nu*phiP+beta*phiR*gamma0)/(Km_nu-beta)-phiR*gamma0*nu*phiP
#start with simple model were amino acid levels are only reduced by growth. Slow down in translation follows simple Michaelis Menten
solve((lam**2)+lam*(beta*nu*(1-phiR-phiO)+beta*phiR*gamma_max)/(a_0-beta)-gamma_max*beta*nu*phiR*(1-phiR-phiO)/(a_0-beta), lam)

##we can then take these solutions and look at the derivative in phiR (robosomal content)
##calculate derivative
grsolution1=(-beta*(gamma_max*phiR - nu*phiO - nu*phiR + nu) + sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR**2 + 2*beta*gamma_max*nu*phiO*phiR + 2*beta*gamma_max*nu*phiR**2 - 2*beta*gamma_max*nu*phiR + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiR - 2*beta*nu**2*phiO + beta*nu**2*phiR**2 - 2*beta*nu**2*phiR + beta*nu**2)))/(2*(a_0 - beta))
dlambda_dphiR=diff(grsolution1,phiR,1)

##print(dlambda_dphiR)
##solve for derivative equals zero to find the optimal ribosome allocation
##this step takes a few seconds
##the optimal ribosome fraction is determined by the derivative being 0.
extreme_growth_solutions=solve(dlambda_dphiR,phiR)
print("solution: growth for optimal ribosome allocation")
#print(extreme_growth_solutions)

###
```

    solution: growth for optimal ribosome allocation

```python
#find  ribosome allocation for fixed precursor levels
#can be skiped since I copied the solution in the next cells.

symbolsToDelete = ('lam')
symbolsToDelete = ('mpc')
var('lam')
var('beta')
var('nu')
var('phiR')
var('gamma_max')
var('a_0')
var('phiO')
var('a_sol1')
var('a_sol2')
var('mpc')

#constant level
solve((mpc**2)+mpc*beta*(-1*nu*(1-phiR-phiO)/(phiR*gamma_max)+1)-nu*(1-phiR-phiO)*a_0*beta/(gamma_max*phiR), mpc)

#we can take this solution and find allocation parameters for which precursor concentration is fixed.
#mpc1sol=(-beta*(gamma_max*phiR - nu*phiO - nu*phiR + nu) + sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR**2 - 2*beta*gamma_max*nu*phiO*phiR - 2*beta*gamma_max*nu*phiR**2 + 2*beta*gamma_max*nu*phiR + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiR - 2*beta*nu**2*phiO + beta*nu**2*phiR**2 - 2*beta*nu**2*phiR + beta*nu**2)))/(2*gamma_max*phiR)
#mpc2sol=-(beta*(gamma_max*phiR - nu*phiO - nu*phiR + nu) + sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR**2 - 2*beta*gamma_max*nu*phiO*phiR - 2*beta*gamma_max*nu*phiR**2 + 2*beta*gamma_max*nu*phiR + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiR - 2*beta*nu**2*phiO + beta*nu**2*phiR**2 - 2*beta*nu**2*phiR + beta*nu**2)))/(2*gamma_max*phiR)
var('avaluec')

#copy in constant solution
mpc1solminconstv=(-beta*(gamma_max*phiR + nu*phiO + nu*phiR - nu) + sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR**2 + 2*beta*gamma_max*nu*phiO*phiR + 2*beta*gamma_max*nu*phiR**2 - 2*beta*gamma_max*nu*phiR + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiR - 2*beta*nu**2*phiO + beta*nu**2*phiR**2 - 2*beta*nu**2*phiR + beta*nu**2)))/(2*gamma_max*phiR)-avaluec
mpc2solminconstv=-(beta*(gamma_max*phiR + nu*phiO + nu*phiR - nu) + sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR**2 + 2*beta*gamma_max*nu*phiO*phiR + 2*beta*gamma_max*nu*phiR**2 - 2*beta*gamma_max*nu*phiR + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiR - 2*beta*nu**2*phiO + beta*nu**2*phiR**2 - 2*beta*nu**2*phiR + beta*nu**2)))/(2*gamma_max*phiR)-avaluec

#mpcl1 = lambdify([phiR,gamma_max,nu,a_0,beta,phiO], mpc1sol, "numpy")
#mpcl2 = lambdify([phiR,gamma_max,nu,a_0,beta,phiO], mpc2sol, "numpy")

#mpcl1minconstv = lambdify([phiR,gamma_max,nu,a_0,beta,phiO], mpc1solminconstv, "numpy")
#mpcl2minconstv = lambdify([phiR,gamma_max,nu,a_0,beta,phiO], mpc2solminconstv, "numpy")

#find solution
fixconst1=solve(mpc1solminconstv,phiR)
fixconst2=solve(mpc2solminconstv,phiR)
display(fixconst1)
display(fixconst2)
```

    [beta*nu*(-a_0*phiO + a_0 - avaluec*phiO + avaluec)/(a_0*beta*nu + avaluec**2*gamma_max + avaluec*beta*gamma_max + avaluec*beta*nu)]

    [beta*nu*(-a_0*phiO + a_0 - avaluec*phiO + avaluec)/(a_0*beta*nu + avaluec**2*gamma_max + avaluec*beta*gamma_max + avaluec*beta*nu)]

```python
#run this cell to have the growth optimal solution ready etc
#definitions of functions to plot things in the following

colorPC='k'#'#4A71B7'
colorPR='k'#'#70AD44'
colorGR='k'
colorGRopt='orange'
colorPCcon='magenta'
alphac=0.8

def grsolution(beta,gamma_maxin,nu,a_0,phiO,phiR,fracactive):
 pf_translationrate=3600./7459. #translation to hour....
 gamma_max=gamma_maxin*fracactive*pf_translationrate
 grsol1s=((-beta*(gamma_max*phiR - nu*phiO - nu*phiR + nu) + np.sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR*phiR + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR*phiR + 2*beta*gamma_max*nu*phiO*phiR + 2*beta*gamma_max*nu*phiR*phiR - 2*beta*gamma_max*nu*phiR + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiR - 2*beta*nu*nu*phiO + beta*nu*nu*phiR*phiR - 2*beta*nu*nu*phiR + beta*nu*nu)))/(2*(a_0 - beta)))

 #grsol1s=((-beta*(gamma_max*phiR - nu*phiO - nu*phiR + nu) + np.sqrt(beta*(-4*a_0*gamma_max*nu*phiO*phiR - 4*a_0*gamma_max*nu*phiR**2 + 4*a_0*gamma_max*nu*phiR + beta*gamma_max**2*phiR*phiR + 2*beta*gamma_max*nu*phiO*phiR + 2*beta*gamma_max*nu*phiR*phiR - 2*beta*gamma_max*nu*phiR + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiR - 2*beta*nu*nu*phiO + beta*nu*nu*phiR*phiR - 2*beta*nu*nu*phiR + beta*nu*nu)))/(2*(a_0 - beta)))

 a_solution1=beta*(nu*(1-phiO-phiR)/grsol1s-1)
 return [grsol1s,a_solution1,gamma_max*a_solution1/(a_solution1+a_0)/fracactive/pf_translationrate]

#solution for optimal growht
#output: fraction ribosomes, precursor level, growth-rate
def optphiR_solution(beta,gamma_maxin,nu,a0,phiO,fracactive):
 pf_translationrate=3600./7459.
 gamma_max=gamma_maxin*fracactive*pf_translationrate
 phiRopt=-(2*a0*gamma_max*nu*phiO - 2*a0*gamma_max*nu - beta*gamma_max*nu*phiO + beta*gamma_max*nu - beta*nu*nu*phiO + beta*nu**2 - gamma_max*phiO*np.sqrt(a0*beta*gamma_max*nu) + gamma_max*np.sqrt(a0*beta*gamma_max*nu) + nu*phiO*np.sqrt(a0*beta*gamma_max*nu) - nu*np.sqrt(a0*beta*gamma_max*nu))/(4*a0*gamma_max*nu - beta*gamma_max*gamma_max - 2*beta*gamma_max*nu - beta*nu**2)
 #print((beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))
 #print(np.sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))
 #phiRopt=optimalgrowth_sol1=-(2*a0*gamma_max*nu*phiO - 2*a0*gamma_max*nu - beta*gamma_max*nu*phiO + beta*gamma_max*nu - beta*nu**2*phiO + beta*nu**2 - gamma_max*phiO*sqrt(a0*beta*gamma_max*nu) + gamma_max*sqrt(a0*beta*gamma_max*nu) + nu*phiO*sqrt(a0*beta*gamma_max*nu) - nu*sqrt(a0*beta*gamma_max*nu))/(4*a0*gamma_max*nu - beta*gamma_max**2 - 2*beta*gamma_max*nu - beta*nu**2)
 grsol1s=((-beta*(gamma_max*phiRopt - nu*phiO - nu*phiRopt + nu) + np.sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))/(2*(a0 - beta)))
 #grsol1s=((-beta*(gamma_max*phiRopt - nu*phiO - nu*phiRopt + nu) + sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt**2 + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max**2*phiRopt**2 + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt**2 - 2*beta*gamma_max*nu*phiRopt + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiRopt - 2*beta*nu**2*phiO + beta*nu**2*phiRopt**2 - 2*beta*nu**2*phiRopt + beta*nu**2)))/(2*(a0 - beta)))
 a_solution1=beta*(nu*(1-phiO-phiRopt)/grsol1s-1)
 translc=grsol1s*0+gamma_max*a_solution1/(a_solution1+a0)/fracactive/pf_translationrate
 return[phiRopt,a_solution1,grsol1s,translc]

def constphiR_solution(beta,gamma_maxin,nu,a0,phiO,phiRin,fracactive):
 pf_translationrate=3600./7459.
 gamma_max=gamma_maxin*fracactive*pf_translationrate
 phiRopt=phiRin
 #-(2*a0*gamma_max*nu*phiO - 2*a0*gamma_max*nu - beta*gamma_max*nu*phiO + beta*gamma_max*nu - beta*nu*nu*phiO + beta*nu**2 - gamma_max*phiO*np.sqrt(a0*beta*gamma_max*nu) + gamma_max*np.sqrt(a0*beta*gamma_max*nu) + nu*phiO*np.sqrt(a0*beta*gamma_max*nu) - nu*np.sqrt(a0*beta*gamma_max*nu))/(4*a0*gamma_max*nu - beta*gamma_max*gamma_max - 2*beta*gamma_max*nu - beta*nu**2)
 #print((beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))
 #print(np.sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))
 #phiRopt=optimalgrowth_sol1=-(2*a0*gamma_max*nu*phiO - 2*a0*gamma_max*nu - beta*gamma_max*nu*phiO + beta*gamma_max*nu - beta*nu**2*phiO + beta*nu**2 - gamma_max*phiO*sqrt(a0*beta*gamma_max*nu) + gamma_max*sqrt(a0*beta*gamma_max*nu) + nu*phiO*sqrt(a0*beta*gamma_max*nu) - nu*sqrt(a0*beta*gamma_max*nu))/(4*a0*gamma_max*nu - beta*gamma_max**2 - 2*beta*gamma_max*nu - beta*nu**2)
 grsol1s=((-beta*(gamma_max*phiRopt - nu*phiO - nu*phiRopt + nu) + np.sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt*phiRopt + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max*gamma_max*phiRopt*phiRopt + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt*phiRopt - 2*beta*gamma_max*nu*phiRopt + beta*nu*nu*phiO*phiO + 2*beta*nu*nu*phiO*phiRopt - 2*beta*nu*nu*phiO + beta*nu*nu*phiRopt*phiRopt - 2*beta*nu*nu*phiRopt + beta*nu*nu)))/(2*(a0 - beta)))
 #grsol1s=((-beta*(gamma_max*phiRopt - nu*phiO - nu*phiRopt + nu) + sqrt(beta*(-4*a0*gamma_max*nu*phiO*phiRopt - 4*a0*gamma_max*nu*phiRopt**2 + 4*a0*gamma_max*nu*phiRopt + beta*gamma_max**2*phiRopt**2 + 2*beta*gamma_max*nu*phiO*phiRopt + 2*beta*gamma_max*nu*phiRopt**2 - 2*beta*gamma_max*nu*phiRopt + beta*nu**2*phiO**2 + 2*beta*nu**2*phiO*phiRopt - 2*beta*nu**2*phiO + beta*nu**2*phiRopt**2 - 2*beta*nu**2*phiRopt + beta*nu**2)))/(2*(a0 - beta)))
 a_solution1=beta*(nu*(1-phiO-phiRopt)/grsol1s-1)
 translc=grsol1s*0+gamma_max*a_solution1/(a_solution1+a0)/fracactive/pf_translationrate
 return[grsol1s*0+phiRopt,a_solution1,grsol1s,translc]

#solution for constant nutrient abundance levels
#output: fraction ribosomes, precursor level, growth-rate
def constPCsolution(beta,gamma_maxin,nu,a_0,avaluec,phiO,fracactive):
    pf_translationrate=3600./7459.
    gamma_max=gamma_maxin*fracactive*pf_translationrate
    sol1c=beta*nu*(-a_0*phiO + a_0 - avaluec*phiO + avaluec)/(a_0*beta*nu + avaluec*avaluec*gamma_max + avaluec*beta*gamma_max + avaluec*beta*nu)
    #sol2c=beta*nu*(-a_0*phiO + a_0 - avaluec*phiO + avaluec)/(a_0*beta*nu + avaluec**2*gamma_max + avaluec*beta*gamma_max + avaluec*beta*nu)
    translc=sol1c*0.+gamma_max*avaluec/(avaluec+a_0)/fracactive/pf_translationrate
    return [sol1c,sol1c*0.+avaluec,gamma_max*sol1c*avaluec/(avaluec+a_0),translc]

```

# Plot variation growth-rate

The physiological solution (positive nutrient levels) states how growth rates are changing with different parameters and when the fraction of ribosomes is varied. Plots generated using the follwoing cell.

As we see, growth rates are varying with ribosome fraction with an optimal ribosome fraction at intermediate levels. If ribosome fractions are too low, then growth is limited by the number of ribosomes, available nutrients within the cell are not used efficiently. If ribosome fractions are too high, then nutrient levels fall and translation is not efficienty anymore.

We see already by these plots that optimal growth rates are occuring at a ribosomal fraction where nutrient levels correspond roughly to $a_0$, the precursor-levels below which ribosomes stop elongation efficiently.

```python
#standard values used to plot

a0v=0.0013*20#
gamma_maxv=20
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65

#get solution vor varying parameters (here phiR)
x=np.linspace(0.01,(1-phiOv)*0.98,100)
[grc,pcc,translcc]=grsolution(betav,gamma_maxv,nuv,a0v,phiOv,x,fracactivev)
#get phiR for optimal growth

gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phiOv,fracactivev)
#get phi for pc levels constant

avaluev=10.0*a0v
pccon_alphaR,pccon_pc,pccon_gr,pccon_transl=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv,fracactivev)

fig, axs = plt.subplots(3,1, figsize=(2.8,6.9))
axs[0].set_xlim(0,(1-phiOv)*1.05*100)
axs[1].set_xlim(0,(1-phiOv)*1.05*100)
axs[1].set_yscale('log')

#plot varying growth-rate
axs[0].plot(100*x,grc,color='k',ls='-')

rlabel='allocation to translation $\\alpha_T\, (\%)$ '
axs[0].set_xlabel(rlabel)
axs[0].set_ylabel("growth rate $\lambda\, (1/h)$")

#plot varying pc concentrations
axs[1].plot(100*x,pcc,color='k',ls='-')
axs[1].set_xlabel(rlabel)
axs[1].set_ylabel("charged tRNA $m_{t}\, (per AA)$")

axs[2].set_xlabel(rlabel)
axs[2].set_ylabel("elongation speed $(AA/s)$")

axs[2].plot(100*x,translcc,color='k',ls='-')

#plot growth-optimal case
axs[0].axvline(100*gropt_alphaR,ls='--',color=colorGRopt,alpha=alphac)
axs[0].axhline(gropt_gr,ls='--',color=colorGRopt,alpha=alphac)
axs[1].axvline(100*gropt_alphaR,ls='--',color=colorGRopt,alpha=alphac)
axs[1].axhline(gropt_pc,ls='--',color=colorGRopt,alpha=alphac)

axs[2].axvline(100*gropt_alphaR,ls='--',color=colorGRopt,alpha=alphac)
axs[2].axhline(gropt_transl,ls='--',color=colorGRopt,alpha=alphac)

#plot case with fixed pcc concentration
if 3>4:
    axs[0].axvline(100*pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
    axs[0].axhline(pccon_gr,ls='--',color=colorPCcon,alpha=alphac)
    axs[1].axvline(100*pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
    axs[1].axhline(pccon_pc,ls='--',color=colorPCcon,alpha=alphac)

axs[1].axhline(a0v,label='$K_{M,PC}$',ls=':',color=colorPC,alpha=alphac)

#plot varying translation

#plt.legend()
plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha.pdf")
```

Now we can check how growth rate is changing when changing parameters. For example, when changing translation efficiency or nutrient efficiency. F

Note: Presentation needs to be improved. Stoped adding explaining comments from here onwards. And there are inconsistencies with the notation (e.g. used alpha for the allocation parameters etc).

```python
# Plot different nutrient qualifies....how growth is varying with ribosome fraction

#decide which nutrient qualities should be probed

a0v=0.0013*20#
gamma_maxv=20
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65

nul=np.linspace(0.05*nuv,nuv,6)

#prepare figure
fig, axs = plt.subplots(3,1, figsize=(2.8,6.9))

axs[0].set_ylabel("growth-rate $\lambda\, (1/h)$")
axs[1].set_ylabel("charged tRNA $m_{t}\, (per AA)$")
axs[2].set_ylabel("translation rate $(AA/s)$")
rlabel='allocation to translation $\\alpha_T\, (\%)$ '
axs[1].set_xlabel(rlabel)
axs[0].set_xlabel(rlabel)
axs[2].set_xlabel(rlabel)
axs[0].set_xlim(0,(1-phiOv)*1.05*100)
axs[1].set_xlim(0,(1-phiOv)*1.05*100)
axs[2].set_xlim(0,(1-phiOv)*1.05*100)

axs[1].set_yscale('log')
colorl=['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928']
#go through different nutrient qualities
x=np.linspace(0.01,(1.-phiOv)*0.99,100)

alphRoptl=[]
groptl=[]
proptl=[]
transllist=[]

for il in range(0,nul.shape[0]):
    colorc=colorl[il]
    #get solution vor varying parameters (here phiR)
    nuv=nul[il]
    [grc,pcc,tcc]=grsolution(betav,gamma_maxv,nuv,a0v,phiOv,x,fracactivev)
    #get phiR for optimal growth
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phiOv,fracactivev)
    alphRoptl.append(gropt_alphaR)
    groptl.append(gropt_gr)
    proptl.append(gropt_pc)
    transllist.append(gropt_transl)
    #get phi for pc levels constant
    #avaluev=10.0*a0v
    #pccon_alphaR,pccon_pc,pccon_gr=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv)

    #plot varying growth-rate
    axs[0].plot(x*100,grc,color=colorc,ls='-',label=str(round(nuv,1)))

    #plot varying pc concentrations
    axs[1].plot(x*100,pcc,color=colorc,ls='-')

    axs[2].plot(x*100,tcc,color=colorc,ls='-')

    #plot growth-optimal case
    axs[0].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)
    #axs[0].axhline(gropt_gr,ls='--',color=colorc,alpha=alphac)
    axs[1].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)

    axs[2].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)
    #axs[1].axhline(gropt_pc,ls='--',color=colorc,alpha=alphac)

    #plot case with fixed pcc concentration
    if 3>4:
        axs[0].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[0].axhline(pccon_gr,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axhline(pccon_pc,ls='--',color=colorPCcon,alpha=alphac)

axs[1].axhline(a0v,label='$K_{M,PC}$',ls=':',color=colorPC,alpha=alphac)
axs[0].legend(title='$\\nu$')
axs[1].legend()

plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varnu.pdf")

#now look at growth-optimal solution
fig, axs = plt.subplots(2,1, figsize=(2.8,5))
axs[0].set_xlim(0,2.2)
axs[1].set_xlim(0,2.2)
nuarray=np.linspace(0.01,10,100)
gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuarray,a0v,phiOv,fracactivev)
#ribosome fraction
axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k',label='opt. growth')
#translation
axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

#offset
fitc=np.polyfit(gropt_gr[int(nuarray.shape[0]/2.):],100*gropt_alphaR[int(nuarray.shape[0]/2.):],1)
xgrr=np.linspace(0,2,20)
axs[0].plot(xgrr,xgrr*fitc[0]+fitc[1],color='k',ls='--')

#plot different nutrient values as examples
for il in range(0,nul.shape[0]):
    axs[0].plot(groptl[il],100*alphRoptl[il],color=colorl[il],marker='o')
#plot transltion rate
    axs[1].plot(groptl[il],transllist[il],color=colorl[il],marker='o')

#do the same thing, but look at constant ribosome level

alphRoptc=alphRoptl[-1]

pcconR_alphaR,pcconR_pc,pcconR_gr,pccon_transl=constphiR_solution(betav,gamma_maxv,nuarray,a0v,phiOv,alphRoptc,fracactivev)
axs[0].plot(pcconR_gr,100*pcconR_alphaR,ls='-.',color='gray',label='constr. rib. levels')
axs[1].plot(pcconR_gr,pccon_transl,ls='-.',color='gray')

#alphRconstl=[]
grconstl=[]
#prconstl=[]

for il in range(0,nul.shape[0]):
    pcconR_alphaR,pcconR_pc,pcconR_gr,pccon_transl=constphiR_solution(betav,gamma_maxv,nul[il],a0v,phiOv,alphRoptc,fracactivev)
    axs[0].plot(pcconR_gr,100*pcconR_alphaR,color=colorl[il],marker='<')
    axs[1].plot(pcconR_gr,pccon_transl,color=colorl[il],marker='<')
    grconstl.append(pcconR_gr)

#do the same thing, but what happens when ribosome activity is not changing
avaluec=proptl[-1]

#alphRconstactl=[]
grconstactl=[]
#prconstactl=[]

pccon_alphaR,pccon_pc,pccon_gr,pccon_transl=constPCsolution(betav,gamma_maxv,nuarray,a0v,avaluec,phiOv,fracactivev)
axs[0].plot(pccon_gr,100*pccon_alphaR,ls=':',color='gray',label='constr. rib. activity')
axs[1].plot(pccon_gr,pccon_transl,ls=':',color='gray')

for il in range(0,nul.shape[0]):
    pccon_alphaR,pccon_pc,pccon_gr,pccon_transl=constPCsolution(betav,gamma_maxv,nul[il],a0v,avaluec,phiOv,fracactivev)
    axs[0].plot(pccon_gr,100*pccon_alphaR,color=colorl[il],marker='^')
    axs[1].plot(pccon_gr,pccon_transl,color=colorl[il],marker='^')
    grconstactl.append(pccon_gr)

axs[0].set_ylabel(rlabel)
axs[0].set_xlabel("growth-rate $\lambda\, (1/h)$")
axs[1].set_ylabel('trans. elongation speed')
axs[1].set_xlabel("growth-rate $\lambda\, (1/h)$")

if 3>2:
    #fit Mori et al.
    grr=np.linspace(0,2,100)
    #axs[0].plot(grr,0.0967+0.2206*grr,color='r',label='Mori et al',alpha=0.5)
    #axs[1,0].plot(,color='r',label='observ transl.')
    #take data from RNAseq (c limitation)
    grdatac=np.array([0.91,0.75, 0.56, 0.51, 0.3 ])
    ribosabundanc=np.array([19.18590608, 14.22365961, 12.50443378, 10.73007257,  8.74428159])/100.
    axs[0].plot(grdatac,100*ribosabundanc,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='RNA-Seq',zorder=-1)
    #fitc=np.polyfit(grdatac,ribosabundanc,1)
    #grrc=np.linspace(0,2,100)
    #axs[0].plot(grrc,100*(grrc*fitc[0]+fitc[1]),color='r',label='RNA-seq',alpha=0.5)

if 3>2:#TRANSLATION SPEED - DAI AT AL.
        grdatac=np.array([1.83,1.28,1.12,0.98,0.75,0.69,0.69,0.55,0.5,0.46,0.41,0.34,0.38,0.33,0.29,0.2,0.23,0.13,0.035])
        elongr=np.array([16.7,16.3,16.1,15.9,14.9,15,14.7,13.7,13.1,12.6,13,12.4,12.4,12,12.1,11.6,12.3,10.7,9.4])
        axs[1].plot(grdatac,elongr,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='Dai et al',zorder=-1)

axs[0].legend()
plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varnu_opt.pdf")

#make a bar diagram with different growth-rates

#constant ribosomes
fig, axs = plt.subplots(1,1, figsize=(3.8,2.3))

barc=[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19,21,22,23]
axs.set_xticks(barc)
axs.set_xticklabels(["",str(nul[0]),"","",str(nul[1]),"","",str(nul[2]),"","",str(round(nul[3],2)),"","",str(nul[4]),"","",str(nul[5]),""])
barcc=-1
for il in range(0,6):
    if il==0:
        labelc="optimal growth"
    else:
        labelc=None
    barcc=barcc+1
    axs.bar(barc[barcc],groptl[il],color=colorl[il],label=labelc)
    barcc=barcc+1
    if il==0:
        labelc="const. transl."
    else:
        labelc=None
    axs.bar(barc[barcc],grconstactl[il],hatch='\\\\',color=colorl[il],label=labelc)

    barcc=barcc+1
    if il==0:
        labelc="constant rib. fract."
    else:
        labelc=None
    axs.bar(barc[barcc],grconstl[il],hatch='//',color=colorl[il],label=labelc)

axs.set_ylabel("growth-rate")
axs.set_xlabel("nutrient efficiency $\\nu\, (1/h)$")
axs.legend()
plt.tight_layout()
plt.savefig("plot_output/bardiagram_differences.pdf")

#constant ribosomes
fig, axs = plt.subplots(1,1, figsize=(3.8,2.3))

barc=[1,2,4,5,7,8,10,11,13,14,16,17]
axs.set_xticks(barc)
axs.set_xticklabels(["",str(nul[0]),"",str(nul[1]),"",str(nul[2]),"",str(round(nul[3],2)),"",str(nul[4]),"",str(nul[5])])
barcc=-1
for il in range(0,6):
    barcc=barcc+1
    if il==0:
        labelc="const. transl."
    else:
        labelc=None
    axs.bar(barc[barcc],100*grconstactl[il]/groptl[il],hatch='\\\\',color=colorl[il],label=labelc)

    barcc=barcc+1
    if il==0:
        labelc="constant rib. fract."
    else:
        labelc=None
    axs.bar(barc[barcc],100*grconstl[il]/groptl[il],hatch='//',color=colorl[il],label=labelc)

axs.set_ylabel("growth (fract. optimum, $\%$)")
axs.set_xlabel("nutrient  efficiency $\\nu\, (1/h)$")
#axs.legend()
plt.tight_layout()
plt.savefig("plot_output/bardiagram_differences_fraction.pdf")

```

# Variation fraction other

```python
# Plot different nutrient qualifies....how growth is varying with ribosome fraction
#decide which nutrient qualities should be probed

a0v=0.0013*20#
gamma_maxv=20
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65

phiOvl=np.linspace(0.1,0.8,8)

#prepare figure
fig, axs = plt.subplots(3,1, figsize=(2.8,6.9))
axs[0].set_ylabel("growth-rate $\lambda\, (1/h)$")
axs[1].set_ylabel("precursor $m_{PC}\, (per AA)$")
axs[2].set_ylabel("translation rate $(AA/s)$")
rlabel='allocation to translation $\\alpha_T\, (\%)$ '
axs[1].set_xlabel(rlabel)
axs[0].set_xlabel(rlabel)
axs[2].set_xlabel(rlabel)
axs[0].set_xlim(0,100)
axs[1].set_xlim(0,100)
axs[2].set_xlim(0,100)
axs[1].set_yscale('log')
colorl=['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928']
#go through different nutrient qualities

alphRoptl=[]
groptl=[]
proptl=[]
gropt_translist=[]
for il in range(0,phiOvl.shape[0]):
    colorc=colorl[il]
    x=np.linspace(0.01,(1-phiOvl[il])*0.99,100)
    #get solution vor varying parameters (here phiR)
    phiOv=phiOvl[il]
    [grc,pcc,tcc]=grsolution(betav,gamma_maxv,nuv,a0v,phiOv,x,fracactivev)
    #get phiR for optimal growth
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phiOv,fracactivev)
    alphRoptl.append(gropt_alphaR)
    groptl.append(gropt_gr)
    proptl.append(gropt_pc)
    gropt_translist.append(gropt_transl)

    #get phi for pc levels constant
    #avaluev=10.0*a0v
    #pccon_alphaR,pccon_pc,pccon_gr=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv)

    #plot varying growth-rate
    axs[0].plot(100*x,grc,color=colorc,ls='-')
    #plot varying pc concentrations
    axs[1].plot(100*x,pcc,color=colorc,ls='-')
    axs[2].plot(100*x,tcc,color=colorc,ls='-')

    #plot growth-optimal case
    axs[0].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)
    axs[1].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)
    axs[2].axvline(100*gropt_alphaR,ls='--',color=colorc,alpha=alphac)

    #plot case with fixed pcc concentration
    if 3>4:
        axs[0].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[0].axhline(100*pccon_gr,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axhline(100*pccon_pc,ls='--',color=colorPCcon,alpha=alphac)

axs[1].axhline(a0v,label='$K_{M,PC}$',ls=':',color=colorPC,alpha=alphac)
plt.legend()
plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varother.pdf")

#now look at growth-optimal solution
fig, axs = plt.subplots(2,1, figsize=(2.8,4.6))
axs[0].set_xlim(0,2)

phi0array=np.linspace(0.,0.8,100)
gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phi0array,fracactivev)
#ribosome fraction
axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k')
#translation
axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

#offset
fitc=np.polyfit(gropt_gr[int(phi0array.shape[0]/2.):],100*gropt_alphaR[int(phi0array.shape[0]/2.):],1)
xgrr=np.linspace(0,2,20)
axs[0].plot(xgrr,xgrr*fitc[0]+fitc[1],color='k',ls='--')

#plot different nutrient values as examples
for il in range(0,phiOvl.shape[0]):
    axs[0].plot(groptl[il],100*alphRoptl[il],color=colorl[il],marker='o')
#plot transltion rate
    axs[1].plot(groptl[il],gropt_translist[il],color=colorl[il],marker='o')

axs[0].set_ylabel('allocation to translation $\\alpha_R$')
axs[0].set_xlabel("growth rate $\lambda\, (1/h)$")

axs[1].set_ylabel('translation $(AA/s)$')
axs[1].set_xlabel("growth rate $\lambda\, (1/h)$")

if 3>2:
    #fit Mori at al.
    grr=np.linspace(0,2,100)
    #axs[0].plot(grr,0.0967+0.2206*grr,color='r',label='Mori et al',alpha=0.5)
    #axs[1,0].plot(,color='r',label='observ transl.')
    #take data from RNAseq (c limitation)
    grdatac=np.array([0.91,0.75, 0.56, 0.51, 0.3 ])
    ribosabundanc=np.array([19.18590608, 14.22365961, 12.50443378, 10.73007257,  8.74428159])/100.
    axs[0].plot(grdatac,100*ribosabundanc,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='RNA-Seq',zorder=-1)
    #fitc=np.polyfit(grdatac,ribosabundanc,1)
    #grrc=np.linspace(0,2,100)
    #axs[0].plot(grrc,100*(grrc*fitc[0]+fitc[1]),color='r',label='RNA-seq',alpha=0.5)

if 3>2:#TRANSLATION SPEED - DAI AT AL.
        grdatac=np.array([1.83,1.28,1.12,0.98,0.75,0.69,0.69,0.55,0.5,0.46,0.41,0.34,0.38,0.33,0.29,0.2,0.23,0.13,0.035])
        elongr=np.array([16.7,16.3,16.1,15.9,14.9,15,14.7,13.7,13.1,12.6,13,12.4,12.4,12,12.1,11.6,12.3,10.7,9.4])
        axs[1].plot(grdatac,elongr,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='Dai et al',zorder=-1)

plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varother_opt.pdf")
```

# Variaton of both, nutrient efficiency and other proteome fraction

```python
# Plot different nutrient qualifies....how growth is varying with ribosome fraction

#decide which nutrient qualities should be probed
a0v=0.0013*20#
gamma_maxv=19
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65

#phiOv=0.3

#fit transporter genes
#[-14.83514886  21.28571246]
#fit motility genes
#[-11.6321223   15.79293438]

phiOvl=np.linspace(0.35,0.7,5)[::-1]
phiOvl=np.append(phiOvl,np.array([0.35]*3))
print(phiOvl)

nul=np.linspace(0.05*nuv,10,8)

#prepare figure
fig, axs = plt.subplots(3,1, figsize=(2.8,6.9))
axs[0].set_ylabel("growth-rate $\lambda\, (1/h)$")
axs[1].set_ylabel("charged tRNA $m_{PC}\, (per AA)$")
axs[2].set_ylabel("translation rate $(AA/s)$")
rlabel='allocation to translation $\\alpha_T\, (\%)$ '
axs[1].set_xlabel(rlabel)
axs[0].set_xlabel(rlabel)
axs[2].set_xlabel(rlabel)
axs[0].set_xlim(0,100*(1-phiOvl[-1])*1.05)
axs[1].set_xlim(0,100*(1-phiOvl[-1])*1.05)
axs[2].set_xlim(0,100*(1-phiOvl[-1])*1.05)
axs[1].set_yscale('log')
colorl=['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928']
#go through different nutrient qualities

alphRoptl=[]
groptl=[]
proptl=[]
gropt_translist=[]
for il in range(0,phiOvl.shape[0]):
    colorc=colorl[il]
    x=np.linspace(0.01,(1-phiOvl[il])*0.99,100)
    #get solution vor varying parameters (here phiR)
    phiOv=phiOvl[il]
    nuv=nul[il]
    [grc,pcc,tcc]=grsolution(betav,gamma_maxv,nuv,a0v,phiOv,x,fracactivev)
    #get phiR for optimal growth
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phiOv,fracactivev)
    alphRoptl.append(gropt_alphaR)
    groptl.append(gropt_gr)
    proptl.append(gropt_pc)
    gropt_translist.append(gropt_transl)
    #get phi for pc levels constant
    #avaluev=10.0*a0v
    #pccon_alphaR,pccon_pc,pccon_gr=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv)

    #plot varying growth-rate
    axs[0].plot(x*100,grc,color=colorc,ls='-',label=str(round(nuv))+" "+str(int((phiOv-phiOvl[-1])*100)))
    axs[0].set_xlabel('allocation to translation $\\alpha_R$')
    axs[0].set_ylabel("growth-rate $\lambda\, (1/h)$")

    #plot varying pc concentrations
    axs[1].plot(x*100,pcc,color=colorc,ls='-')
    axs[1].set_xlabel('allocation to translation $\\alpha_R$')
    axs[1].set_ylabel("precursor $m_{PC}\, (per AA)$")

    #plot growth-optimal case
    axs[0].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)
    #axs[0].axhline(gropt_gr,ls='--',color=colorc,alpha=alphac)
    axs[1].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)
    axs[2].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)
    axs[2].plot(x*100,tcc,color=colorc,ls='-')
    #axs[1].axhline(gropt_pc,ls='--',color=colorc,alpha=alphac)

    #plot case with fixed pcc concentration
    if 3>4:
        axs[0].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        #axs[0].axhline(pccon_gr,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        #axs[1].axhline(pccon_pc,ls='--',color=colorPCcon,alpha=alphac)

axs[1].axhline(a0v,label='$K_{M,PC}$',ls=':',color=colorPC,alpha=alphac)
axs[0].legend(title='$\\nu\, \\alpha_O$')
plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varboth.pdf")

#now look at growth-optimal solution
fig, axs = plt.subplots(2,1, figsize=(2.8,4.6))
axs[0].set_xlim(0,2)
axs[1].set_xlim(0,2)
#axs[1].set_ylim(0,1)

#phiOvl
#phi0array=np.linspace(phiOvl[-1],phiOvl[0],160)[::-1]
for ill in range(0,len(phiOvl)-1):
    if ill==0:
        phi0array=np.linspace(phiOvl[ill],phiOvl[ill+1],20)
    else:
        phi0array=np.append(phi0array,np.linspace(phiOvl[ill],phiOvl[ill+1],20))
nuarray=np.linspace(nul[0],nul[-1],140)

gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuarray,a0v,phi0array,fracactivev)
#ribosome fraction
axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k')
#translation
axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

#offset
fitc=np.polyfit(gropt_gr[int(gropt_gr.shape[0]/2.):],100*gropt_alphaR[int(gropt_gr.shape[0]/2.):],1)
xgrr=np.linspace(0,2,20)
axs[0].plot(xgrr,xgrr*fitc[0]+fitc[1],color='k',ls='--')

#plot different nutrient values as examples
for il in range(0,phiOvl.shape[0]):
    axs[0].plot(groptl[il],100*alphRoptl[il],color=colorl[il],marker='o')
    #axs[0].plot(groptl[il],100*(phiOvl[il]-phiOvl[-1]),color=colorl[il],marker='^')
#plot transltion rate
    axs[1].plot(groptl[il],gropt_translist[il],color=colorl[il],marker='o')

axs[0].set_ylabel('allocation to translation $\\alpha_R$')
axs[0].set_xlabel("growth rate $\lambda\, (1/h)$")

if 3>2:
    #fit transporter genes and motility genes (2nd part)
    xrgc=np.linspace(0,2)
    #axs[0].plot(xrgc,-14.83514886*xrgc+21.28571246-11.6321223*xrgc+15.79293438,color='m')

    #fit motility genes

if 3>2:
    #fit Mori at al.
    grr=np.linspace(0,2,100)
    #axs[0].plot(grr,0.0967+0.2206*grr,color='r',label='Mori et al',alpha=0.5)
    #axs[1,0].plot(,color='r',label='observ transl.')
    #take data from RNAseq (c limitation)
    grdatac=np.array([0.91,0.75, 0.56, 0.51, 0.3 ])
    ribosabundanc=np.array([19.18590608, 14.22365961, 12.50443378, 10.73007257,  8.74428159])/100.
    #axs[0].plot(grdatac,100*ribosabundanc,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='RNA-Seq',zorder=-1)
    #fitc=np.polyfit(grdatac,ribosabundanc,1)
    #grrc=np.linspace(0,2,100)
    #axs[0].plot(grrc,100*(grrc*fitc[0]+fitc[1]),color='r',label='RNA-seq',alpha=0.5)

if 3>2:#TRANSLATION SPEED - DAI AT AL.
        grdatac=np.array([1.83,1.28,1.12,0.98,0.75,0.69,0.69,0.55,0.5,0.46,0.41,0.34,0.38,0.33,0.29,0.2,0.23,0.13,0.035])
        elongr=np.array([16.7,16.3,16.1,15.9,14.9,15,14.7,13.7,13.1,12.6,13,12.4,12.4,12,12.1,11.6,12.3,10.7,9.4])
        axs[1].plot(grdatac,elongr,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='Dai et al',zorder=-1)

axs[1].set_ylabel('trans. elongation speed $(AA/s)$')
axs[1].set_xlabel("growth rate $\lambda\, (1/h)$")

plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_varboth_opt.pdf")
```

# Variation translation rate

```python
# Plot different nutrient qualifies....how growth is varying with ribosome fraction
#decide which nutrient qualities should be probed

a0v=0.0013*20#
gamma_maxv=20
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65
gamma_maxvl=np.linspace(0.1,20.,8)

#prepare figure
fig, axs = plt.subplots(3,1, figsize=(2.8,6.9))
axs[0].set_ylabel("growth rate $\lambda\, (1/h)$")
axs[1].set_ylabel("precursor $m_{PC}\, (per AA)$")
axs[2].set_ylabel("translation rate $(AA/s)$")
rlabel='allocation to translation $\\alpha_T\, (\%)$ '
axs[1].set_xlabel(rlabel)
axs[0].set_xlabel(rlabel)
axs[2].set_xlabel(rlabel)
axs[0].set_xlim(0,100*(1-phiOv)*1.05)
axs[1].set_xlim(0,100*(1-phiOv)*1.05)
axs[2].set_xlim(0,100*(1-phiOv)*1.05)
axs[1].set_yscale('log')
colorl=['#a6cee3','#1f78b4','#b2df8a','#33a02c','#fb9a99','#e31a1c','#fdbf6f','#ff7f00','#cab2d6','#6a3d9a','#ffff99','#b15928']
#go through different nutrient qualities

alphRoptl=[]
groptl=[]
proptl=[]
gropt_transllist=[]
for il in range(0,gamma_maxvl.shape[0]):
    colorc=colorl[il]
    x=np.linspace(0.01,(1.-phiOv)*0.99,100)
    #get solution vor varying parameters (here phiR)
    [grc,pcc,tcc]=grsolution(betav,gamma_maxvl[il],nuv,a0v,phiOv,x,fracactivev)
    #get phiR for optimal growth
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxvl[il],nuv,a0v,phiOv,fracactivev)
    alphRoptl.append(gropt_alphaR)
    groptl.append(gropt_gr)
    proptl.append(gropt_pc)
    gropt_transllist.append(gropt_transl)
    #get phi for pc levels constant
    #avaluev=10.0*a0v
    #pccon_alphaR,pccon_pc,pccon_gr=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv)

    #plot varying growth-rate
    axs[0].plot(x*100,grc,color=colorc,ls='-')

    #plot varying pc concentrations
    axs[1].plot(x*100,pcc,color=colorc,ls='-')

    axs[2].plot(x*100,tcc,color=colorc,ls='-')

    #plot growth-optimal case
    axs[0].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)
    axs[1].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)
    axs[2].axvline(gropt_alphaR*100,ls='--',color=colorc,alpha=alphac)

    #plot case with fixed pcc concentration
    if 3>4:
        axs[0].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[0].axhline(pccon_gr,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axvline(pccon_alphaR,ls='--',color=colorPCcon,alpha=alphac)
        axs[1].axhline(pccon_pc,ls='--',color=colorPCcon,alpha=alphac)

axs[1].axhline(a0v,label='$K_{M,PC}$',ls=':',color=colorPC,alpha=alphac)
plt.legend()
plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_vartransl.pdf")

#now look at growth-optimal solution
fig, axs = plt.subplots(2,1, figsize=(2.8,4.6))
axs[0].set_xlim(0,2)
axs[1].set_xlim(0,2)

gammaarray=np.linspace(0.,gamma_maxv,100)
gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gammaarray,nuv,a0v,phiOv,fracactivev)
#ribosome fraction
axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k')
#translation
axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

#linear fit
#fitc=np.polyfit(gropt_gr[int(phiarray.shape[0]/2.):],gropt_alphaR[int(phiarray.shape[0]/2.):],1)
#xgrr=np.linspace(0,2,20)
#axs[0].plot(xgrr,xgrr*fitc[0]+fitc[1],color='k',ls='--')

#plot different nutrient values as examples
for il in range(0,gamma_maxvl.shape[0]):
    axs[0].plot(groptl[il],100*alphRoptl[il],color=colorl[il],marker='o')
#plot transltion rate
    axs[1].plot(groptl[il],gropt_transllist[il],color=colorl[il],marker='o')

#do the same thing, but look at constant ribosome level
alphRoptc=alphRoptl[-1]
pcconR_alphaR,pcconR_pc,pcconR_gr,pcconR_tr=constphiR_solution(betav,gammaarray,nuv,a0v,phiOv,alphRoptc,fracactivev)
axs[0].plot(pcconR_gr,100*pcconR_alphaR,ls='-.',color='gray')
axs[1].plot(pcconR_gr,pcconR_tr,ls='-.',color='gray')

grconstactl=[]
for il in range(0,gamma_maxvl.shape[0]):
    pcconR_alphaR,pcconR_pc,pcconR_gr,pcconR_transl=constphiR_solution(betav,gamma_maxvl[il],nuv,a0v,phiOv,alphRoptc,fracactivev)
    axs[0].plot(pcconR_gr,100*pcconR_alphaR,color=colorl[il],marker='<')
    axs[1].plot(pcconR_gr,pcconR_transl,color=colorl[il],marker='<')
    grconstactl.append(pcconR_gr)

#do the same thing, but what happens when ribosome activity is not changing
avaluec=proptl[-1]

if 3>2:
    pccon_alphaR,pccon_pc,pccon_gr,pccon_transl=constPCsolution(betav,gammaarray,nuv,a0v,avaluec,phiOv,fracactivev)
    #axs[0].plot(pccon_gr,100*pccon_alphaR,ls=':',color='gray')
    #axs[1].plot(pccon_gr,pccon_transl,ls=':',color='gray')
    grconstl=[]
    for il in range(0,gamma_maxvl.shape[0]):
        pccon_alphaR,pccon_pc,pccon_gr,pccon_transl=constPCsolution(betav,gamma_maxvl[il],nuv,a0v,avaluec,phiOv,fracactivev)
        axs[0].plot(pccon_gr,100*pccon_alphaR,color=colorl[il],marker='^')
        axs[1].plot(pccon_gr,pccon_transl,color=colorl[il],marker='^')
        grconstl.append(pccon_gr)
axs[0].set_ylabel('allocation to translation $\\alpha_R$')
axs[0].set_xlabel("growth rate $\lambda\, (1/h)$")

axs[1].set_ylabel('tran. elongation speed $(AA/s)$')
axs[1].set_xlabel("growth rate $\lambda\, (1/h)$")
axs[0].set_ylim(0,70)

plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_vartrans_opt.pdf")

####

#constant ribosomes
fig, axs = plt.subplots(1,1, figsize=(3.8,2.3))

barc=[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19,21,22,23]
axs.set_xticks(barc)
labelll=["",str(round(gamma_maxvl[0],1)),"","",str(round(gamma_maxvl[1],1)),"","",str(round(gamma_maxvl[2],1)),"","",str(round(gamma_maxvl[3],2)),"","",str(round(gamma_maxvl[4],1)),"","",str(round(gamma_maxvl[5],))]

axs.set_xticklabels(labelll)

barcc=-1
for il in range(0,6):
    if il==0:
        labelc="optimal growth"
    else:
        labelc=None
    barcc=barcc+1
    axs.bar(barc[barcc],groptl[il],color=colorl[il],label=labelc)
    barcc=barcc+1
    if il==0:
        labelc="const. transl."
    else:
        labelc=None
    axs.bar(barc[barcc],grconstactl[il],hatch='\\\\',color=colorl[il],label=labelc)

    barcc=barcc+1
    if il==0:
        labelc="constant rib. fract."
    else:
        labelc=None
    axs.bar(barc[barcc],grconstl[il],hatch='//',color=colorl[il],label=labelc)

axs.set_ylabel("growth rate")
axs.set_xlabel("trans. elongation speed $(AA/s)$")
axs.legend()
plt.tight_layout()
plt.savefig("plot_output/bardiagram_differences_trans.pdf")

#constant ribosomes
fig, axs = plt.subplots(1,1, figsize=(3.8,2.3))

barc=[1,2,4,5,7,8,10,11,13,14,16,17]
axs.set_xticks(barc)
labelll=["",str(round(gamma_maxvl[0],1)),"",str(round(gamma_maxvl[1],1)),"",str(round(gamma_maxvl[2],1)),"",str(round(gamma_maxvl[3],2)),"",str(round(gamma_maxvl[4],1)),"",str(round(gamma_maxvl[5],))]

axs.set_xticklabels(labelll)

barcc=-1
for il in range(0,6):
    barcc=barcc+1
    if il==0:
        labelc="constant ribo. fract."
    else:
        labelc=None
    axs.bar(barc[barcc],100*grconstactl[il]/groptl[il],hatch='\\\\',color=colorl[il],label=labelc)

    barcc=barcc+1
    if il==0:
        labelc="const. tRNA levels"
    else:
        labelc=None
    axs.bar(barc[barcc],100*grconstl[il]/groptl[il],hatch='//',color=colorl[il],label=labelc)

axs.set_ylabel("growth (fract. optimum, $\%$)")
axs.set_xlabel("trans. elongation speed $(AA/s)$")
#axs.legend()
plt.tight_layout()
plt.savefig("plot_output/bardiagram_differences_fraction_trans.pdf")

```

```python
#variation translation rate for different nutrient qualities

# Plot different nutrient qualifies....how growth is varying with ribosome fraction
#decide which nutrient qualities should be probed

a0v=0.0013*20#
gamma_maxv=20
betav=1
nuv=10
phiOv=0.35
fracactivev=0.65
gamma_maxvl=np.linspace(0.1,20.,8)

fig, axs = plt.subplots(2,1, figsize=(2.8,4.6))
axs[0].set_xlim(0,2.2)
axs[1].set_xlim(0,2.2)

for nuv in [2,4,6,8,10]:

    alphRoptl=[]
    groptl=[]
    proptl=[]
    gropt_transllist=[]
    for il in range(0,gamma_maxvl.shape[0]):
        colorc=colorl[il]
        x=np.linspace(0.01,(1.-phiOv)*0.99,100)
        #get solution vor varying parameters (here phiR)
        [grc,pcc,tcc]=grsolution(betav,gamma_maxvl[il],nuv,a0v,phiOv,x,fracactivev)
        #get phiR for optimal growth
        gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxvl[il],nuv,a0v,phiOv,fracactivev)
        alphRoptl.append(gropt_alphaR)
        groptl.append(gropt_gr)
        proptl.append(gropt_pc)
        gropt_transllist.append(gropt_transl)
        #get phi for pc levels constant
        #avaluev=10.0*a0v
        #pccon_alphaR,pccon_pc,pccon_gr=constPCsolution(betav,gamma_maxv,nuv,a0v,avaluev,phiOv)

    #now look at growth-optimal solution

    gammaarray=np.linspace(0.,gamma_maxv,100)
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gammaarray,nuv,a0v,phiOv,fracactivev)
    #ribosome fraction
    axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k')
    #translation
    axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

    #plot different nutrient values as examples
    for il in range(0,gamma_maxvl.shape[0]):
        axs[0].plot(groptl[il],100*alphRoptl[il],color=colorl[il],marker='o')
        #plot transltion rate
        axs[1].plot(groptl[il],gropt_transllist[il],color=colorl[il],marker='o')

###############################
#now do same thing for varying nutrient quality more smoothling
################################

nul=np.linspace(0.05*nuv,nuv,6)

x=np.linspace(0.01,(1.-phiOv)*0.99,100)

alphRoptl=[]
groptl=[]
proptl=[]
transllist=[]

for il in range(0,nul.shape[0]):
    colorc=colorl[il]
    #get solution vor varying parameters (here phiR)
    nuv=nul[il]
    [grc,pcc,tcc]=grsolution(betav,gamma_maxv,nuv,a0v,phiOv,x,fracactivev)
    #get phiR for optimal growth
    gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuv,a0v,phiOv,fracactivev)
    alphRoptl.append(gropt_alphaR)
    groptl.append(gropt_gr)
    proptl.append(gropt_pc)
    transllist.append(gropt_transl)

#now look at growth-optimal solution
nuarray=np.linspace(0.01,10,100)
gropt_alphaR,gropt_pc,gropt_gr,gropt_transl=optphiR_solution(betav,gamma_maxv,nuarray,a0v,phiOv,fracactivev)
#ribosome fraction
axs[0].plot(gropt_gr,100*gropt_alphaR,ls='-',color='k',label='opt. growth')
#translation
axs[1].plot(gropt_gr,gropt_transl,ls='-',color='k')

#offset
fitc=np.polyfit(gropt_gr[int(nuarray.shape[0]/2.):],100*gropt_alphaR[int(nuarray.shape[0]/2.):],1)
xgrr=np.linspace(0,2,20)
axs[0].plot(xgrr,xgrr*fitc[0]+fitc[1],color='k',ls='--')

#plot different nutrient values as examples
for il in range(0,nul.shape[0]):
    axs[0].plot(groptl[il],100*alphRoptl[il],color='gray',marker='o')
    axs[1].plot(groptl[il],transllist[il],color='gray',marker='o')

if 3>2:
    #fit Mori at al.
    grr=np.linspace(0,2,100)
    #axs[0].plot(grr,0.0967+0.2206*grr,color='r',label='Mori et al',alpha=0.5)
    #axs[1,0].plot(,color='r',label='observ transl.')
    #take data from RNAseq (c limitation)
    grdatac=np.array([0.91,0.75, 0.56, 0.51, 0.3 ])
    ribosabundanc=np.array([19.18590608, 14.22365961, 12.50443378, 10.73007257,  8.74428159])/100.
    axs[0].plot(grdatac,100*ribosabundanc,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='RNA-Seq',zorder=-1)
    #fitc=np.polyfit(grdatac,ribosabundanc,1)
    #grrc=np.linspace(0,2,100)
    #axs[0].plot(grrc,100*(grrc*fitc[0]+fitc[1]),color='r',label='RNA-seq',alpha=0.5)

if 3>2:#TRANSLATION SPEED - DAI AT AL.
        grdatac=np.array([1.83,1.28,1.12,0.98,0.75,0.69,0.69,0.55,0.5,0.46,0.41,0.34,0.38,0.33,0.29,0.2,0.23,0.13,0.035])
        elongr=np.array([16.7,16.3,16.1,15.9,14.9,15,14.7,13.7,13.1,12.6,13,12.4,12.4,12,12.1,11.6,12.3,10.7,9.4])
        axs[1].plot(grdatac,elongr,marker='s',ls='',markeredgewidth=2,markeredgecolor='k',markerfacecolor='None',label='Dai et al',zorder=-1)

################
#finish plotting
################

axs[0].set_ylabel('allocation to translation $\\alpha_R$')
axs[0].set_xlabel("growth rate $\lambda\, (1/h)$")

axs[1].set_ylabel('tran. elongation speed $(AA/s)$')
axs[1].set_xlabel("growth rate $\lambda\, (1/h)$")
axs[0].set_ylim(0,70)

plt.tight_layout()
plt.savefig("plot_output/growthrate_vs_alpha_vartrans_opt_diffnu.pdf")

```

```python

```