```python
try:
    import openmdao.api as om
    import dymos as dm
except ImportError:
    !python -m pip install openmdao[notebooks]
    !python -m pip install dymos[docs]
    import openmdao.api as om
    import dymos as dm
```

# Water Rocket

Author: Bernardo Bahia Monteiro (bbahia@umich.edu)

In this example, we will optimize a water rocket for range and height at the apogee, using design variables that are easily modifiable just before launch: the empty mass, the initial water volume and the launch angle.
This example builds on [multi-phase cannonball](../multi_phase_cannonball/multi_phase_cannonball.md) ane is adapted from _Optimization of a Water Rocket in OpenMDAO/Dymos_ {cite}`bbahia_2020`.

## Nomenclature

| Symbol               | definition                             |
|----------------------|----------------------------------------|
| $v_\text{out}$       | water exit speed at the nozzle         |
| $A_\text{out}$       | nozzle area                            |
| $V_w$                | water volume in the rocket             |
| $p$                  | pressure in the rocket                 |
| $p_a$                | ambient pressure                       |
| $\dot{\,}$           | time derivative                        |
| $k$                  | polytropic constant                    |
| $V_b$                | internal volume of the rocket          |
| $\rho_w$             | water density                          |
| $T$                  | thrust                                 |
| $q$                  | dynamic pressure                       |
| $S$                  | cross sectional area                   |
| $(\cdot)_0$          | value of $(\cdot)$ at $t=0$            |
| $t$                  | time                                   |

## Problem Formulation

A natural objective function for a water rocket is the maximum height achieved by the rocket during flight, or the horizontal distance it travels, i.e. its range.
The design of a water rocket is somewhat constrained by the soda bottle used as its engine.
This means that the volume available for water and air is fixed, the initial launch pressure is limited by the bottle's strength (since the pressure is directly related to the energy available for the rocket, it is easy to see that it should be as high as possible) and the nozzle throat area is also fixed.
Given these manufacturing constraints, the design variables we are left with are the empty mass (it can be easily changed through adding ballast), the water volume at the launch, and the launch angle.
With this considerations in mind, a natural formulation for the water rocket problem is

\begin{align}
    \text{maximize}   &\quad \text{range or height} \\
    \text{w.r.t.}     &\quad \text{empty mass, initial water volume, launch angle, trajectory} \\
    \text{subject to} &\quad \text{flight dynamics} \\
                      &\quad \text{fluid dynamics inside the rocket} \\
                      &\quad 0 < \text{initial water volume} < \text{volume of bottle} \\
                      &\quad 0^\circ < \text{launch angle} < 90^\circ \\
                      &\quad 0 < \text{empty mass}
\end{align}

##  Model

The water rocket model is divided into three basic components: a *water engine*, responsible for modelling the fluid dynamics inside the rocket and returning its thrust;  the *aerodynamics*, responsible for calculating the atmospheric drag of the rocket; and the *equations of motion*, responsible for propagating the rocket's trajectory in time, using Newton's laws and the forces provided by the other two components.

In order to integrate these three basic components, some additional interfacing components are necessary: an atmospheric model to provide values of ambient pressure for the water engine and air density to the calculation of the dynamic pressure for the aerodynamic model, and a component that calculates the instantaneous mass of the rocket by summing the water mass with the rocket's empty mass.
The high level layout of this model is shown in below.

N2 diagram for the water rocket model

`atmos`, `dynamic_pressure`, `aero` and `eom` are the same models used in [multi-phase cannonball](../multi_phase_cannonball/multi_phase_cannonball.md).
The remaining components are discussed below.

```{Warning}
The `eom` component has a singularity in the flight path angle derivative when the flight speed is zero.
This happens because the rotational dynamics are not modelled.
This can cause convergence problems if the launch velocity is set to zero or the launch angle is set to $90^\circ$
```

```{Note}
Since the range of altitudes achieved by the water rocket is very small (100m), the air density and pressure are practically constant, thus the use of an atmospheric model is not necessary. However, using it makes it easier to reuse code from [multi-phase cannonball (../multi_phase_cannonball/multi_phase_cannonball.md).
```

### Water engine

The water engine is modelled by assuming that the air expansion in the rocket
follows an adiabatic process and the water flow is incompressible and inviscid,
i.e.  it follows Bernoulli's equation. We also make the following simplifying
assumptions:

1. The thrust developed after the water is depleted is negligible
2. The area inside the bottle is much smaller than the nozzle area
3. The inertial forces do not affect the fluid dynamics inside the bottle

This simplified modelling can be found in Prusa[@Prusa2000].
A more rigorous formulation, which drops all these simplifying assumptions can be found in Wheeler[@Wheeler2002], Gommes[@Gommes2010], and Barria-Perotti[@BarrioPerotti2010].

The first assumption leads to an underestimation of the rocket performance, since the air left in the bottle after it is out of water is known to generate appreciable thrust[@Thorncroft2009].
This simplified model, however, produces physically meaningful results.

There are two states in this dynamical model, the water volume in the rocket $V_w$ and the gauge pressure inside the rocket $p$.
The constitutive equations and the N2 diagram showing the model organization are shown below.

### Constitutive equations of the water engine model
| Component              | Equation                                                    |
| -----------------------|-------------------------------------------------------------|
| water_exhaust_speed    | $v_\text{out} = \sqrt{2(p-p_a)/\rho_w}$                     |
| water_flow_rate        | $\dot{V}_w = -v_\text{out} A_\text{out}$                    |
| pressure_rate          | $\dot{p} = kp\frac{\dot{V_w}}{(V_b-V_w)}$                   |
| water_thrust           | $T = (\rho_w v_\text{out})(v_\text{out}A_\text{out})$       |

N2 diagram for the water engine group

### Water engine

The water engine is modelled by assuming that the air expansion in the rocket
follows an adiabatic process and the water flow is incompressible and inviscid,
i.e.  it follows Bernoulli's equation. We also make the following simplifying
assumptions:

1. The thrust developed after the water is depleted is negligible
2. The area inside the bottle is much smaller than the nozzle area
3. The inertial forces do not affect the fluid dynamics inside the bottle

This simplified modelling can be found in Prusa {cite}`Prusa2000`.
A more rigorous formulation, which drops all these simplifying assumptions can be found in Wheeler {cite}`Wheeler2002`, Gommes {cite}`Gommes2010`, and Barria-Perotti {cite}`BarrioPerotti2010`.

The first assumption leads to an underestimation of the rocket performance, since the air left in the bottle after it is out of water is known to generate appreciable thrust {cite}`Thorncroft2009`.
This simplified model, however, produces physically meaningful results.

There are two states in this dynamical model, the water volume in the rocket $V_w$ and the gauge pressure inside the rocket $p$.
The constitutive equations and the N2 diagram showing the model organization are shown below.

### Constitutive equations of the water engine model
| Component              | Equation                                                    |
| -----------------------|-------------------------------------------------------------|
| water_exhaust_speed    | $v_\text{out} = \sqrt{2(p-p_a)/\rho_w}$                     |
| water_flow_rate        | $\dot{V}_w = -v_\text{out} A_\text{out}$                    |
| pressure_rate          | $\dot{p} = kp\frac{\dot{V_w}}{(V_b-V_w)}$                   |
| water_thrust           | $T = (\rho_w v_\text{out})(v_\text{out}A_\text{out})$       |

N2 diagram for the water engine group

```python
import numpy as np

import openmdao.api as om

class WaterEngine(om.Group):
    """
    Computes thrust and water flow for a water.

    Simplifications:
     - the pressure due to the water column in the non inertial frame (i.e.
       under a+g acceleration) is insignificant compared to the air pressure
     - the water does not have appreciable speed inside the bottle
    """
    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        self.add_subsystem(name='water_exhaust_speed',
                           subsys=_WaterExhaustSpeed(num_nodes=nn),
                           promotes=['p', 'p_a', 'rho_w'])

        self.add_subsystem(name='water_flow_rate',
                           subsys=_WaterFlowRate(num_nodes=nn),
                           promotes=['A_out', 'Vdot'])

        self.add_subsystem(name='pressure_rate',
                           subsys=_PressureRate(num_nodes=nn),
                           promotes=['p', 'k', 'V_b', 'V_w', 'Vdot', 'pdot'])

        self.add_subsystem(name='water_thrust',
                           subsys=_WaterThrust(num_nodes=nn),
                           promotes=['rho_w', 'A_out', 'F'])

        self.connect('water_exhaust_speed.v_out', 'water_flow_rate.v_out')
        self.connect('water_exhaust_speed.v_out', 'water_thrust.v_out')

```

```python
class _WaterExhaustSpeed(om.ExplicitComponent):
    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        self.add_input(name='rho_w', val=1e3*np.ones(nn), desc='water density', units='kg/m**3')
        self.add_input(name='p', val=6.5e5*np.ones(nn), desc='air pressure', units='N/m**2')  # 5.5bar = 80 psi
        self.add_input(name='p_a', val=1.01e5*np.ones(nn), desc='air pressure', units='N/m**2')

        self.add_output(name='v_out', shape=(nn,), desc='water exhaust speed', units='m/s')

        ar = np.arange(nn)

        self.declare_partials(of='*', wrt='*', rows=ar, cols=ar)

    def compute(self, inputs, outputs):
        p = inputs['p']
        p_a = inputs['p_a']
        rho_w = inputs['rho_w']

        outputs['v_out'] = np.sqrt(2*(p-p_a)/rho_w)

    def compute_partials(self, inputs, partials):
        p = inputs['p']
        p_a = inputs['p_a']
        rho_w = inputs['rho_w']

        v_out = np.sqrt(2*(p-p_a)/rho_w)

        partials['v_out', 'p'] = 1/v_out/rho_w
        partials['v_out', 'p_a'] = -1/v_out/rho_w
        partials['v_out', 'rho_w'] = dv_outdrho_w = 1/v_out*(-(p-p_a)/rho_w**2)
```

```python
class _PressureRate(om.ExplicitComponent):
    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        self.add_input(name='p', val=np.ones(nn), desc='air pressure', units='N/m**2')
        self.add_input(name='k', val=1.4*np.ones(nn), desc='polytropic coefficient for expansion', units=None)
        self.add_input(name='V_b', val=2e-3*np.ones(nn), desc='bottle volume', units='m**3')
        self.add_input(name='V_w', val=1e-3*np.ones(nn), desc='water volume', units='m**3')
        self.add_input(name='Vdot', shape=(nn,), desc='water flow', units='m**3/s')

        self.add_output(name='pdot', shape=(nn,), desc='pressure derivative', units='N/m**2/s')

        ar = np.arange(nn)

        self.declare_partials(of='*', wrt='*', rows=ar, cols=ar)

    def compute(self, inputs, outputs):
        p = inputs['p']
        k = inputs['k']
        V_b = inputs['V_b']
        V_w = inputs['V_w']
        Vdot = inputs['Vdot']

        pdot = p*k*Vdot/(V_b-V_w)

        outputs['pdot'] = pdot

    def compute_partials(self, inputs, partials):
        p = inputs['p']
        k = inputs['k']
        V_b = inputs['V_b']
        V_w = inputs['V_w']
        Vdot = inputs['Vdot']

        partials['pdot', 'p'] = k*Vdot/(V_b-V_w)
        partials['pdot', 'k'] = p*Vdot/(V_b-V_w)
        partials['pdot', 'V_b'] = -p*Vdot/(V_b-V_w)**2
        partials['pdot', 'V_w'] = p*Vdot/(V_b-V_w)**2
        partials['pdot', 'Vdot'] = p*k/(V_b-V_w)
```

```python
class _WaterFlowRate(om.ExplicitComponent):
    """ Computer water flow rate"""
    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        self.add_input(name='A_out', val=np.ones(nn), desc='nozzle outlet area', units='m**2')
        self.add_input(name='v_out', val=np.zeros(nn), desc='water exhaust speed', units='m/s')

        self.add_output(name='Vdot', shape=(nn,), desc='water flow', units='m**3/s')

        ar = np.arange(nn)

        self.declare_partials(of='*', wrt='*', rows=ar, cols=ar)

    def compute(self, inputs, outputs):
        A_out = inputs['A_out']
        v_out = inputs['v_out']

        outputs['Vdot'] = -v_out*A_out

    def compute_partials(self, inputs, partials):
        A_out = inputs['A_out']
        v_out = inputs['v_out']

        partials['Vdot', 'A_out'] = -v_out
        partials['Vdot', 'v_out'] = -A_out

```

The `_MassAdder` component calculates the rocket's instantaneous mass by
summing the water mass with the rockets empty mass, i.e.

\begin{align}
    m = m_\text{empty}+\rho_w V_w
\end{align}

```python
class _MassAdder(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('num_nodes', types=int)

    def setup(self):
        nn = self.options['num_nodes']

        self.add_input('m_empty', val=np.zeros(nn), desc='empty mass', units='kg')
        self.add_input('V_w', val=1e-3*np.ones(nn), desc='water volume', units='m**3')
        self.add_input('rho_w', val=1e3*np.ones(nn), desc="water density", units='kg/m**3')

        self.add_output('m', val=np.zeros(nn), desc='total mass', units='kg')

        ar = np.arange(nn)
        self.declare_partials('*', '*', cols=ar, rows=ar)

    def compute(self, inputs, outputs):
        outputs['m'] = inputs['m_empty'] + inputs['rho_w']*inputs['V_w']

    def compute_partials(self, inputs, jacobian):
        jacobian['m', 'm_empty'] = 1
        jacobian['m', 'rho_w'] = inputs['V_w']
        jacobian['m', 'V_w'] = inputs['rho_w']
```

Now these components are joined in a single group

```python
class WaterPropulsionODE(om.Group):

    def initialize(self):
        self.options.declare('num_nodes', types=int)
        self.options.declare('ballistic', types=bool, default=False,
                             desc='If True, neglect propulsion system.')

    def setup(self):
        nn = self.options['num_nodes']

        self.add_subsystem(name='atmos',
                           subsys=USatm1976Comp(num_nodes=nn))

        if not self.options['ballistic']:
            self.add_subsystem(name='water_engine',
                               subsys=WaterEngine(num_nodes=nn))

            self.add_subsystem(name='mass_adder',
                               subsys=_MassAdder(num_nodes=nn))

        self.add_subsystem(name='dynamic_pressure',
                           subsys=DynamicPressureComp(num_nodes=nn))

        self.add_subsystem(name='aero',
                           subsys=LiftDragForceComp(num_nodes=nn))

        self.add_subsystem(name='eom',
                           subsys=FlightPathEOM2D(num_nodes=nn))

        self.connect('atmos.rho', 'dynamic_pressure.rho')
        self.connect('dynamic_pressure.q', 'aero.q')

        self.connect('aero.f_drag', 'eom.D')
        self.connect('aero.f_lift', 'eom.L')

        if not self.options['ballistic']:
            self.connect('atmos.pres', 'water_engine.p_a')
            self.connect('water_engine.F', 'eom.T')
            self.connect('mass_adder.m', 'eom.m')
```

## Phases

The flight of the water rocket is split in three distinct phases: propelled ascent, ballistic ascent and ballistic descent.
If the simplification of no thrust without water were lifted, there would be an extra "air propelled ascent" phase between the propelled ascent and ballistic ascent phases.

**Propelled ascent:** is the flight phase where the rocket still has water inside, and hence it is producing thrust.
The thrust is given by the water engine model, and fed into the flight dynamic equations.
It starts at launch and finishes when the water is depleted, i.e. $V_w=0$.

**Ballistic ascent:** is the flight phase where the rocket is ascending ($\gamma>0$) but produces no thrust.
This phase begins at the end of thepropelled ascent phase and ends at the apogee, defined by $\gamma=0$.

**Descent:** is the phase where the rocket is descending without thrust.
It begins at the end of the ballistic ascent phase and ends with ground impact, i.e. $h=0$.

```python
def new_propelled_ascent_phase(transcription):
    propelled_ascent = dm.Phase(ode_class=WaterPropulsionODE,
                                transcription=transcription)

    # All initial states except flight path angle and water volume are fixed
    # Final flight path angle is fixed (we will set it to zero so that the phase ends at apogee)
    # Final water volume is fixed (we will set it to zero so that phase ends when bottle empties)
    propelled_ascent.set_time_options(
        fix_initial=True, duration_bounds=(0.001, 0.5), duration_ref=0.1, units='s')
    propelled_ascent.add_state('r', units='m', rate_source='eom.r_dot',
                               fix_initial=True, fix_final=False, ref=1.0, defect_ref=1.0)
    propelled_ascent.add_state('h', units='m', rate_source='eom.h_dot', targets=['atmos.h'],
                               fix_initial=True, fix_final=False, ref=1.0, defect_ref=1.0)
    propelled_ascent.add_state('gam', units='deg', rate_source='eom.gam_dot', targets=['eom.gam'],
                               fix_initial=False, fix_final=False, lower=0, upper=85.0, ref=90)
    propelled_ascent.add_state('v', units='m/s', rate_source='eom.v_dot', targets=['dynamic_pressure.v', 'eom.v'],
                               fix_initial=True, fix_final=False, ref=100, defect_ref=100)

    propelled_ascent.add_state('p', units='bar', rate_source='water_engine.pdot',
                               targets=['water_engine.p'], fix_initial=True, fix_final=False,
                               lower=1.02)
    propelled_ascent.add_state('V_w', units='L', rate_source='water_engine.Vdot',
                               targets=['water_engine.V_w', 'mass_adder.V_w'],
                               fix_initial=False, fix_final=True, ref=10, defect_ref=10)

    propelled_ascent.add_parameter(
        'S', targets=['aero.S'], units='m**2')
    propelled_ascent.add_parameter(
        'm_empty', targets=['mass_adder.m_empty'], units='kg')
    propelled_ascent.add_parameter(
        'V_b', targets=['water_engine.V_b'], units='m**3')

    propelled_ascent.add_timeseries_output('water_engine.F', 'T', units='N')

    return propelled_ascent
```

```python
def new_ballistic_ascent_phase(transcription):
    ballistic_ascent = dm.Phase(ode_class=WaterPropulsionODE, transcription=transcription,
                                ode_init_kwargs={'ballistic': True})

    # All initial states are free (they will be  linked to the final stages of propelled_ascent).
    # Final flight path angle is fixed (we will set it to zero so that the phase ends at apogee)
    ballistic_ascent.set_time_options(
        fix_initial=False, initial_bounds=(0.001, 1), duration_bounds=(0.001, 10),
        duration_ref=1, units='s')

    ballistic_ascent.add_state('r', units='m', rate_source='eom.r_dot', fix_initial=False,
                               fix_final=False)
    ballistic_ascent.add_state('h', units='m', rate_source='eom.h_dot', targets=['atmos.h'],
                               fix_initial=False, fix_final=False)
    ballistic_ascent.add_state('gam', units='deg', rate_source='eom.gam_dot', targets=['eom.gam'],
                               fix_initial=False, fix_final=True, upper=89)
    ballistic_ascent.add_state('v', units='m/s', rate_source='eom.v_dot',
                               targets=['dynamic_pressure.v', 'eom.v'], fix_initial=False,
                               fix_final=False)

    ballistic_ascent.add_parameter('S', targets=['aero.S'], units='m**2')
    ballistic_ascent.add_parameter('m_empty', targets=['eom.m'], units='kg')

    return ballistic_ascent
```

```python
def new_descent_phase(transcription):
    descent = dm.Phase(ode_class=WaterPropulsionODE, transcription=transcription,
                       ode_init_kwargs={'ballistic': True})

    # All initial states and time are free (they will be linked to the final states of ballistic_ascent).
    # Final altitude is fixed (we will set it to zero so that the phase ends at ground impact)
    descent.set_time_options(initial_bounds=(.5, 100), duration_bounds=(.5, 100),
                             duration_ref=10, units='s')

    descent.add_state('r', units='m', rate_source='eom.r_dot', fix_initial=False, fix_final=False)
    descent.add_state('h', units='m', rate_source='eom.h_dot', targets=['atmos.h'],
                      fix_initial=False, fix_final=True)
    descent.add_state('gam', units='deg', rate_source='eom.gam_dot', targets=['eom.gam'],
                      fix_initial=False, fix_final=False)
    descent.add_state('v', units='m/s', rate_source='eom.v_dot',
                      targets=['dynamic_pressure.v', 'eom.v'], fix_initial=False, fix_final=False)

    descent.add_parameter('S', targets=['aero.S'], units='m**2')
    descent.add_parameter('mass', targets=['eom.m'], units='kg')

    return descent
```

##  Model parameters

The model requires a few constant parameters.
The values used are shown in the following table.

Values for parameters in the water rocket model

|   Parameter        | Value                | Unit         | Reference                                           |
|--------------------|----------------------|--------------|-----------------------------------------------------|
| $C_D$              | 0.3450               | -            | {cite}`BarrioPerotti2009`                           |
| $S$                | $\pi 106^2/4$        | $mm^2$       | {cite}`BarrioPerotti2009`                           |
| $k$                | 1.2                  | -            | {cite}`Thorncroft2009` {cite}`Fischer2020` {cite}`Romanelli2013`   |
| $A_\text{out}$     | $\pi22^2/4$          | $mm^2$       | {cite}`aircommand_nozzle`                                |
| $V_b$              | 2                    | L            |                                                     |
| $\rho_w$           | 1000                 | $kg/m^3$     |                                                     |
| $p_0$              | 6.5                  | bar          |                                                     |
| $v_0$              | 0.1                  | $m/s$        |                                                     |
| $h_0$              | 0                    | $m$          |                                                     |
| $r_0$              | 0                    | $m$          |                                                     |

Values for the bottle volume $V_b$, its cross-sectional area $S$ and the nozzle area $A_\text{out}$ are determined by the soda bottle that makes the rocket primary structure, and thus are not easily modifiable by the designer.
The polytropic coefficient $k$ is a function of the moist air characteristics inside the rocket.
The initial speed $v_0$ must be set to a value higher than zero, otherwise the flight dynamic equations become singular.
This issue arises from the angular dynamics of the rocket not being modelled.
The drag coefficient $C_D$ is sensitive to the aerodynamic design, but can be optimized by a single discipline analysis.
The initial pressure $p_0$ should be maximized in order to obtain the maximum range or height for the rocket.
It is limited by the structural properties of the bottle, which are modifiable by the designer, since the bottle needs to be available commercially.
Finally, the starting point of the rocket is set to the origin.

## Putting it all together

The different phases must be combined in a single trajectory, and linked in a sequence.
Here we also define the design variables.

```python
def new_water_rocket_trajectory(objective):
    tx_prop = dm.Radau(num_segments=50, order=3, compressed=True)
    tx_bal = dm.Radau(num_segments=10, order=3, compressed=True)
    tx_desc = dm.Radau(num_segments=10, order=3, compressed=True)
    traj = dm.Trajectory()

    # Add phases to trajectory
    propelled_ascent = traj.add_phase('propelled_ascent', new_propelled_ascent_phase(tx_prop))
    ballistic_ascent = traj.add_phase('ballistic_ascent', new_ballistic_ascent_phase(tx_bal))
    descent = traj.add_phase('descent', new_descent_phase(tx_desc))

    # Link phases
    traj.link_phases(phases=['propelled_ascent', 'ballistic_ascent'], vars=['*'])
    traj.link_phases(phases=['ballistic_ascent', 'descent'], vars=['*'])

    # Set objective function
    if objective == 'height':
        ballistic_ascent.add_objective('h', loc='final', ref=-1.0)
    elif objective == 'range':
        descent.add_objective('r', loc='final', ref=-0.01)
    else:
        raise ValueError(f"objective='{objective}' is not defined. Try using 'height' or 'range'")

    # Add design parameters to the trajectory.
    traj.add_parameter('CD',
                       targets={'propelled_ascent': ['aero.CD'],
                                'ballistic_ascent': ['aero.CD'],
                                'descent': ['aero.CD']},
                       val=0.3450, units=None, opt=False)
    traj.add_parameter('CL',
                       targets={'propelled_ascent': ['aero.CL'],
                                'ballistic_ascent': ['aero.CL'],
                                'descent': ['aero.CL']},
                       val=0.0, units=None, opt=False)
    traj.add_parameter('T',
                       targets={'ballistic_ascent': ['eom.T'],
                                'descent': ['eom.T']},
                       val=0.0, units='N', opt=False)
    traj.add_parameter('alpha',
                       targets={'propelled_ascent': ['eom.alpha'],
                                'ballistic_ascent': ['eom.alpha'],
                                'descent': ['eom.alpha']},
                       val=0.0, units='deg', opt=False)

    traj.add_parameter('m_empty', units='kg', val=0.15,
                       targets={'propelled_ascent': 'm_empty',
                                'ballistic_ascent': 'm_empty',
                                'descent': 'mass'},
                       lower=0, upper=1, ref=0.1,
                       opt=True)
    traj.add_parameter('V_b', units='m**3', val=2e-3,
                       targets={'propelled_ascent': 'V_b'},
                       opt=False)

    traj.add_parameter('S', units='m**2', val=np.pi*106e-3**2/4, opt=False)
    traj.add_parameter('A_out', units='m**2', val=np.pi*22e-3**2/4.,
                       targets={'propelled_ascent': ['water_engine.A_out']},
                       opt=False)
    traj.add_parameter('k', units=None, val=1.2, opt=False,
                       targets={'propelled_ascent': ['water_engine.k']})

    return traj, {'propelled_ascent': propelled_ascent,
                  'ballistic_ascent': ballistic_ascent,
                  'descent': descent}
```

## Helper Functions to Access the Results

```python
def summarize_results(water_rocket_problem):
    p = water_rocket_problem
    Entry = namedtuple('Entry', 'value unit')
    summary = {
        'Launch angle': Entry(p.get_val('traj.propelled_ascent.timeseries.states:gam',  units='deg')[0, 0], 'deg'),
        'Flight angle at end of propulsion': Entry(p.get_val('traj.propelled_ascent.timeseries.states:gam',
                                                   units='deg')[-1, 0], 'deg'),
        'Empty mass': Entry(p.get_val('traj.parameters:m_empty', units='kg')[0], 'kg'),
        'Water volume': Entry(p.get_val('traj.propelled_ascent.timeseries.states:V_w', 'L')[0, 0], 'L'),
        'Maximum range': Entry(p.get_val('traj.descent.timeseries.states:r', units='m')[-1, 0], 'm'),
        'Maximum height': Entry(p.get_val('traj.ballistic_ascent.timeseries.states:h', units='m')[-1, 0], 'm'),
        'Maximum velocity': Entry(p.get_val('traj.propelled_ascent.timeseries.states:v', units='m/s')[-1, 0], 'm/s'),
    }

    return summary

```

```python
def plot_propelled_ascent(p, exp_out):
    fig, ax = plt.subplots(5, 1, sharex=True, figsize=(4, 8))
    t_imp = p.get_val('traj.propelled_ascent.time', 's')
    t_exp = exp_out.get_val('traj.propelled_ascent.time', 's')

    ax[0].plot(t_imp, p.get_val('traj.propelled_ascent.timeseries.states:p', 'bar'), 'ro', markerfacecolor='None')
    ax[0].plot(t_exp, exp_out.get_val('traj.propelled_ascent.timeseries.states:p', 'bar'), 'r-')
    ax[0].set_ylabel('p (bar)')
    ax[0].set_ylim(bottom=0)

    ax[1].plot(t_imp, p.get_val('traj.propelled_ascent.timeseries.states:V_w', 'L'), 'ro', markerfacecolor='None')
    ax[1].plot(t_exp, exp_out.get_val('traj.propelled_ascent.timeseries.states:V_w', 'L'), 'r-')
    ax[1].set_ylabel('$V_w$ (L)')
    ax[1].set_ylim(0, p.get_val('traj.parameters:V_b', 'L')[0])

    ax[2].plot(t_imp, p.get_val('traj.propelled_ascent.timeseries.T', 'N'), 'ro', markerfacecolor='None')
    ax[2].plot(t_exp, exp_out.get_val('traj.propelled_ascent.timeseries.T', 'N'), 'r-')
    ax[2].set_ylabel('T (N)')
    ax[2].set_ylim(bottom=0)

    ax[3].plot(t_imp, p.get_val('traj.propelled_ascent.timeseries.states:v', 'm/s'), 'ro', markerfacecolor='None')
    ax[3].plot(t_exp, exp_out.get_val('traj.propelled_ascent.timeseries.states:v', 'm/s'), 'r-')
    ax[3].set_ylabel('v (m/s)')
    ax[3].set_ylim(bottom=0)

    ax[4].plot(t_imp, p.get_val('traj.propelled_ascent.timeseries.states:gam', 'deg'), 'ro', markerfacecolor='None')
    ax[4].plot(t_exp, exp_out.get_val('traj.propelled_ascent.timeseries.states:gam', 'deg'), 'r-')
    ax[4].set_ylabel('$\gamma$ (deg)')
    ax[4].yaxis.set_major_locator(mpl.ticker.MaxNLocator(nbins='auto', steps=[1, 1.5, 3, 4.5, 6, 9, 10]))

    ax[-1].set_xlabel('t (s)')

    fig.tight_layout()
```

```python
def plot_states(p, exp_out):
    fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(4, 8), sharex=True)

    states = ['r', 'h', 'v', 'gam']
    units = ['m', 'm', 'm/s', 'deg']
    phases = ['propelled_ascent', 'ballistic_ascent', 'descent']

    time_imp = {'ballistic_ascent': p.get_val('traj.ballistic_ascent.timeseries.time'),
                'propelled_ascent': p.get_val('traj.propelled_ascent.timeseries.time'),
                'descent': p.get_val('traj.descent.timeseries.time')}

    time_exp = {'ballistic_ascent': exp_out.get_val('traj.ballistic_ascent.timeseries.time'),
                'propelled_ascent': exp_out.get_val('traj.propelled_ascent.timeseries.time'),
                'descent': exp_out.get_val('traj.descent.timeseries.time')}

    x_imp = {phase: {state: p.get_val(f"traj.{phase}.timeseries.states:{state}", unit)
                     for state, unit in zip(states, units)
                     }
             for phase in phases
             }

    x_exp = {phase: {state: exp_out.get_val(f"traj.{phase}.timeseries.states:{state}", unit)
                     for state, unit in zip(states, units)
                     }
             for phase in phases
             }

    for i, (state, unit) in enumerate(zip(states, units)):
        axes[i].set_ylabel(f"{state} ({unit})" if state != 'gam' else f'$\gamma$ ({unit})')

        axes[i].plot(time_imp['propelled_ascent'], x_imp['propelled_ascent'][state], 'ro', markerfacecolor='None')
        axes[i].plot(time_imp['ballistic_ascent'], x_imp['ballistic_ascent'][state], 'mo', markerfacecolor='None')
        axes[i].plot(time_imp['descent'], x_imp['descent'][state], 'bo', markerfacecolor='None')
        axes[i].plot(time_exp['propelled_ascent'], x_exp['propelled_ascent'][state], 'r-', label='Propelled Ascent')
        axes[i].plot(time_exp['ballistic_ascent'], x_exp['ballistic_ascent'][state], 'm-', label='Ballistic Ascent')
        axes[i].plot(time_exp['descent'], x_exp['descent'][state], 'b-', label='Descent')

        if state == 'gam':
            axes[i].yaxis.set_major_locator(mpl.ticker.MaxNLocator(nbins='auto', steps=[1, 1.5, 3, 4.5, 6, 9, 10]))
            axes[i].set_yticks(np.arange(-90, 91, 45))

    axes[i].set_xlabel('t (s)')
    axes[0].legend()

    fig.tight_layout()
```

```python
def plot_trajectory(p, exp_out):
    fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(6, 8))

    time_imp = {'ballistic_ascent': p.get_val('traj.ballistic_ascent.timeseries.time'),
                'propelled_ascent': p.get_val('traj.propelled_ascent.timeseries.time'),
                'descent': p.get_val('traj.descent.timeseries.time')}

    time_exp = {'ballistic_ascent': exp_out.get_val('traj.ballistic_ascent.timeseries.time'),
                'propelled_ascent': exp_out.get_val('traj.propelled_ascent.timeseries.time'),
                'descent': exp_out.get_val('traj.descent.timeseries.time')}

    r_imp = {'ballistic_ascent': p.get_val('traj.ballistic_ascent.timeseries.states:r'),
             'propelled_ascent': p.get_val('traj.propelled_ascent.timeseries.states:r'),
             'descent': p.get_val('traj.descent.timeseries.states:r')}

    r_exp = {'ballistic_ascent': exp_out.get_val('traj.ballistic_ascent.timeseries.states:r'),
             'propelled_ascent': exp_out.get_val('traj.propelled_ascent.timeseries.states:r'),
             'descent': exp_out.get_val('traj.descent.timeseries.states:r')}

    h_imp = {'ballistic_ascent': p.get_val('traj.ballistic_ascent.timeseries.states:h'),
             'propelled_ascent': p.get_val('traj.propelled_ascent.timeseries.states:h'),
             'descent': p.get_val('traj.descent.timeseries.states:h')}

    h_exp = {'ballistic_ascent': exp_out.get_val('traj.ballistic_ascent.timeseries.states:h'),
             'propelled_ascent': exp_out.get_val('traj.propelled_ascent.timeseries.states:h'),
             'descent': exp_out.get_val('traj.descent.timeseries.states:h')}

    axes.plot(r_imp['propelled_ascent'], h_imp['propelled_ascent'], 'ro', markerfacecolor='None')
    axes.plot(r_imp['ballistic_ascent'], h_imp['ballistic_ascent'], 'mo', markerfacecolor='None')
    axes.plot(r_imp['descent'], h_imp['descent'], 'bo', markerfacecolor='None')

    axes.plot(r_exp['propelled_ascent'], h_exp['propelled_ascent'], 'r-')
    axes.plot(r_exp['ballistic_ascent'], h_exp['ballistic_ascent'], 'm-')
    axes.plot(r_exp['descent'], h_exp['descent'], 'b-')

    axes.set_xlabel('r (m)')
    axes.set_ylabel('h (m)')
    axes.set_aspect('equal', 'box')

    fig.tight_layout()
```

## Optimizing for Height

```python
from dymos.models.atmosphere import USatm1976Comp
from dymos.examples.aircraft_steady_flight.dynamic_pressure_comp import DynamicPressureComp
from dymos.examples.min_time_climb.aero.lift_drag_force_comp import LiftDragForceComp
from dymos.models.eom.flight_path_eom_2d import FlightPathEOM2D
from dymos.examples.water_rocket.water_engine_comp import _WaterThrust
from dymos.examples.water_rocket.phases import set_sane_initial_guesses
from dymos.examples.water_rocket.water_propulsion_ode import WaterPropulsionODE
import matplotlib.pyplot as plt
import matplotlib as mpl
from collections import namedtuple

p = om.Problem(model=om.Group())

traj, phases = new_water_rocket_trajectory(objective='height')
traj = p.model.add_subsystem('traj', traj)

p.driver = om.pyOptSparseDriver(optimizer='IPOPT')
p.driver.opt_settings['print_level'] = 4
p.driver.opt_settings['max_iter'] = 1000
p.driver.declare_coloring()

# Finish Problem Setup
p.model.linear_solver = om.DirectSolver()

p.setup()
set_sane_initial_guesses(p, phases)

p.run_driver()

summary = summarize_results(p)
for key, entry in summary.items():
    print(f'{key}: {entry.value:6.4f} {entry.unit}')

exp_out = traj.simulate(times_per_seg=200)

# NOTE: only the last figure is shown in the generated docs
plot_propelled_ascent(p, exp_out)
plot_trajectory(p, exp_out)
plot_states(p, exp_out)

plt.show()

# Check results (tolerance is relative unless value is zero)
```

```python
from openmdao.utils.assert_utils import assert_near_equal

# Check results (tolerance is relative unless value is zero)
assert_near_equal(summary['Launch angle'].value, 85, .02)
assert_near_equal(summary['Flight angle at end of propulsion'].value, 85, .02)
assert_near_equal(summary['Empty mass'].value, 0.1425114, 1e-3)
assert_near_equal(summary['Water volume'].value, 0.868281, 1e-3)
assert_near_equal(summary['Maximum range'].value, 15.78, 5)
assert_near_equal(summary['Maximum height'].value, 54.133184, 1e-3)
assert_near_equal(summary['Maximum velocity'].value, 47.320298, 1e-3)
```

# Optimizing for Range

```python
p = om.Problem(model=om.Group())

traj, phases = new_water_rocket_trajectory(objective='range')
traj = p.model.add_subsystem('traj', traj)

p.driver = om.pyOptSparseDriver(optimizer='IPOPT')
p.driver.opt_settings['print_level'] = 4
p.driver.opt_settings['max_iter'] = 1000
p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based'
p.driver.declare_coloring(tol=1.0E-12)

# Finish Problem Setup
p.model.linear_solver = om.DirectSolver()
# p.driver.add_recorder(om.SqliteRecorder('ex_water_rocket.db'))

p.setup()
set_sane_initial_guesses(p, phases)

p.run_driver()

summary = summarize_results(p)
for key, entry in summary.items():
    print(f'{key}: {entry.value:6.4f} {entry.unit}')

exp_out = traj.simulate(times_per_seg=200)

# NOTE: only the last figure is shown in the generated docs
plot_propelled_ascent(p, exp_out)
plot_trajectory(p, exp_out)
plot_states(p, exp_out)

plt.show()

# Check results (tolerance is relative unless value is zero)
```

```python
from openmdao.utils.assert_utils import assert_near_equal

# Check results (tolerance is relative unless value is zero)
assert_near_equal(summary['Launch angle'].value, 46, 0.02)
assert_near_equal(summary['Flight angle at end of propulsion'].value, 38, 0.02)
assert_near_equal(summary['Empty mass'].value, 0.189, 1e-2)
assert_near_equal(summary['Water volume'].value, 1.026, 1e-2)
assert_near_equal(summary['Maximum range'].value, 85.11, 1e-2)
assert_near_equal(summary['Maximum height'].value, 23.08, 1e-2)
assert_near_equal(summary['Maximum velocity'].value, 41.31, 1e-2)
```

## References

```{bibliography}
:filter: docname in docnames
```


# Muography
#### Roland Grinis  -  Researcher at MIPT Nuclear Physics Methods lab  -  CTO at GrinisRIT (grinisrit.com)
#### Danila Riazanov - Sudent at MIPT, JetBrains Research trainee

Code available within `NOA` [github.com/grinisrit/noa](https://github.com/grinisrit/noa) - Non-linear Optimisation Algorithms in `C++17` over [LibTorch](https://pytorch.org/cppdocs)

## Installation

The `conda` environment provided with the repository has all the required dependencies. For this particular tutorial we will need the following `python` packages:

```python
import torch
from torch.utils.cpp_extension import load
```

Now we need to build and load `C++17/CUDA` extensions for `PyTorch`, set up the locations:

```python
!mkdir -p build
noa_location = '../..'
```

If you are running this on Google Colab, you need to clone `NOA` and set `noa_location` accordingly:
```python
!git clone https://github.com/grinisrit/noa.git
noa_location = 'noa'
```

Also, make sure that `ninja` and `g++-9` or higher are available. The following commands will do that for you:
```python
!pip install Ninja
!add-apt-repository ppa:ubuntu-toolchain-r/test -y
!apt update
!apt install gcc-9 g++-9
!update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9
!apt upgrade -y
!gcc --version
!g++ --version
!nvcc --version
```
Finally, you get the extensions into `python` by calling `load`:

```python
!git clone https://github.com/grinisrit/noa.git
noa_location = 'noa'
```

    Cloning into 'noa'...
    remote: Enumerating objects: 2374, done.
    remote: Counting objects: 100% (422/422), done.
    remote: Compressing objects: 100% (250/250), done.
    remote: Total 2374 (delta 245), reused 293 (delta 166), pack-reused 1952
    Receiving objects: 100% (2374/2374), 20.64 MiB | 23.85 MiB/s, done.
    Resolving deltas: 100% (1431/1431), done.

```python
!pip install Ninja
!add-apt-repository ppa:ubuntu-toolchain-r/test -y
!apt update
!apt install gcc-9 g++-9
!update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 --slave /usr/bin/g++ g++ /usr/bin/g++-9
!apt upgrade -y
!gcc --version
!g++ --version
!nvcc --version
```

    Collecting Ninja
      Downloading ninja-1.10.2-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.whl (108 kB)
         |████████████████████████████████| 108 kB 4.0 MB/s
    Installing collected packages: Ninja
    Successfully installed Ninja-1.10.2
    Get:1 https://cloud.r-project.org/bin/linux/ubuntu bionic-cran40/ InRelease [3,626 B]
    Ign:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  InRelease
    Get:3 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB]
    Ign:4 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  InRelease
    Hit:5 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  Release
    Hit:6 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  Release
    Hit:7 http://archive.ubuntu.com/ubuntu bionic InRelease
    Get:9 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic InRelease [15.9 kB]
    Get:10 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB]
    Hit:12 http://ppa.launchpad.net/cran/libgit2/ubuntu bionic InRelease
    Get:13 http://security.ubuntu.com/ubuntu bionic-security/restricted amd64 Packages [543 kB]
    Get:14 http://archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB]
    Hit:15 http://ppa.launchpad.net/deadsnakes/ppa/ubuntu bionic InRelease
    Get:16 http://archive.ubuntu.com/ubuntu bionic-updates/restricted amd64 Packages [575 kB]
    Get:17 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [1,424 kB]
    Hit:18 http://ppa.launchpad.net/graphics-drivers/ppa/ubuntu bionic InRelease
    Get:19 http://security.ubuntu.com/ubuntu bionic-security/main amd64 Packages [2,294 kB]
    Get:20 http://archive.ubuntu.com/ubuntu bionic-updates/multiverse amd64 Packages [34.4 kB]
    Get:21 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [2,730 kB]
    Get:22 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic InRelease [20.8 kB]
    Get:23 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [2,199 kB]
    Get:24 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic/main Sources [1,792 kB]
    Get:25 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic/main amd64 Packages [917 kB]
    Get:26 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 Packages [50.4 kB]
    Fetched 12.9 MB in 8s (1,631 kB/s)
    Reading package lists... Done
    Hit:1 https://cloud.r-project.org/bin/linux/ubuntu bionic-cran40/ InRelease
    Ign:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  InRelease
    Ign:3 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  InRelease
    Hit:4 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  Release
    Hit:5 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  Release
    Hit:6 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic InRelease
    Hit:8 http://security.ubuntu.com/ubuntu bionic-security InRelease
    Hit:9 http://archive.ubuntu.com/ubuntu bionic InRelease
    Hit:11 http://archive.ubuntu.com/ubuntu bionic-updates InRelease
    Hit:12 http://ppa.launchpad.net/cran/libgit2/ubuntu bionic InRelease
    Hit:13 http://archive.ubuntu.com/ubuntu bionic-backports InRelease
    Hit:14 http://ppa.launchpad.net/deadsnakes/ppa/ubuntu bionic InRelease
    Hit:15 http://ppa.launchpad.net/graphics-drivers/ppa/ubuntu bionic InRelease
    Hit:16 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic InRelease
    Reading package lists... Done
    Building dependency tree
    Reading state information... Done
    62 packages can be upgraded. Run 'apt list --upgradable' to see them.
    Reading package lists... Done
    Building dependency tree
    Reading state information... Done
    The following package was automatically installed and is no longer required:
      libnvidia-common-460
    Use 'apt autoremove' to remove it.
    The following additional packages will be installed:
      cpp-9 gcc-11-base gcc-9-base libasan5 libatomic1 libcc1-0 libgcc-9-dev
      libgcc-s1 libgomp1 libitm1 liblsan0 libquadmath0 libstdc++-9-dev libstdc++6
      libtsan0 libubsan1
    Suggested packages:
      gcc-9-locales g++-9-multilib gcc-9-doc libstdc++6-9-dbg gcc-9-multilib
      libgcc1-dbg libgomp1-dbg libitm1-dbg libatomic1-dbg libasan5-dbg
      liblsan0-dbg libtsan0-dbg libubsan1-dbg libquadmath0-dbg libstdc++-9-doc
    The following NEW packages will be installed:
      cpp-9 g++-9 gcc-11-base gcc-9 gcc-9-base libasan5 libgcc-9-dev libgcc-s1
      libstdc++-9-dev libubsan1
    The following packages will be upgraded:
      libatomic1 libcc1-0 libgomp1 libitm1 liblsan0 libquadmath0 libstdc++6
      libtsan0
    8 upgraded, 10 newly installed, 0 to remove and 54 not upgraded.
    Need to get 30.2 MB of archives.
    After this operation, 121 MB of additional disk space will be used.
    Get:1 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 gcc-11-base amd64 11.1.0-1ubuntu1~18.04.1 [19.0 kB]
    Get:2 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libgcc-s1 amd64 11.1.0-1ubuntu1~18.04.1 [41.8 kB]
    Get:3 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libstdc++6 amd64 11.1.0-1ubuntu1~18.04.1 [580 kB]
    Get:4 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 gcc-9-base amd64 9.4.0-1ubuntu1~18.04 [19.3 kB]
    Get:5 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 cpp-9 amd64 9.4.0-1ubuntu1~18.04 [7,489 kB]
    Get:6 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libcc1-0 amd64 11.1.0-1ubuntu1~18.04.1 [40.8 kB]
    Get:7 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libgomp1 amd64 11.1.0-1ubuntu1~18.04.1 [107 kB]
    Get:8 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libitm1 amd64 11.1.0-1ubuntu1~18.04.1 [26.1 kB]
    Get:9 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libatomic1 amd64 11.1.0-1ubuntu1~18.04.1 [9,168 B]
    Get:10 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libasan5 amd64 9.4.0-1ubuntu1~18.04 [393 kB]
    Get:11 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 liblsan0 amd64 11.1.0-1ubuntu1~18.04.1 [150 kB]
    Get:12 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libtsan0 amd64 11.1.0-1ubuntu1~18.04.1 [331 kB]
    Get:13 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libubsan1 amd64 11.1.0-1ubuntu1~18.04.1 [143 kB]
    Get:14 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libquadmath0 amd64 11.1.0-1ubuntu1~18.04.1 [145 kB]
    Get:15 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libgcc-9-dev amd64 9.4.0-1ubuntu1~18.04 [2,356 kB]
    Get:16 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 gcc-9 amd64 9.4.0-1ubuntu1~18.04 [8,257 kB]
    Get:17 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libstdc++-9-dev amd64 9.4.0-1ubuntu1~18.04 [1,719 kB]
    Get:18 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 g++-9 amd64 9.4.0-1ubuntu1~18.04 [8,399 kB]
    Fetched 30.2 MB in 26s (1,143 kB/s)
    Selecting previously unselected package gcc-11-base:amd64.
    (Reading database ... 148486 files and directories currently installed.)
    Preparing to unpack .../gcc-11-base_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking gcc-11-base:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up gcc-11-base:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Selecting previously unselected package libgcc-s1:amd64.
    (Reading database ... 148491 files and directories currently installed.)
    Preparing to unpack .../libgcc-s1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libgcc-s1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Replacing files in old package libgcc1:amd64 (1:8.4.0-1ubuntu1~18.04) ...
    Setting up libgcc-s1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    (Reading database ... 148493 files and directories currently installed.)
    Preparing to unpack .../libstdc++6_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libstdc++6:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Setting up libstdc++6:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Selecting previously unselected package gcc-9-base:amd64.
    (Reading database ... 148493 files and directories currently installed.)
    Preparing to unpack .../00-gcc-9-base_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking gcc-9-base:amd64 (9.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package cpp-9.
    Preparing to unpack .../01-cpp-9_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking cpp-9 (9.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../02-libcc1-0_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libcc1-0:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../03-libgomp1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libgomp1:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../04-libitm1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libitm1:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../05-libatomic1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libatomic1:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package libasan5:amd64.
    Preparing to unpack .../06-libasan5_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking libasan5:amd64 (9.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../07-liblsan0_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking liblsan0:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../08-libtsan0_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libtsan0:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package libubsan1:amd64.
    Preparing to unpack .../09-libubsan1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libubsan1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Preparing to unpack .../10-libquadmath0_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libquadmath0:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package libgcc-9-dev:amd64.
    Preparing to unpack .../11-libgcc-9-dev_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking libgcc-9-dev:amd64 (9.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package gcc-9.
    Preparing to unpack .../12-gcc-9_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking gcc-9 (9.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package libstdc++-9-dev:amd64.
    Preparing to unpack .../13-libstdc++-9-dev_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking libstdc++-9-dev:amd64 (9.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package g++-9.
    Preparing to unpack .../14-g++-9_9.4.0-1ubuntu1~18.04_amd64.deb ...
    Unpacking g++-9 (9.4.0-1ubuntu1~18.04) ...
    Setting up libquadmath0:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up libgomp1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up gcc-9-base:amd64 (9.4.0-1ubuntu1~18.04) ...
    Setting up libatomic1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up libcc1-0:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up libtsan0:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up liblsan0:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up cpp-9 (9.4.0-1ubuntu1~18.04) ...
    Setting up libasan5:amd64 (9.4.0-1ubuntu1~18.04) ...
    Setting up libitm1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up libubsan1:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up libgcc-9-dev:amd64 (9.4.0-1ubuntu1~18.04) ...
    Setting up gcc-9 (9.4.0-1ubuntu1~18.04) ...
    Setting up libstdc++-9-dev:amd64 (9.4.0-1ubuntu1~18.04) ...
    Setting up g++-9 (9.4.0-1ubuntu1~18.04) ...
    Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
    Processing triggers for libc-bin (2.27-3ubuntu1.2) ...
    /sbin/ldconfig.real: /usr/local/lib/python3.7/dist-packages/ideep4py/lib/libmkldnn.so.0 is not a symbolic link

    update-alternatives: using /usr/bin/gcc-9 to provide /usr/bin/gcc (gcc) in auto mode
    Reading package lists... Done
    Building dependency tree
    Reading state information... Done
    Calculating upgrade... Done
    The following packages were automatically installed and are no longer required:
      libnvidia-common-460 linux-headers-4.15.0-153
      linux-headers-4.15.0-153-generic
    Use 'apt autoremove' to remove them.
    The following NEW packages will be installed:
      lib32gcc-s1 linux-headers-4.15.0-154 linux-headers-4.15.0-154-generic
    The following packages have been kept back:
      libcudnn8 libcudnn8-dev libnccl-dev libnccl2
    The following packages will be upgraded:
      binutils binutils-common binutils-x86-64-linux-gnu cuda-compat-11-0
      distro-info-data gnupg2 gzip lib32gcc1 lib32stdc++6 libaudit-common
      libaudit1 libbinutils libc-bin libcublas-dev libcublas10 libcudnn7
      libcudnn7-dev libgcc1 libhogweed4 libldap-2.4-2 libldap-common liblz4-1
      libnettle6 libobjc4 libp11-kit0 libpam-modules libpam-modules-bin
      libpam-runtime libpam-systemd libpam0g libperl5.26 libsasl2-2
      libsasl2-modules-db libseccomp2 libsystemd0 libudev1 libzstd1
      linux-headers-generic linux-libc-dev openssl perl perl-base
      perl-modules-5.26 r-cran-gert r-cran-survival systemd systemd-sysv tar
      ubuntu-keyring udev
    50 upgraded, 3 newly installed, 0 to remove and 4 not upgraded.
    Need to get 485 MB of archives.
    After this operation, 161 MB of additional disk space will be used.
    Get:1 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  cuda-compat-11-0 450.142.00-1 [6,715 kB]
    Get:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  libcublas10 10.2.3.254-1 [43.1 MB]
    Get:3 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libgcc1 amd64 1:11.1.0-1ubuntu1~18.04.1 [41.7 kB]
    Get:4 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 gzip amd64 1.6-5ubuntu1.1 [89.8 kB]
    Get:5 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64  libcublas-dev 10.2.3.254-1 [42.4 MB]
    Get:6 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 lib32gcc1 amd64 1:11.1.0-1ubuntu1~18.04.1 [1,120 B]
    Get:7 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libperl5.26 amd64 5.26.1-6ubuntu0.5 [3,534 kB]
    Get:8 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 lib32gcc-s1 amd64 11.1.0-1ubuntu1~18.04.1 [49.5 kB]
    Get:9 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  libcudnn7-dev 7.6.5.32-1+cuda10.2 [165 MB]
    Get:10 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 perl amd64 5.26.1-6ubuntu0.5 [201 kB]
    Get:11 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 perl-base amd64 5.26.1-6ubuntu0.5 [1,391 kB]
    Get:12 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 lib32stdc++6 amd64 11.1.0-1ubuntu1~18.04.1 [611 kB]
    Get:13 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 perl-modules-5.26 all 5.26.1-6ubuntu0.5 [2,762 kB]
    Get:14 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 tar amd64 1.29b-2ubuntu0.2 [234 kB]
    Get:15 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libc-bin amd64 2.27-3ubuntu1.4 [643 kB]
    Get:16 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libaudit-common all 1:2.8.2-1ubuntu1.1 [4,068 B]
    Get:17 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libaudit1 amd64 1:2.8.2-1ubuntu1.1 [38.7 kB]
    Get:18 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libpam0g amd64 1.1.8-3.6ubuntu2.18.04.3 [55.0 kB]
    Get:19 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libpam-modules-bin amd64 1.1.8-3.6ubuntu2.18.04.3 [40.3 kB]
    Get:20 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libpam-modules amd64 1.1.8-3.6ubuntu2.18.04.3 [252 kB]
    Get:21 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libsystemd0 amd64 237-3ubuntu10.51 [207 kB]
    Get:22 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libpam-systemd amd64 237-3ubuntu10.51 [107 kB]
    Get:23 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 systemd amd64 237-3ubuntu10.51 [2,913 kB]
    Get:24 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 udev amd64 237-3ubuntu10.51 [1,102 kB]
    Get:25 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libudev1 amd64 237-3ubuntu10.51 [56.2 kB]
    Get:26 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libpam-runtime all 1.1.8-3.6ubuntu2.18.04.3 [37.1 kB]
    Get:27 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 systemd-sysv amd64 237-3ubuntu10.51 [13.9 kB]
    Get:28 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 liblz4-1 amd64 0.0~r131-2ubuntu3.1 [48.5 kB]
    Get:29 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libzstd1 amd64 1.3.3+dfsg-2ubuntu1.2 [189 kB]
    Get:30 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libnettle6 amd64 3.4.1-0ubuntu0.18.04.1 [111 kB]
    Get:31 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libhogweed4 amd64 3.4.1-0ubuntu0.18.04.1 [140 kB]
    Get:32 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libp11-kit0 amd64 0.23.9-2ubuntu0.1 [187 kB]
    Get:33 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libseccomp2 amd64 2.5.1-1ubuntu1~18.04.1 [43.1 kB]
    Get:34 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 ubuntu-keyring all 2018.09.18.1~18.04.2 [22.3 kB]
    Get:35 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 distro-info-data all 0.37ubuntu0.11 [4,652 B]
    Get:36 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 openssl amd64 1.1.1-1ubuntu2.1~18.04.10 [614 kB]
    Get:37 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 binutils-x86-64-linux-gnu amd64 2.30-21ubuntu1~18.04.5 [1,839 kB]
    Get:38 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 binutils-common amd64 2.30-21ubuntu1~18.04.5 [197 kB]
    Get:39 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 binutils amd64 2.30-21ubuntu1~18.04.5 [3,388 B]
    Get:40 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libbinutils amd64 2.30-21ubuntu1~18.04.5 [489 kB]
    Get:41 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libsasl2-modules-db amd64 2.1.27~101-g0780600+dfsg-3ubuntu2.3 [15.0 kB]
    Get:42 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libsasl2-2 amd64 2.1.27~101-g0780600+dfsg-3ubuntu2.3 [49.2 kB]
    Get:43 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libldap-common all 2.4.45+dfsg-1ubuntu1.10 [15.8 kB]
    Get:44 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libldap-2.4-2 amd64 2.4.45+dfsg-1ubuntu1.10 [154 kB]
    Get:45 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 linux-headers-4.15.0-154 all 4.15.0-154.161 [10.9 MB]
    Get:46 http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu bionic/main amd64 libobjc4 amd64 11.1.0-1ubuntu1~18.04.1 [43.5 kB]
    Get:47 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 linux-headers-4.15.0-154-generic amd64 4.15.0-154.161 [1,256 kB]
    Get:48 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 linux-headers-generic amd64 4.15.0.154.143 [2,440 B]
    Get:49 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 linux-libc-dev amd64 4.15.0-154.161 [988 kB]
    Get:50 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 gnupg2 all 2.2.4-1ubuntu1.4 [5,292 B]
    Get:51 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic/main amd64 r-cran-gert amd64 1.3.2-1cran1.1804.0 [177 kB]
    Get:52 https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64  libcudnn7 7.6.5.32-1+cuda10.2 [189 MB]
    Get:53 http://ppa.launchpad.net/c2d4u.team/c2d4u4.0+/ubuntu bionic/main amd64 r-cran-survival amd64 3.2-12-1cran1.1804.0 [6,612 kB]
    Fetched 485 MB in 10s (49.7 MB/s)
    Extracting templates from packages: 100%
    Preconfiguring packages ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../gzip_1.6-5ubuntu1.1_amd64.deb ...
    Unpacking gzip (1.6-5ubuntu1.1) over (1.6-5ubuntu1) ...
    Setting up gzip (1.6-5ubuntu1.1) ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libperl5.26_5.26.1-6ubuntu0.5_amd64.deb ...
    Unpacking libperl5.26:amd64 (5.26.1-6ubuntu0.5) over (5.26.1-6ubuntu0.3) ...
    Preparing to unpack .../perl_5.26.1-6ubuntu0.5_amd64.deb ...
    Unpacking perl (5.26.1-6ubuntu0.5) over (5.26.1-6ubuntu0.3) ...
    Preparing to unpack .../perl-base_5.26.1-6ubuntu0.5_amd64.deb ...
    Unpacking perl-base (5.26.1-6ubuntu0.5) over (5.26.1-6ubuntu0.3) ...
    Setting up perl-base (5.26.1-6ubuntu0.5) ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../perl-modules-5.26_5.26.1-6ubuntu0.5_all.deb ...
    Unpacking perl-modules-5.26 (5.26.1-6ubuntu0.5) over (5.26.1-6ubuntu0.3) ...
    Preparing to unpack .../tar_1.29b-2ubuntu0.2_amd64.deb ...
    Unpacking tar (1.29b-2ubuntu0.2) over (1.29b-2ubuntu0.1) ...
    Setting up tar (1.29b-2ubuntu0.2) ...
    update-alternatives: warning: forcing reinstallation of alternative /usr/sbin/rmt-tar because link group rmt is broken
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libc-bin_2.27-3ubuntu1.4_amd64.deb ...
    Unpacking libc-bin (2.27-3ubuntu1.4) over (2.27-3ubuntu1.2) ...
    Setting up libc-bin (2.27-3ubuntu1.4) ...
    /sbin/ldconfig.real: /usr/local/lib/python3.7/dist-packages/ideep4py/lib/libmkldnn.so.0 is not a symbolic link

    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libaudit-common_1%3a2.8.2-1ubuntu1.1_all.deb ...
    Unpacking libaudit-common (1:2.8.2-1ubuntu1.1) over (1:2.8.2-1ubuntu1) ...
    Setting up libaudit-common (1:2.8.2-1ubuntu1.1) ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libaudit1_1%3a2.8.2-1ubuntu1.1_amd64.deb ...
    Unpacking libaudit1:amd64 (1:2.8.2-1ubuntu1.1) over (1:2.8.2-1ubuntu1) ...
    Setting up libaudit1:amd64 (1:2.8.2-1ubuntu1.1) ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libpam0g_1.1.8-3.6ubuntu2.18.04.3_amd64.deb ...
    Unpacking libpam0g:amd64 (1.1.8-3.6ubuntu2.18.04.3) over (1.1.8-3.6ubuntu2.18.04.2) ...
    Setting up libpam0g:amd64 (1.1.8-3.6ubuntu2.18.04.3) ...
    (Reading database ... 149564 files and directories currently installed.)
    Preparing to unpack .../libpam-modules-bin_1.1.8-3.6ubuntu2.18.04.3_amd64.deb ...
    Unpacking libpam-modules-bin (1.1.8-3.6ubuntu2.18.04.3) over (1.1.8-3.6ubuntu2.18.04.2) ...
    Setting up libpam-modules-bin (1.1.8-3.6ubuntu2.18.04.3) ...
    (Reading database ... 149566 files and directories currently installed.)
    Preparing to unpack .../libpam-modules_1.1.8-3.6ubuntu2.18.04.3_amd64.deb ...
    Unpacking libpam-modules:amd64 (1.1.8-3.6ubuntu2.18.04.3) over (1.1.8-3.6ubuntu2.18.04.2) ...
    Setting up libpam-modules:amd64 (1.1.8-3.6ubuntu2.18.04.3) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libsystemd0_237-3ubuntu10.51_amd64.deb ...
    Unpacking libsystemd0:amd64 (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Setting up libsystemd0:amd64 (237-3ubuntu10.51) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libpam-systemd_237-3ubuntu10.51_amd64.deb ...
    Unpacking libpam-systemd:amd64 (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Preparing to unpack .../systemd_237-3ubuntu10.51_amd64.deb ...
    Unpacking systemd (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Preparing to unpack .../udev_237-3ubuntu10.51_amd64.deb ...
    Unpacking udev (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Preparing to unpack .../libudev1_237-3ubuntu10.51_amd64.deb ...
    Unpacking libudev1:amd64 (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Setting up libudev1:amd64 (237-3ubuntu10.51) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libpam-runtime_1.1.8-3.6ubuntu2.18.04.3_all.deb ...
    Unpacking libpam-runtime (1.1.8-3.6ubuntu2.18.04.3) over (1.1.8-3.6ubuntu2.18.04.2) ...
    Setting up libpam-runtime (1.1.8-3.6ubuntu2.18.04.3) ...
    Setting up systemd (237-3ubuntu10.51) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../systemd-sysv_237-3ubuntu10.51_amd64.deb ...
    Unpacking systemd-sysv (237-3ubuntu10.51) over (237-3ubuntu10.50) ...
    Preparing to unpack .../liblz4-1_0.0~r131-2ubuntu3.1_amd64.deb ...
    Unpacking liblz4-1:amd64 (0.0~r131-2ubuntu3.1) over (0.0~r131-2ubuntu3) ...
    Setting up liblz4-1:amd64 (0.0~r131-2ubuntu3.1) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libzstd1_1.3.3+dfsg-2ubuntu1.2_amd64.deb ...
    Unpacking libzstd1:amd64 (1.3.3+dfsg-2ubuntu1.2) over (1.3.3+dfsg-2ubuntu1.1) ...
    Setting up libzstd1:amd64 (1.3.3+dfsg-2ubuntu1.2) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libnettle6_3.4.1-0ubuntu0.18.04.1_amd64.deb ...
    Unpacking libnettle6:amd64 (3.4.1-0ubuntu0.18.04.1) over (3.4-1) ...
    Setting up libnettle6:amd64 (3.4.1-0ubuntu0.18.04.1) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libhogweed4_3.4.1-0ubuntu0.18.04.1_amd64.deb ...
    Unpacking libhogweed4:amd64 (3.4.1-0ubuntu0.18.04.1) over (3.4-1) ...
    Setting up libhogweed4:amd64 (3.4.1-0ubuntu0.18.04.1) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libp11-kit0_0.23.9-2ubuntu0.1_amd64.deb ...
    Unpacking libp11-kit0:amd64 (0.23.9-2ubuntu0.1) over (0.23.9-2) ...
    Setting up libp11-kit0:amd64 (0.23.9-2ubuntu0.1) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libseccomp2_2.5.1-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libseccomp2:amd64 (2.5.1-1ubuntu1~18.04.1) over (2.4.3-1ubuntu3.18.04.3) ...
    Setting up libseccomp2:amd64 (2.5.1-1ubuntu1~18.04.1) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../ubuntu-keyring_2018.09.18.1~18.04.2_all.deb ...
    Unpacking ubuntu-keyring (2018.09.18.1~18.04.2) over (2018.09.18.1~18.04.0) ...
    Setting up ubuntu-keyring (2018.09.18.1~18.04.2) ...
    (Reading database ... 149570 files and directories currently installed.)
    Preparing to unpack .../libgcc1_1%3a11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libgcc1 (1:11.1.0-1ubuntu1~18.04.1) over (1:8.4.0-1ubuntu1~18.04) ...
    Setting up libgcc1 (1:11.1.0-1ubuntu1~18.04.1) ...
    (Reading database ... 149571 files and directories currently installed.)
    Preparing to unpack .../00-distro-info-data_0.37ubuntu0.11_all.deb ...
    Unpacking distro-info-data (0.37ubuntu0.11) over (0.37ubuntu0.10) ...
    Preparing to unpack .../01-openssl_1.1.1-1ubuntu2.1~18.04.10_amd64.deb ...
    Unpacking openssl (1.1.1-1ubuntu2.1~18.04.10) over (1.1.1-1ubuntu2.1~18.04.6) ...
    Preparing to unpack .../02-binutils-x86-64-linux-gnu_2.30-21ubuntu1~18.04.5_amd64.deb ...
    Unpacking binutils-x86-64-linux-gnu (2.30-21ubuntu1~18.04.5) over (2.30-21ubuntu1~18.04.4) ...
    Preparing to unpack .../03-binutils-common_2.30-21ubuntu1~18.04.5_amd64.deb ...
    Unpacking binutils-common:amd64 (2.30-21ubuntu1~18.04.5) over (2.30-21ubuntu1~18.04.4) ...
    Preparing to unpack .../04-binutils_2.30-21ubuntu1~18.04.5_amd64.deb ...
    Unpacking binutils (2.30-21ubuntu1~18.04.5) over (2.30-21ubuntu1~18.04.4) ...
    Preparing to unpack .../05-libbinutils_2.30-21ubuntu1~18.04.5_amd64.deb ...
    Unpacking libbinutils:amd64 (2.30-21ubuntu1~18.04.5) over (2.30-21ubuntu1~18.04.4) ...
    Preparing to unpack .../06-cuda-compat-11-0_450.142.00-1_amd64.deb ...
    Unpacking cuda-compat-11-0 (450.142.00-1) over (450.80.02-1) ...
    Preparing to unpack .../07-lib32gcc1_1%3a11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking lib32gcc1 (1:11.1.0-1ubuntu1~18.04.1) over (1:8.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package lib32gcc-s1.
    Preparing to unpack .../08-lib32gcc-s1_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking lib32gcc-s1 (11.1.0-1ubuntu1~18.04.1) ...
    Preparing to unpack .../09-lib32stdc++6_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking lib32stdc++6 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Preparing to unpack .../10-libcublas10_10.2.3.254-1_amd64.deb ...
    Unpacking libcublas10 (10.2.3.254-1) over (10.2.1.243-1) ...
    Preparing to unpack .../11-libcublas-dev_10.2.3.254-1_amd64.deb ...
    Unpacking libcublas-dev (10.2.3.254-1) over (10.2.1.243-1) ...
    Preparing to unpack .../12-libcudnn7-dev_7.6.5.32-1+cuda10.2_amd64.deb ...
    update-alternatives: removing manually selected alternative - switching libcudnn to auto mode
    update-alternatives: using /usr/include/x86_64-linux-gnu/cudnn_v8.h to provide /usr/include/cudnn.h (libcudnn) in auto mode
    Unpacking libcudnn7-dev (7.6.5.32-1+cuda10.2) over (7.6.5.32-1+cuda10.1) ...
    Preparing to unpack .../13-libcudnn7_7.6.5.32-1+cuda10.2_amd64.deb ...
    Unpacking libcudnn7 (7.6.5.32-1+cuda10.2) over (7.6.5.32-1+cuda10.1) ...
    Preparing to unpack .../14-libsasl2-modules-db_2.1.27~101-g0780600+dfsg-3ubuntu2.3_amd64.deb ...
    Unpacking libsasl2-modules-db:amd64 (2.1.27~101-g0780600+dfsg-3ubuntu2.3) over (2.1.27~101-g0780600+dfsg-3ubuntu2.1) ...
    Preparing to unpack .../15-libsasl2-2_2.1.27~101-g0780600+dfsg-3ubuntu2.3_amd64.deb ...
    Unpacking libsasl2-2:amd64 (2.1.27~101-g0780600+dfsg-3ubuntu2.3) over (2.1.27~101-g0780600+dfsg-3ubuntu2.1) ...
    Preparing to unpack .../16-libldap-common_2.4.45+dfsg-1ubuntu1.10_all.deb ...
    Unpacking libldap-common (2.4.45+dfsg-1ubuntu1.10) over (2.4.45+dfsg-1ubuntu1.6) ...
    Preparing to unpack .../17-libldap-2.4-2_2.4.45+dfsg-1ubuntu1.10_amd64.deb ...
    Unpacking libldap-2.4-2:amd64 (2.4.45+dfsg-1ubuntu1.10) over (2.4.45+dfsg-1ubuntu1.6) ...
    Preparing to unpack .../18-libobjc4_11.1.0-1ubuntu1~18.04.1_amd64.deb ...
    Unpacking libobjc4:amd64 (11.1.0-1ubuntu1~18.04.1) over (8.4.0-1ubuntu1~18.04) ...
    Selecting previously unselected package linux-headers-4.15.0-154.
    Preparing to unpack .../19-linux-headers-4.15.0-154_4.15.0-154.161_all.deb ...
    Unpacking linux-headers-4.15.0-154 (4.15.0-154.161) ...
    Selecting previously unselected package linux-headers-4.15.0-154-generic.
    Preparing to unpack .../20-linux-headers-4.15.0-154-generic_4.15.0-154.161_amd64.deb ...
    Unpacking linux-headers-4.15.0-154-generic (4.15.0-154.161) ...
    Preparing to unpack .../21-linux-headers-generic_4.15.0.154.143_amd64.deb ...
    Unpacking linux-headers-generic (4.15.0.154.143) over (4.15.0.153.142) ...
    Preparing to unpack .../22-linux-libc-dev_4.15.0-154.161_amd64.deb ...
    Unpacking linux-libc-dev:amd64 (4.15.0-154.161) over (4.15.0-118.119) ...
    Preparing to unpack .../23-r-cran-gert_1.3.2-1cran1.1804.0_amd64.deb ...
    Unpacking r-cran-gert (1.3.2-1cran1.1804.0) over (1.3.1-1cran1.1804.0) ...
    Preparing to unpack .../24-r-cran-survival_3.2-12-1cran1.1804.0_amd64.deb ...
    Unpacking r-cran-survival (3.2-12-1cran1.1804.0) over (3.2-11-1cran1.1804.0) ...
    Preparing to unpack .../25-gnupg2_2.2.4-1ubuntu1.4_all.deb ...
    Unpacking gnupg2 (2.2.4-1ubuntu1.4) over (2.2.4-1ubuntu1.3) ...
    Setting up libcudnn7 (7.6.5.32-1+cuda10.2) ...
    Setting up libobjc4:amd64 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up r-cran-gert (1.3.2-1cran1.1804.0) ...
    Setting up libldap-common (2.4.45+dfsg-1ubuntu1.10) ...
    Setting up cuda-compat-11-0 (450.142.00-1) ...
    Setting up systemd-sysv (237-3ubuntu10.51) ...
    Setting up libsasl2-modules-db:amd64 (2.1.27~101-g0780600+dfsg-3ubuntu2.3) ...
    Setting up linux-libc-dev:amd64 (4.15.0-154.161) ...
    Setting up libsasl2-2:amd64 (2.1.27~101-g0780600+dfsg-3ubuntu2.3) ...
    Setting up libcudnn7-dev (7.6.5.32-1+cuda10.2) ...
    update-alternatives: using /usr/include/x86_64-linux-gnu/cudnn_v7.h to provide /usr/include/cudnn.h (libcudnn) in manual mode
    Setting up linux-headers-4.15.0-154 (4.15.0-154.161) ...
    Setting up distro-info-data (0.37ubuntu0.11) ...
    Setting up lib32gcc-s1 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up perl-modules-5.26 (5.26.1-6ubuntu0.5) ...
    Setting up binutils-common:amd64 (2.30-21ubuntu1~18.04.5) ...
    Setting up gnupg2 (2.2.4-1ubuntu1.4) ...
    Setting up libcublas10 (10.2.3.254-1) ...
    Setting up libcublas-dev (10.2.3.254-1) ...
    Setting up libperl5.26:amd64 (5.26.1-6ubuntu0.5) ...
    Setting up udev (237-3ubuntu10.51) ...
    invoke-rc.d: could not determine current runlevel
    invoke-rc.d: policy-rc.d denied execution of restart.
    Setting up libldap-2.4-2:amd64 (2.4.45+dfsg-1ubuntu1.10) ...
    Setting up linux-headers-4.15.0-154-generic (4.15.0-154.161) ...
    /etc/kernel/header_postinst.d/dkms:
     * dkms: running auto installation service for kernel 4.15.0-154-generic

    Kernel preparation unnecessary for this kernel.  Skipping...
    applying patch disable_fstack-clash-protection_fcf-protection.patch...patching file Kbuild
    Hunk #1 succeeded at 82 (offset 11 lines).

    Building module:
    cleaning build area...(bad exit status: 2)
    unset ARCH; [ ! -h /usr/bin/cc ] && export CC=/usr/bin/gcc; env NV_VERBOSE=1 'make' -j2 NV_EXCLUDE_BUILD_MODULES='' KERNEL_UNAME=4.15.0-154-generic IGNORE_XEN_PRESENCE=1 IGNORE_CC_MISMATCH=1 SYSSRC=/lib/modules/4.15.0-154-generic/build LD=/usr/bin/ld.bfd modules................................................
    cleaning build area...(bad exit status: 2)

    DKMS: build completed.

    nvidia.ko:
    Running module version sanity check.
     - Original module
       - No original module exists within this kernel
     - Installation
       - Installing to /lib/modules/4.15.0-154-generic/updates/dkms/

    nvidia-modeset.ko:
    Running module version sanity check.
     - Original module
       - No original module exists within this kernel
     - Installation
       - Installing to /lib/modules/4.15.0-154-generic/updates/dkms/

    nvidia-drm.ko:
    Running module version sanity check.
     - Original module
       - No original module exists within this kernel
     - Installation
       - Installing to /lib/modules/4.15.0-154-generic/updates/dkms/

    nvidia-peermem.ko:
    Running module version sanity check.
     - Original module
       - No original module exists within this kernel
     - Installation
       - Installing to /lib/modules/4.15.0-154-generic/updates/dkms/

    nvidia-uvm.ko:
    Running module version sanity check.
     - Original module
       - No original module exists within this kernel
     - Installation
       - Installing to /lib/modules/4.15.0-154-generic/updates/dkms/

    depmod...

    DKMS: install completed.
       ...done.
    Setting up openssl (1.1.1-1ubuntu2.1~18.04.10) ...
    Setting up lib32stdc++6 (11.1.0-1ubuntu1~18.04.1) ...
    Setting up r-cran-survival (3.2-12-1cran1.1804.0) ...
    Setting up lib32gcc1 (1:11.1.0-1ubuntu1~18.04.1) ...
    Setting up linux-headers-generic (4.15.0.154.143) ...
    Setting up libpam-systemd:amd64 (237-3ubuntu10.51) ...
    Setting up libbinutils:amd64 (2.30-21ubuntu1~18.04.5) ...
    Setting up perl (5.26.1-6ubuntu0.5) ...
    Setting up binutils-x86-64-linux-gnu (2.30-21ubuntu1~18.04.5) ...
    Setting up binutils (2.30-21ubuntu1~18.04.5) ...
    Processing triggers for mime-support (3.60ubuntu1) ...
    Processing triggers for libc-bin (2.27-3ubuntu1.4) ...
    /sbin/ldconfig.real: /usr/local/lib/python3.7/dist-packages/ideep4py/lib/libmkldnn.so.0 is not a symbolic link

    Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
    Processing triggers for dbus (1.12.2-1ubuntu1.2) ...
    gcc (Ubuntu 9.4.0-1ubuntu1~18.04) 9.4.0
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    g++ (Ubuntu 9.4.0-1ubuntu1~18.04) 9.4.0
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    nvcc: NVIDIA (R) Cuda compiler driver
    Copyright (c) 2005-2020 NVIDIA Corporation
    Built on Wed_Jul_22_19:09:09_PDT_2020
    Cuda compilation tools, release 11.0, V11.0.221
    Build cuda_11.0_bu.TC445_37.28845127_0

```python
muons = load(name='muons',
             build_directory='./build',
             sources=[f'{noa_location}/docs/pms/muons.cc'],
             extra_include_paths=[f'{noa_location}/include'],
             extra_cflags=['-Wall -Wextra -Wpedantic -O3 -std=c++17'],
             verbose=False)
```

```python
muons_cuda = load(name='muons_cuda',
             build_directory='./build',
             sources=[f'{noa_location}/docs/pms/muons.cu'],
             extra_include_paths=[f'{noa_location}/include'],
             extra_cflags=['-Wall -Wextra -Wpedantic -O3 -std=c++17'],
             extra_cuda_cflags=['-std=c++17 --extended-lambda'],
             verbose=False) if torch.cuda.is_available() else None
```

## Differential Cross-Section calculations

Differential cross-sections (DCS) are implemented in `<noa/pms/dcs.hh>` for `CPU` and `<noa/pms/dcs.cuh>` for `CUDA` within the namespace `noa::pms::dcs`.

Here, we demonstrate the calculations for muons passing through the standard rock. In `<noa/pms/constants.hh>` you will find:
```cpp
constexpr ParticleMass MUON_MASS = 0.10565839;     // GeV/c^2

constexpr AtomicElement<Scalar> STANDARD_ROCK =
            AtomicElement<Scalar>{
                    22.,       // Atomic mass in g/mol
                    0.1364E-6, // Mean Excitation in GeV
                    11 // Atomic number
    };
```

Let's get a range of kinetic and recoil energies:

```python
kinetic_energies = torch.linspace(1e-3, 1e6, 10000).double()
recoil_energies = 0.0505 * kinetic_energies
```

```python
kinetic_energies_gpu = kinetic_energies.cuda()
recoil_energies_gpu = recoil_energies.cuda()
```

```python
kinetic_energies_gpu[:5]
```

    tensor([1.0000e-03, 1.0001e+02, 2.0002e+02, 3.0003e+02, 4.0004e+02],
           device='cuda:0', dtype=torch.float64)

From now on, we shall get ourselves into the namespace:
```cpp
using namespace noa::pms;
```
On `CPU` the DCS computation kernel can be mapped on a tensor via the utility `dcs::vmap`. The user is expected to provide a `result` tensor with the same options as `kinetic_energies` which will get populated with the calculation values:
```cpp
const auto result = torch::zeros_like(kinetic_energies);
```

### Bremsstrahlung

Bremsstrahlung dominates other muon interaction processes in the region of catastrophic collisions (𝑣 ≥ 0.1 ), that is
at “moderate” muon energies above the kinematic limit for knock–on electron production. At high energies (𝐸 ≥ 1
TeV) this process contributes about 40% of the average muon energy loss.

##### Input:
   * K - The projectile initial kinetic energy, in GeV
   * q - The kinetic energy lost to the photon, in GeV
   * A - The mass number of the target atom, in g/mol
   * Z - The charge number of the target atom.
   * mu - The projectile rest mass, in GeV

##### Output:
    DCS (differential crros-section) in m^2/kg.

#### Definitions and Applicability

Theory from: https://pdg.lbl.gov/2014/AtomicNuclearProperties/adndt.pdf.

The formula for the differential cross section applies when:

* 𝐸 ≫ 𝜇, hence the ultrarelativistic approximation is used
* 𝐸 ≤ $10^{20}$ eV; above this energy, LPM suppression can be expected
* 𝑣 ≥ $10^{−6}$ ; below $10^{−6}$ Ter-Mikaelyan suppression takes place. However, in the latter region the cross section
of muon bremsstrahlung is several orders of magnitude less than that of other processes.
The Coulomb correction (for high 𝑍) is not included. However, existing calculations [AB97] show that for muon
bremsstrahlung this correction is small.

##### Formulae

\begin{equation}
\frac{\text{d}\sigma}{\text{d}\nu} = \left.\frac{\text{d}\sigma}{\text{d}\nu}\right|_\text{brems, elec} + \left.\frac{\text{d}\sigma}{\text{d}\nu}\right|_\text{brems, nucl}
\end{equation}

The differential cross section for muon bremsstrahlung from a (screened) nucleus:

\begin{equation}
\left.\frac{\text{d}\sigma}{\text{d}\nu}\right|_\text{brems, nucl} = \alpha(2Z\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)\Phi_n(\delta)
\end{equation}

To account for bremsstrahlung losses on atomic electrons, approximation for the contribution from electrons, taking into account electronic binding and recoil, is

\begin{equation}
\left.\frac{\text{d}\sigma}{\text{d}\nu}\right|_\text{brems, elec} = \alpha Z(2\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)\Phi_\text{in}(\delta)
\end{equation}

\begin{equation}
\frac{\text{d}\sigma}{\text{d}\nu} = \alpha Z(2\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)(\Phi_\text{in}(\delta) + Z\Phi_n(\delta) )
\end{equation}

Here $\nu = \frac{q}{E}$ is the fraction of the muon's energy transferred to the photon, and

\begin{equation}
\Phi(\delta) = \ln(\frac{BZ^{\frac{-1}{3}}(M_\mu + \delta(D_n\sqrt{e} - 2))}{(m_e + \delta \sqrt{e}BZ^{\frac{-1}{3}})D_n},
\end{equation}

where $D_n = 1.54A^{0.27}$ , $B = 182.7$ ($B = 202.4$ for hygrogen), $e = 2.7181...$ and

\begin{equation}
\delta = \frac{M_\mu^{2}\nu}{2E(1 - \nu)}
\end{equation}

\begin{equation}
\Phi_\text{in}(\delta) = \ln(\frac{M_\mu BZ^{-2/3} \sqrt{e}}{(m_e + \delta  BZ^{-2/3} \sqrt{e})(\frac{M_\mu \delta}{m_e^{2}} + \sqrt{e})}),
\end{equation}

where $B = 1429$ for all elements but hydrogen, where $B = 446$

For our calculations we use differential cross section, as
\begin{equation}
\frac{\text{d} \sigma}{\text{d} q} = \frac{\text{d}\sigma}{\text{d}\nu E}
\end{equation}
This formula takes the form:
\begin{equation}
\frac{\text{d}\sigma}{\text{d}q} = \frac{\alpha Z}{E}(2\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)(\Phi_\text{in}(\delta) + Z\Phi_n(\delta) )
\end{equation}
But, this DCS in $\frac{m^2}{Gev}$. We need to multiply by Avogadro's number, energy and divide by the mass number of the target atom to get DCS in $\frac{m^2}{kg}$.

The final formula:
\begin{equation}
\frac{\text{d}\sigma}{\text{d}q} = \frac{\alpha Z}{E}(2\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)(\Phi_\text{in}(\delta) + Z\Phi_n(\delta) ) \frac{N_a E}{A}
\end{equation}

And take into account, that A in g/mol

\begin{equation}
\frac{\text{d}\sigma}{\text{d}q} = \frac{\alpha Z}{E}(2\frac{m_e}{M_\mu})^{2}(\frac{4}{3}(\frac{1}{\nu} - 1) + \nu)(\Phi_\text{in}(\delta) + Z\Phi_n(\delta) ) \frac{1000 N_a E}{A}
\end{equation}

For `CPU` we have:
```cpp
dcs::vmap(dcs::pumas::bremsstrahlung)(
    result, kinetic_energies, recoil_energies, STANDARD_ROCK, MUON_MASS);
```
For `CUDA` we create the lambda function directly ourselves:
```cpp
dcs::pumas::cuda::vmap_bremsstrahlung(
    result, kinetic_energies, recoil_energies, STANDARD_ROCK, MUON_MASS);
```

```python
brems = muons.bremsstrahlung(kinetic_energies, recoil_energies)
brems[:5]
```

    tensor([3.5293e-04, 3.9395e-06, 4.0777e-06, 4.1341e-06, 4.1650e-06],
           dtype=torch.float64)

```python
brems_gpu = muons_cuda.bremsstrahlung(kinetic_energies_gpu, recoil_energies_gpu);
brems_gpu[:5]
```

    tensor([3.5293e-04, 3.9395e-06, 4.0777e-06, 4.1341e-06, 4.1650e-06],
           device='cuda:0', dtype=torch.float64)

```python
(brems - brems_gpu.cpu()).abs().sum()
```

    tensor(3.2179e-18, dtype=torch.float64)

```python
%timeit muons_cuda.bremsstrahlung(kinetic_energies_gpu, recoil_energies_gpu);
```

    157 µs ± 3.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

```python
%timeit muons.bremsstrahlung(kinetic_energies, recoil_energies);
```

    326 µs ± 3.58 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

### Pair Production

Direct electron pair production is one of the most important muon interaction processes. At TeV muon energies,
the pair production cross section exceeds those of other muon interaction processes over a range of energy transfers
between 100 MeV and 0.1𝐸𝜇. The average energy loss for pair production increases linearly with muon energy, and
in the TeV region this process contributes more than half the total energy loss rate.

To adequately describe the number of pairs produced, the average energy loss and the stochastic energy loss distribution,
the differential cross section behavior over an energy transfer range of 5 MeV ≤ 𝜖 ≤ 0.1 ·𝐸𝜇 must be accurately
reproduced. This is is because the main contribution to the total cross section is given by transferred energies 5 MeV
≤ 𝜖 ≤ 0.01 ·𝐸𝜇, and because the contribution to the average muon energy loss is determined mostly in the region
0.001 · 𝐸𝜇 ≤ 𝜖 ≤ 0.1 ·𝐸𝜇 .

##### Input:
   * Z - The charge number of the target atom.
   * A - The mass number of the target atom.
   * mass - The projectile rest mass, in GeV
   * K - The projectile initial kinetic energy.
   * q - The kinetic energy lost to the photon ($E - E^{\text{'}}$).

##### Output:
    DCS in m^2/kg.

#### Definitions and Applicability

Theory from https://geant4-userdoc.web.cern.ch/UsersGuides/PhysicsReferenceManual/fo/PhysicsReferenceManual.pdf (Page 151)

Coefficients for the Gaussian quadrature from:
https://pomax.github.io/bezierinfo/legendre-gauss.html.

The formula for the differential cross section applies when:
* $E_\mu \gg \mu $ $ ( E \geq 2 - 5 GeV)$ and $E_\mu \leq 10^{15} - 10^{17} eV $. If muon energies exceed this limit, the LPM (Landau Pomeranchuk Migdal) effect may become important, depending on the material.
*The muon energy transfer $q$ lies between $q_\text{min} = 4m_e$ and $q_\text{max} = E_\mu - \frac{3\sqrt{e}}{4} M_\mu Z^{1/3}$, although the formal lower limit is $q \gg 2m_e$, and the formal upper limit requires $E_\mu ^\text{'} \gg \mu$.
*$Z \leq 40 - 50$. For higher Z, the Coulomb correction is important but has not been sufficiently studied theoretically.

##### Formulae

\begin{equation}
\frac{\text{d}\sigma}{\text{d}q}(Z, A, E, q) = \frac{4}{3\pi} \frac{Z(Z + \xi(Z))}{A} N_A {(\alpha r_e)}^{2} (\frac{1 - \nu}{q}) \int_{p}[\Phi_e + (m_e/M_\mu)^{2}\Phi_\mu]dp,
\end{equation}

where $\Phi_{e,\mu} = B_{e,\mu} {L_{e,\mu}}^{'}$  and  $\Phi_{e,\mu} = 0 \text{  whenever  } \Phi_{e,\mu}<0$.

$B_e$ and $B_\mu$ do not depend on Z, A, and are given by

\begin{equation}
B_e = [(2 + \rho^{2})(1 + \beta) + \xi(3 + \rho^{2})]\ln(1 + \frac{1}{\xi}) + \frac{1 - \rho^{2} - \beta}{1 + \xi} - (3 + \rho^{2})
\approx \frac{1}{2\xi}[(3 - \rho^{2}) + 2\beta(z + \rho^{2})] \text{ for } \xi \geq 10 ^{3}
\end{equation}

\begin{equation}
B_\mu = [(1 + \rho^{2})(1 + \frac{3\beta}{2}) - \frac{1}{\xi}(1 + 2\beta)(1 - \rho^{2})]\ln(1 + \xi) + \frac{\xi(1 - \rho{2} - \beta)}{1 + \xi} + (1 + 2\beta)(1 - \rho^{2})
\approx \frac{\xi}{2}[(5 - \rho^{2}) + \beta(3 + \rho^{2})] \text{ for } \xi \leq 10^{-3}
\end{equation}

Also,

\begin{equation}
\xi = \frac{\mu^{2}\nu^{2}}{4m^{2}} \frac{(1 - \rho^{2})}{1 - \nu}; \text{ }
\beta = \frac{\nu^{2}}{2(1 - \nu)}
\end{equation}

\begin{equation}
L_e^\text{'} = \ln(\frac{A^{*}Z^{-1/3} \sqrt{(1 + \xi)(1 + Y_e)}}{1 + \frac{2m\sqrt{e}A*Z^{-1/3}(1 + \xi)(1 + Y_\mu)}{E\nu(1 - \rho^{2})}})  - \frac{1}{2}ln([1 + (\frac{3mZ^{1/3}}{2\mu})^{2}(1 + \xi)(1 + Y_e)])
\end{equation}

\begin{equation}
L_\mu ^\text{'} = \ln(\frac{(\frac{M_\mu}{m_e})A^{*}Z^{-1/3} \sqrt{(1 + \frac{1}{\xi})(1 + Y_e)}}{1 + \frac{2m\sqrt{e}A*Z^{-1/3}(1 + \xi)(1 + Y_\mu)}{E\nu(1 - \rho^{2})}})  - ln(\frac{3}{2} Z^{1/3}\sqrt{(1 + \frac{1}{\xi})(1 + Y_\mu)})
\end{equation}

For faster computing, the expressions for $L_{e, \mu} ^\text{'}$ are further algebraically transformed. The function $L_{e, \mu} ^\text{'}$ include the nuclear size correction in comparison with parameterization.

\begin{equation}
Y_e = \frac{5 - \rho^2 + 4\beta(1 + \rho^2)}{2(1 + 3\beta)\ln(3 + 1/\xi)) - \rho^2 - 2\beta(2 - \rho^2)}
\end{equation}

\begin{equation}
Y_\mu = \frac{4 + \rho^2 + 3\beta(1 + \rho^2)}{(1 + \rho^2)(\frac{3}{2} + 2\beta)\ln(3 + \xi) + 1 - \frac{3}{2}\rho^2}
\end{equation}

\begin{equation}
\rho_{max} = 1 - \frac{6M_\mu^2}{E^2(1 - \nu)})\sqrt{1 - \frac{4m_e}{E\nu}}
\end{equation}

```cpp
dcs::vmap(dcs::pumas::pair_production)(
            result, kinetic_energies, recoil_energies, STANDARD_ROCK, MUON_MASS);
```

```python
ppair = muons.pair_production(kinetic_energies, recoil_energies)
ppair[:5]
```

    tensor([0.0000e+00, 6.5366e-06, 7.3699e-06, 7.7919e-06, 8.0572e-06],
           dtype=torch.float64)

### Photonuclear

Input:
   * Z - The charge number of the target atom.
   * A - The mass number of the target atom.
   * ml - The projectile rest mass, in GeV
   * K - The projectile initial kinetic energy.
   * q - The kinetic energy lost to the photon.

Output:
    DCS in m^2/kg.

#### Definitions and Applicability

Theory from: https://arxiv.org/pdf/hep-ph/9712415.pdf, https://arxiv.org/pdf/hep-ph/0012350.pdf

The inelastic interaction of muons with nuclei is important at high muon energies (𝐸 ≥ 10 GeV), and at relatively high
energy transfers 𝜈 (𝜈/𝐸 ≥ 10−2). It is especially important for light materials and for the study of detector response
to high energy muons, muon propagation and muon-induced hadronic background. The average energy loss for this
process increases almost linearly with energy, and at TeV muon energies constitutes about 10% of the energy loss rate.

The main contribution to the cross section 𝜎(𝐸, 𝜈) and energy loss comes from the low 𝑄2–region ( 𝑄2 ≪ 1 GeV2).
In this domain, many simplifications can be made in the theoretical consideration of the process in order to obtain
convenient and simple formulae for the cross section. Most widely used are the expressions given by Borog and
Petrukhin [BP75], and Bezrukov and Bugaev [BB81]. Results from these authors agree within 10% for the differential
cross section and within about 5% for the average energy loss, provided the same photonuclear cross section, 𝜎𝛾𝑁, is
used in the calculations.

##### Formulae

The differential cross section can be written in the form

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{4\pi\alpha^2}{Q^4}\frac{F_2(x, Q^2)}{x}(1-y-\frac{Mxy}{2E} + (1 - \frac{2m_l^2}{Q^2})\frac{y^2(1 + \frac{4M^2x^2}{Q^2})}{2(1 + R(x, Q^2))})
\end{equation}

Where $F_2$ - a nucleon structure function

\begin{equation}
F_2(x, Q^2) = \frac{Q^2}{Q^2 + m_0^2} (F_2^P(x, Q^2) + F_2^R(x, Q^2))
\end{equation}

\begin{equation}
F_2 ^{R}(x, Q^{2}) = c_R(t)x_R^{a_R(t)}(1 - x)^{b_R(t)}
= c_R(t)e^{a_R(t)\ln x_R+ b_R(t)\ln(1 - x)}
\end{equation}

\begin{equation}
F_2 ^{P}(x, Q^{2}) = c_P(t)x_P^{a_P(t)}(1 - x)^{b_P(t)}
= c_P(t)e^{\ln(x_P^{a_P(t)}(1 - x)^{b_P(t)})}
= c_P(t)e^{\ln x_P^{a_P(t)}+ \ln(1 - x)^{b_P(t)}}
= c_P(t)e^{a_P(t)\ln x_P+ b_P(t)\ln(1 - x)}
\end{equation}

Take $x$ as $x = \frac{Q^2}{2Mq}$

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{4\pi\alpha^2 F_2}{Q^4} \frac{2Mq}{Q^2}(1 - y - \frac{My}{2E}\frac{Q^2}{2Mq} + (1 - \frac{2m_l^2}{Q^2})\frac{y^2(1 + (\frac{2MQ^2}{2MqQ})^2)}{2(1 + R)})
\end{equation}

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{8\pi\alpha^2F_2Mq}{Q^6}(1 - y - \frac{Q^2y}{4Eq} + (1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + (y\frac{Q}{q})^2}{2(1 + R)})
\end{equation}

Take into account, that $y = \frac{q}{E}$

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{8\pi\alpha^2F_2Mq}{Q^6}(1 - y - \frac{Q^2}{4E^2} + (1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{2(1 + R)})
\end{equation}

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{8\pi\alpha^2F_2Mq}{Q^6}(1 - y  + (1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{2(1 + R)}) - \frac{8\pi\alpha^2F_2Mq}{Q^6}\frac{Q^2}{4E^2}
\end{equation}

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{8\pi\alpha^2F_2Mq}{Q^6}(1 - y  + (1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{2(1 + R)}) - \frac{8\pi\alpha^2F_2Mq}{4Q^4E^2}
\end{equation}

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}x} = \frac{8\pi\alpha^2F_2Mq}{Q^2}((\frac{1 - y  + \frac{1}{2}(1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{1 + R}}{Q^4}) - \frac{1}{4Q^2E^2})
\end{equation}

Let's see on $x = \frac{Q^2}{2Mq}$

\begin{equation}
\frac{\text{d}x}{\text{d}q} = \frac{-Q^2}{2Mq^2}
\end{equation}

And
\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}q}\frac{2Mq^2}{-Q^2} = \frac{8\pi\alpha^2F_2Mq}{Q^2}((\frac{1 - y  + \frac{1}{2}(1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{1 + R}}{Q^4}) - \frac{1}{4Q^2E^2})
\end{equation}

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}q} = \frac{-4\pi\alpha^2F_2}{q}((\frac{1 - y  + \frac{1}{2}(1 - \frac{2m_l^2}{Q^2}) \frac{y^2 + \frac{Q^2}{E^2}}{1 + R}}{Q^4}) - \frac{1}{4Q^2E^2})
\end{equation}

So it's not enough correct equation. We should add some approximation factors. And finally we have:

\begin{equation}
\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}q} = \frac{\text{cf} F_2}{q} (\frac{1-y+\frac{1}{2}(1 - \frac{2m_l^2}{Q^2}) \frac {y^2 + \frac{Q^2}{E^2}}{1 + R}}{Q^4} - \frac{1}{4E^2Q^2})
\end{equation}

Then integrate this equation using Gaussian Quadrature from $Q_\text{min}^2 = \frac{m_l^2y^2}{1 - y} $ to $Q_\text{max}^2 = 2MEy - ((M + m_\pi)^2 - M^2) $

\begin{equation}
\frac{\text{d}\sigma}{\text{d}q} = \int{\frac{\text{d}\sigma(x, Q^2)}{\text{d}Q^2\text{d}q}}\text{d}Q^2
\end{equation}

```cpp
dcs::vmap(dcs::pumas::photonuclear)(
            result, kinetic_energies, recoil_energies, STANDARD_ROCK, MUON_MASS);
```

```python
photonuc = muons.photonuclear(kinetic_energies, recoil_energies)
photonuc[:5]
```

    tensor([0.0000e+00, 2.2912e-06, 2.1304e-06, 2.0719e-06, 2.0427e-06],
           dtype=torch.float64)

### Ionisation

Input:
   * A - The charge number of the target atom.
   * I - The mean excitation of the target atom.
   * Z - The mass number of the target atom.
   * mu - The projectile rest mass, in GeV
   * K - The projectile initial kinetic energy.
   * q - The kinetic energy lost to the photon.

Output:
    DCS in m^2/kg.

### Definitions and Applicability

Theory from: Salvat et al., NIMB316 (2013) 144-159, Sokalski et al., Phys.Rev.D64 (2001) 074015 (MUM)

The differential cross section for ionisation is computed following Salvat et al., NIMB316 (2013) 144-159, considering only close interactions for DELs. In addition a radiative correction is applied according toSokalski et al., Phys.Rev.D64 (2001) 074015 (MUM).

\begin{equation}
\frac{\text{d}\sigma}{\text{d}q} = \frac{CEZ}{A(\frac{1}{2P_2} + \frac{P_2W_\text{max}}{E^2W_\text{max} - qP_2})}(1 + \Delta_{e\gamma}),
\end{equation}

where $P_2 = E^2 - M_\mu^2$, $W_{max} = \frac{2m_e P_2}{M_\mu^2 + m_e(m_e + 2E)}$ and $C = 1.535336*10^{-5}$

$\Delta_{e\gamma}$ - Radiative correction

\begin{equation}
\Delta_{e\gamma} = \frac{\alpha}{2\pi}\ln(1 + \frac{2\nu E}{m_e})(\ln(\frac{4E^2(1 - \nu)}{M_\mu^2}) - \ln(1 + \frac{2\nu E}{m_e}))
\end{equation}

If we take into account, that $\nu = \frac{q}{E}$

\begin{equation}
\Delta_{e\gamma} = \frac{\alpha}{2\pi}\ln(1 + \frac{2q}{m_e})(\ln(\frac{4E(E - q)}{M_\mu^2}) - \ln(1 + \frac{2q}{m_e}))
\end{equation}

```cpp
dcs::vmap(dcs::pumas::ionisation)(
            result, kinetic_energies, recoil_energies, STANDARD_ROCK, MUON_MASS);
```

```python
ionis = muons.ionisation(kinetic_energies, recoil_energies)
ionis[:5]
```

    tensor([0.0000e+00, 3.0168e-05, 1.5300e-05, 1.0284e-05, 7.7575e-06],
           dtype=torch.float64)

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

sample = 1000 #amount of points

recoil_energies = np.zeros(sample)

E = np.linspace(0.00001, 10000, 10 ) #array with different energy levels

for j in range(10):

  kinetic_energies_np = np.full(sample, E[j]) #numpy array with one energy level
  kinetic_energies_torch = torch.Tensor(kinetic_energies_np).double()

  for i in range(sample):
    recoil_energies[i] = E[j]*(0.05 + i*(1 - 0.05)/sample)

  recoil_energies_torch = torch.Tensor(recoil_energies).double()

  brems = muons.bremsstrahlung(kinetic_energies_torch, recoil_energies_torch)
  ppair = muons.pair_production(kinetic_energies_torch, recoil_energies_torch)
  photonuc = muons.photonuclear(kinetic_energies_torch, recoil_energies_torch)
  ionis = muons.ionisation(kinetic_energies_torch, recoil_energies_torch)

  print("Energy level: ", E[j])

  fig, ax = plt.subplots()
  ax.plot(recoil_energies, brems.numpy(), label = 'bremsstrahlung')
  ax.plot(recoil_energies, ppair.numpy(), label = 'pair production')
  ax.plot(recoil_energies, photonuc, label = 'photonuclear')
  ax.plot(recoil_energies, ionis.numpy(), label = 'ionisation')

  ax.set_xlabel('The kinetic energy lost to the photon, GeV', fontsize = 20)
  ax.set_ylabel('DCS, m^2/kg', fontsize = 20)
  ax.set_title("Energy loss")

  fig.set_figwidth(20)
  fig.set_figheight(10)

  ax.legend()

```