# Adding HTML elements to figures

This notebook contains examples how to add HTML elements to figures and create interaction between Javascript and Python code.

**Note**: this notebook makes interactive calculation when slider position is changed, so you need to download this notebook to see any changes in plot.

```
%matplotlib inline
import matplotlib.pylab as plt
import mpld3
mpld3.enable_notebook()
```

## Simple example: slider plugin

We add a simple slider HTML element ``<input type="range">`` to our figure. When slider position is changed, we call ``kernel.execute()`` and pass updated value to Python function ``runCalculation()``. In this simple example we just update the frequency $\omega$ of $\sin(\omega x)$.

```
class SliderView(mpld3.plugins.PluginBase):
    """ Add slider and JavaScript / Python interaction. """

    JAVASCRIPT = """
    mpld3.register_plugin("sliderview", SliderViewPlugin);
    SliderViewPlugin.prototype = Object.create(mpld3.Plugin.prototype);
    SliderViewPlugin.prototype.constructor = SliderViewPlugin;
    SliderViewPlugin.prototype.requiredProps = ["idline", "callback_func"];
    SliderViewPlugin.prototype.defaultProps = {}

    function SliderViewPlugin(fig, props){
        mpld3.Plugin.call(this, fig, props);
    };

    SliderViewPlugin.prototype.draw = function(){
      var line = mpld3.get_element(this.props.idline);
      var callback_func = this.props.callback_func;

      var div = d3.select("#" + this.fig.figid);

      // Create slider
      div.append("input").attr("type", "range").attr("min", 0).attr("max", 10).attr("step", 0.1).attr("value", 1)
          .on("change", function() {
              var command = callback_func + "(" + this.value + ")";
              console.log("running "+command);
              var callbacks = { 'iopub' : {'output' : handle_output}};
              var kernel = IPython.notebook.kernel;
              kernel.execute(command, callbacks, {silent:false});
          });

      function handle_output(out){
        //console.log(out);
        var res = null;
        // if output is a print statement
        if (out.msg_type == "stream"){
          res = out.content.data;
        }
        // if output is a python object
        else if(out.msg_type === "pyout"){
          res = out.content.data["text/plain"];
        }
        // if output is a python error
        else if(out.msg_type == "pyerr"){
          res = out.content.ename + ": " + out.content.evalue;
          alert(res);
        }
        // if output is something we haven't thought of
        else{
          res = "[out type not implemented]";
        }

        // Update line data
        line.data = JSON.parse(res);
        line.elements()
          .attr("d", line.datafunc(line.data))
          .style("stroke", "black");

       }

    };
    """

    def __init__(self, line, callback_func):
        self.dict_ = {"type": "sliderview",
                      "idline": mpld3.utils.get_id(line),
                      "callback_func": callback_func}
```

```
import numpy as np

def updateSlider(val1):
    t = np.linspace(0, 10, 500)
    y = np.sin(val1*t)
    return map(list, list(zip(list(t), list(y))))

fig, ax = plt.subplots(figsize=(8, 4))

t = np.linspace(0, 10, 500)
y = np.sin(t)
ax.set_xlabel('Time')
ax.set_ylabel('Amplitude')

# create the line object
line, = ax.plot(t, y, '-k', lw=3, alpha=0.5)
ax.set_ylim(-1.2, 1.2)
ax.set_title("Slider demo")

mpld3.plugins.connect(fig, SliderView(line, callback_func="updateSlider"))
```

**Note**: this notebook makes interactive calculation when slider position is changed, so you need to download this notebook to see any changes in plot.

## Complex example: beam deflection

When creating more interaction between Javascript and Python, things get easily quite complicated. Therefore one should consider using e.g. Backbone or similar to get more structured code in Javascript side. IPython notebook seems to be using Backbone internally already.

In the next example we add more inputs and use Backbone to handle syncronizing Python and Javascript. In this example we calculate the deflection line $v(x)$ for simple supported Euler Bernoulli beam and update visualization when user changes force location $x \in [0, 1]$. The formula for deflection $v(x)$ is

\begin{equation}
v(x) = \frac{FL^2}{6EI}\left[ \frac{ab}{L^2}(L+b)\frac{x}{L} - b\left(\frac{x}{L}\right)^3 + \frac{1}{L^2}<x-a>^3 \right],
\end{equation}
where
\begin{equation}
<x-a>=\begin{cases}
0 & ,x<a\\
x-a & ,x\geq a
\end{cases}
\end{equation}
and $a$ is distance from left support, $a+b=L$.

More information about deflection: http://en.wikipedia.org/wiki/Deflection_%28engineering%29

First we define the template we use in our plugin:

```
from IPython import display
display.HTML("""

""")
```

Our plugin code comes next. Note that we have now Backbone model LineModel to handle Python-Javascript-interaction and Backbone views ToolsView and CanvasView to take care of visualization when data is changed.

Note that not all input elements are "connected" to the visualization, they are more like placeholders ready for your own coding experiments. They are not implemented on purpose to keep lines of code as low as possible. To get them work, modify ``this.notImplemented`` $\rightarrow$ ``this.modelChanged`` in ``initialize`` function and change ``var command = ... `` in ``modelChanged`` to pass more parameters to notebook server. Don't forget to change Python side function accordingly.

```
class MyUserInterface(mpld3.plugins.PluginBase):
    """ Here we use Backbone to create more structured Javascript. """

    JAVASCRIPT = """

var LineModel = Backbone.Model.extend({

    initialize: function(options) {
        this.options = options || {};
        this.on("change:sliderPosition", this.modelChanged);
        this.on("change:boundaryCondition", this.notImplemented);
        this.on("change:youngsModulus", this.notImplemented);
        this.on("change:useFEM", this.notImplemented);
    },

    /**
        This example should be quite easy to extend to use more inputs. You
        just have to pass more model.get('...') things to kernel execute command below.
    */
    notImplemented: function(model) {
        alert("This function is not implemented in the example on purpose.");
    },

    /**
        Model changed, execute notebook kernel and update model data.
    */
    modelChanged: function(model) {
        var command = this.options.callback_func + "(" + model.get('sliderPosition') + ")";
        console.log("IPython kernel execute "+command);
        var callbacks = {
            'iopub' : {
                'output' : function(out) {
                    //console.log(out);
                    var res = null;
                    // if output is a print statement
                    if (out.msg_type == "stream"){
                      res = out.content.data;
                    }
                    // if output is a python object
                    else if(out.msg_type === "pyout"){
                      res = out.content.data["text/plain"];
                    }
                    // if output is a python error
                    else if(out.msg_type == "pyerr"){
                      res = out.content.ename + ": " + out.content.evalue;
                      alert(res);
                    }
                    // if output is something we haven't thought of
                    else{
                      res = "[out type not implemented]";
                      alert(res);
                    }
                    model.set("line", JSON.parse(res));
                }
            }
        };
        IPython.notebook.kernel.execute(command, callbacks, {silent:false});
    }

});

var ToolsView = Backbone.View.extend({

    /**
        This view renders toolbar with slider and other html elements.
    */
    initialize: function(options) {
        this.options = options || {};
        _.bindAll(this, 'render');
    },

    render: function() {
        var template = _.template($("#tools-template").html(), {});
        $(this.el).append(template);
        return this;
    },

    /**
        Listen event changes.
    */
    events: {
        "change #slider1": "changeSlider1",
        "change #boundary_conditions": "changeBoundaryConditions",
        "change #young": "changeModulus",
        "change #useFEM": "changeUseFEM"
    },

    changeSlider1: function(ev) {
        var sliderPosition = $(ev.currentTarget).val();
        this.model.set('sliderPosition', sliderPosition);
        $(this.el).find("#slider1label").text(parseFloat(sliderPosition).toFixed(2));
    },

    changeBoundaryConditions: function(ev) {
        this.model.set('boundaryCondition', $(ev.currentTarget).val());
    },

    changeModulus: function(ev) {
        this.model.set('youngsModulus', $(ev.currentTarget).val());
    },

    changeUseFEM: function(ev) {
        var isChecked = $(ev.currentTarget).is(":checked");
        this.model.set('useFEM', isChecked);
    }

});

var CanvasView = Backbone.View.extend({

    initialize: function(options) {
        this.options = options || {};
        this.line = mpld3.get_element(this.options.props.idline);
        _.bindAll(this, 'render');
        this.model.bind('change:line', this.render);
    },

    /**
        Update line when model changes, f.e. new data is calculated
        inside notebook and updated to Backbone model.
    */
    render: function() {
        this.line.elements().transition()
            .attr("d", this.line.datafunc(this.model.get('line')))
            .style("stroke", "black");
    }

});

// PLUGIN START

mpld3.register_plugin("myuserinterface", MyUserInterfacePlugin);
MyUserInterfacePlugin.prototype = Object.create(mpld3.Plugin.prototype);
MyUserInterfacePlugin.prototype.constructor = MyUserInterfacePlugin;
MyUserInterfacePlugin.prototype.requiredProps = ["idline", "callback_func"];
MyUserInterfacePlugin.prototype.defaultProps = {}

function MyUserInterfacePlugin(fig, props){
    mpld3.Plugin.call(this, fig, props);
};

MyUserInterfacePlugin.prototype.draw = function() {

    // Some hacking to get proper layout.
    var div = $("#" + this.fig.figid).attr("style", "border: 1px solid;");
    var figdiv = div.find("div");
    figdiv.attr("style", "display: inline;");

    // Create LineModel
    var lineModel = new LineModel({
      callback_func: this.props.callback_func
    });

    // Create tools view
    var myel = $('<div style="float: left; margin: 10px 30px;" id="tools"></div>');
    div.append(myel);
    var toolsView = new ToolsView({
        el: myel,
        model: lineModel
    });
    toolsView.render();

    // Create canvas view which updates line visualization when the model is changed
    var canvasView = new CanvasView({
        el: figdiv,
        model: lineModel,
        props: this.props
    });

};
"""

    def __init__(self, line, callback_func):
        self.dict_ = {"type": "myuserinterface",
                      "idline": mpld3.utils.get_id(line),
                      "callback_func": callback_func}
```

Next we do the actual calculation of the deflection using Python and display results:

```
import numpy as np

L = 1.0
F = 3.0
E = 100.0
I = 0.1

def v_(x, a):
    b = L - a
    v = a*b/L**2*(L+b)*x/L - b*(x/L)**3
    if x-a > 0.0:
        v += 1.0/L**2*(x-a)**3
    v *= F*L**2/(6.0*E*I)
    return v

v = np.vectorize(v_)

def runCalculation(a):
    """
    """
    x = np.linspace(0, L, 500)
    y = -v(x, a)*1000.0
    return map(list, list(zip(list(x), list(y))))

fig, ax = plt.subplots(figsize=(8, 4))

t = np.linspace(0, 1, 200)
y = np.sin(t)
ax.set_xlabel('x [m]')
ax.set_ylabel('Deflection [mm]')
ax.set_title('Euler-Bernoulli beam deflection line')

# create the line object
initial_data = np.array(runCalculation(0.5))
line, = ax.plot(initial_data[:, 0], initial_data[:, 1], '-k', lw=3, alpha=0.5)
ax.plot([0.975, 1.025, 1.00, 0.975], [-1, -1, 0, -1], '-k', lw=1)
ax.plot([-0.025, 0.025, 0.000, -0.025], [-1, -1, 0, -1], '-k', lw=1)
ax.set_ylim(-10, 5)
ax.grid(lw=0.1, alpha=0.2)

mpld3.plugins.connect(fig, MyUserInterface(line, callback_func="runCalculation"))
```

**Note**: this notebook makes interactive calculation when slider position is changed, so you need to download this notebook to see any changes in plot.

**Note 2**: The line is updated only when force location is changed, but it should be trivial to extend this example use more tools.

```

```


```python
from sympy import Symbol, Matrix, symbols, sin, lambdify
import time
import  numpy as np
from collections import OrderedDict

```

```python
id_wind=False
bo=False

```
R = Matrix([[1, 0,0],[0,np.cos(10*np.pi/180),-np.sin(10*np.pi/180)],[0,np.sin(10*np.pi/180), np.cos(10*np.pi/180)]])
c=Matrix([0,0,0])
v_B=Matrix([10,0,0])
v_W=Matrix([0,0,0])
Om=Matrix([0,0,0])
cro=Matrix([0,1,0])
GenDirectForceWing(Om, c, v_B, v_W, R, cro)

```python
for used_logged_v_in_model in [True, False]:
    t0=time.time()

    vlog_i,vlog_j,vlog_k=symbols("vlog_i,vlog_j,vlog_k",real=True)
    v_log=Matrix([[vlog_i],
            [vlog_j],
            [vlog_k]])
    vpred_i,vpred_j,vpred_k=symbols("vpred_i,vpred_j,vpred_k",real=True)
    v_pred=Matrix([[vpred_i],
            [vpred_j],
            [vpred_k]])
    v_i,v_j,v_k=(vlog_i,vlog_j,vlog_k) if used_logged_v_in_model else (vpred_i,vpred_j,vpred_k)
    v_B=Matrix([[v_i],
            [v_j],
            [v_k]])

    print("\nElapsed : %f s , Prev step time: -1 s \\ Generating first symbols ..."%(time.time()-t0))
    dt=Symbol('dt',positive=True,real=True)
    CL_1_sa = Symbol('C^{sa}_{L,1}',real=True)             # Coeff rechercher
    CD_0_sa = Symbol('C^{sa}_{D,0}',real=True)             # Coeff rechercher
    CD_1_sa = Symbol('C^{sa}_{D,1}',real=True)             # Coeff rechercher
    CD_0_fp = Symbol('C^{fp}_{D,0}',real=True)             # Coeff rechercher
    CD_1_fp = Symbol('C^{fp}_{D,1}',real=True)             # Coeff rechercher
    k_0 = Symbol('k_0', real=True)                         # coeff rechercher
    k_1 = Symbol('k_1', real=True)                         # coeff rechercher
    k_2 = Symbol('k_2', real=True)                         # coeff rechercher
    delta_s = Symbol('delta_s', real=True)                # Coeff rechercher : largeur du stall
    alpha_s = Symbol('alpha_s',real=True)                 # Coeff rechercher
    B_B       = Matrix([[1,0,0], [0,1,0], [0,0,1]])                              # Base dans le repère body
    omega1, omega2, omega3 = symbols('\omega_1, \omega_2, \omega_3', real=True)
    Omega     = Matrix([omega1, omega2, omega3])                                 # Vecteur de rotation
    r00, r01, r02, r10, r11, r12, r20, r21, r22 = symbols('r_{00}, r_{01}, r_{02}, r_{10}, r_{11}, r_{12}, r_{20}, r_{21}, r_{22}', real=True)
    R         = Matrix([[r00,r01,r02], [r10,r11, r12], [r20, r21, r22]])          # Matrice de rotation
#    Vb1,Vb2,Vb3=symbols('V_{b1} V_{b2} V_{b3}',real=True)
    #v_B        = Matrix([Vb1, Vb2, Vb3])                                          # Vitesse du corps (repère drone)
    Vw1,Vw2,Vw3=symbols('V_{w1} V_{w2} V_{w3}',real=True)                         # Vitesse du vent dans le repère NED
    v_W        = Matrix([Vw1, Vw2, Vw3])
    xcp, ycp, zcp = symbols('x_{cp}, y_{cp}, z_{cp}')
    X_cp     = Matrix([xcp, ycp, zcp])                                            # Position du centre de poussé d'un corps dans le repère body
    C_t, C_q, C_h=symbols('C_t,C_q, C_h',real=True)                           # Coefficient de poussée des moteurs, coefficient de couple des moteurs
    motor_axis_in_body_frame = Matrix([1,0,0])                                    # Axe des moteurs, ici placé en mode avion
    omega_rotor = symbols('\omega_{rotor}', real=True)                            # Vitesse de rotation des moteurs
    crossward_B = B_B[:,1]
    c45, s45 = np.cos(45*np.pi/180), np.sin(45*np.pi/180)
    r = np.array(((1,0, 0),(0,c45,-s45),(0,s45, c45)))
    r_neg     = np.array(((1,0, 0), (0,c45, s45),(0,-s45, c45)))
    R_list_sympy = [R, R,  R * r,  R *r_neg, R]      # Liste des matrices de rotation de chaque surface portante du drone, seul les éléments de la queue (element 3 et 4) ne sont pas dans le repère inertiel.
    cp1x,cp1y, cp1z, cp2x,cp2y,cp2z, cp3x,cp3y,cp3z, cp4x,cp4y,cp4z,cp5x,cp5y,cp5z = symbols('cp1x,cp1y, cp1z, cp2x,cp2y,cp2z, cp3x,cp3y,cp3z, cp4x,cp4y,cp4z,cp5x,cp5y,cp5z', real=True)
    cp_list = [Matrix([cp1x,cp1y, cp1z]), Matrix([cp2x,cp2y,cp2z]), Matrix([cp3x,cp3y,cp3z]), Matrix([cp4x,cp4y,cp4z]), Matrix([cp5x,cp5y,cp5z])]
    A1, A2, A3 = symbols('A_1 A_2 A_3', real=True)
    Aire_list = [A1, A1, A2, A2, A3]  # Liste des 1/2 * rho * S pour chaque surface
    cp_list_rotor = [Matrix([0.713,0.475,0]), Matrix([0.713,-0.475,0])]
    spinning_sense_list = [1,-1]

    ##### Listes des angles (d'attaque et de contrôles) pour faire la somme des forces en une seule équations
    alpha1, alpha2, alpha3, alpha4, alpha5 = symbols('alpha_1, alpha_2, alpha_3, alpha_4, alpha_5', real=True)
    alpha_list =Matrix([alpha1, alpha2, alpha3, alpha4, alpha5])
    alpha0 = Symbol('alpha_0', real=True)
    alpha0_list = Matrix([alpha0 , alpha0 ,0, 0, alpha0])
    delta0_1, delta0_2, delta0_3, delta0_4, delta0_5 = symbols('delta_0_1, delta_0_2, delta_0_3, delta_0_4, delta_0_5', real=True)
    delta0_list = Matrix([delta0_1, delta0_2, delta0_3, delta0_4, delta0_5])
    t1=time.time()
    print("Elapsed : %f s , Prev step time: -1 s \\ Generating dynamics function ..."%(t1-t0))
    def compute_alpha(dragDirection, liftDirection, frontward_Body, VelinLDPlane, R=None):
        if R is None :
            calpha= np.vdot(dragDirection, frontward_Body)
        else :
            calpha= np.vdot(dragDirection,  R@frontward_Body)
        absalpha= -np.arccos(calpha)
        if R is None :
            signalpha = np.sign(np.vdot(liftDirection, frontward_Body))
        else:
            signalpha = np.sign(np.vdot(liftDirection, R@frontward_Body))

        if np.linalg.norm(VelinLDPlane)>1e-7 :
            alpha = signalpha*absalpha
        else :
            alpha=0
        if abs(alpha)>0.5*np.pi:
            if alpha>0 :alpha=alpha-np.pi
            else: alpha=alpha+np.pi
        return alpha

    ##################################################### génération des équations pour Cd et Cl (utiliser pour générer les équations symbolique pour chaque surface portantes) ####################################################
    def compute_cl_cd(a, a_0, a_s, d_0, d_s, cl1sa, cd1fp, k0, k1, k2, cd0fp, cd0sa, cd1sa):
        if bo == True:
            CL_sa = 1/2 * cl1sa * (2*(a + (d_0)))
            CD_sa =   cd1sa * (a + d_0)*(a + d_0)

            C_L = CL_sa + (d_0)
            C_D = CD_sa
        else:
            CL_sa = 1/2 * cl1sa * sin(2*(a + (k1*d_0) + a_0))
            CD_sa = cd0sa + cd1sa * sin(a + (k0*d_0) + a_0)*sin(a +  (k0*d_0) + a_0)

            CL_fp = 1/2 * cd1fp * sin(2*(a+ (k1*d_0) + a_0))
            CD_fp = cd0fp + cd1fp * sin(a + (k0*d_0) + a_0)*sin(a +  (k0*d_0) + a_0)

            puiss=5
            x=((a+a_0)**2/a_s**2)**puiss
            s = 1.0 - x/(x + 100+200*d_s)

            C_L = CL_fp + s*(CL_sa - CL_fp) + k2 * sin(d_0)
            C_D = CD_fp + s*(CD_sa - CD_fp)
        return C_L, C_D

    def GenDirectForceWing(Omega, cp, vB, vW, R, crossward_Body):
        if bo==True:
        # Cette fonction permet d'obtenir les directions des efforts de portances et de trainé en fonction des vitesses, et de l'orientation dans le repère NED.
            Air_speed_earth = vB -vW
            VelinLDPlane    = Air_speed_earth - Air_speed_earth.dot(R*crossward_Body) * R*crossward_Body
            dragDirection = -VelinLDPlane / VelinLDPlane.norm()
            liftDirection = -(R*crossward_Body).cross(dragDirection)
        else:
            Air_speed_earth = vB - vW
            Air_speed_body  = (R.T* Air_speed_earth) - cp.cross(Omega)
            VelinLDPlane    = Air_speed_body - Air_speed_body.dot(crossward_Body.T) * crossward_Body
            dragDirection = -VelinLDPlane / VelinLDPlane.norm()
            liftDirection = -crossward_Body.cross(dragDirection)

        return VelinLDPlane, dragDirection, liftDirection

    def GenForceWing(A, VelinLDPlane, dragDirection, liftDirection, Cd, Cl, cp):
        # Cette fonction permet de générer les forces aerodynamique d'une aile dans son repère.
        D = A * VelinLDPlane.norm()**2 * dragDirection * Cd
        L = A * VelinLDPlane.norm()**2 * liftDirection * Cl

        F_wing = L+D
        Torque_wing =  cp.cross(F_wing)

        return F_wing, Torque_wing

    def Generate_Sum_Force_wing(A_list, Omega, cp_list, R_list, vB, vW,  Cd_list, Cl_list, crossward_body, r_queue, r_queue_neg):
        # Cette function permet de généer l'équation complète de la somme des forces pour les différentes surfaces portantes
        p = 0
        Sum_Force_Wing = Matrix([0,0,0])
        Sum_Torque_Wing =  Matrix([0,0,0])
        for i in cp_list:
            VelinLDPlane, dragDirection, liftDirection= GenDirectForceWing(Omega, i, vB, vW, R_list[p], crossward_body)
            if bo==True:
                F_wing, Torque_wing =  GenForceWing(A_list[p], VelinLDPlane, dragDirection, liftDirection, Cd_list[p], Cl_list[p], i)
                Sum_Force_Wing  = Sum_Force_Wing +  F_wing
                Sum_Torque_Wing = Sum_Torque_Wing + Torque_wing
                p+=1
            else:
                if p == 2 :
                    # Comme la fonction GenForceWing donne les efforts des ailes dans leur repère propre, on doit passer par les matrice de rotation pour les ailes de la queue
                    F_wing, Torque_wing =  GenForceWing(A_list[p], VelinLDPlane, dragDirection, liftDirection, Cd_list[p], Cl_list[p], Matrix([0,0,0]))
                    Sum_Force_Wing  = Sum_Force_Wing +  r_queue.T  @ F_wing
                    Sum_Torque_Wing = Sum_Torque_Wing + i.cross(r_queue.T  @ F_wing)
                elif p == 3 :
                    F_wing, Torque_wing =  GenForceWing(A_list[p], VelinLDPlane, dragDirection, liftDirection, Cd_list[p], Cl_list[p], Matrix([0,0,0]))
                    Sum_Force_Wing  = Sum_Force_Wing +  r_queue_neg.T  @ F_wing
                    Sum_Torque_Wing = Sum_Torque_Wing + i.cross(r_queue_neg.T  @ F_wing)
                else:
                    F_wing, Torque_wing =  GenForceWing(A_list[p], VelinLDPlane, dragDirection, liftDirection, Cd_list[p], Cl_list[p], i)
                    Sum_Force_Wing  = Sum_Force_Wing +  F_wing
                    Sum_Torque_Wing = Sum_Torque_Wing + Torque_wing
                p+=1

        return Sum_Force_Wing, Sum_Torque_Wing

    def GenForceMoteur(Omega, ct, cq, omega_rotor, cp, vB, vW, ch, R, motor_axis_in_body_frame, spinning_sense):
        ## Cette fonction calcule les effort produit par un rotor sur le drone en fonction de son sens de rotation et de sa localisation, les efforts sont donnés
        ## dans le repère inertiel. l'axe des moteur est placé suivant l'axe x du drone (mode avion seulement)
        if bo==True:
            Air_speed_earth = vB - vW
            Axial_speed = Air_speed_earth.dot(R*motor_axis_in_body_frame)
            lat_speed = Air_speed_earth - (Axial_speed * (R*motor_axis_in_body_frame))
            T = ct*omega_rotor**2

            T_vec = T * R*motor_axis_in_body_frame

            torque_at_body_center = cp.cross(T_vec.T)
        else:
            Air_speed_earth = vB - vW
            air_speed_in_rotor_frame = (R.T* Air_speed_earth) - cp.cross(Omega)
            Axial_speed = air_speed_in_rotor_frame.dot(motor_axis_in_body_frame)
            lat_speed = air_speed_in_rotor_frame - (Axial_speed * (motor_axis_in_body_frame))
            T = ct*omega_rotor**2
            H = ch * omega_rotor

            T_vec = T * motor_axis_in_body_frame - H * lat_speed

            torque = - omega_rotor * cq * lat_speed
            torque = - spinning_sense * cq * T * motor_axis_in_body_frame
            torque_at_body_center = torque + cp.cross(T_vec.T)
        return T_vec, torque_at_body_center

    def Generate_Sum_Force_Moteur(Omega, ct, cq, omega_rotor, cp_list_rotor, vB, vW, ch, R, motor_axis_in_body_frame_list, spinning_sense_list):
        # Calcul des forces des moteurs sur le drone, génère toutes les forces, ainsi que le couple appliqué au centre de gravité du drone, dans le repère inertiel
        p = 0
        Sum_Force_Rotor = Matrix([0,0,0])
        Sum_Torque_Rotor =  Matrix([0,0,0])
        for cp in cp_list_rotor:
            F_rotor, Q_rotor = GenForceMoteur(Omega, ct, cq, omega_rotor, cp, vB, vW, ch, R, motor_axis_in_body_frame_list, spinning_sense_list[p])
            Sum_Force_Rotor  = Sum_Force_Rotor + F_rotor
            Sum_Torque_Rotor = Sum_Torque_Rotor + Q_rotor
            p+=1

        return Sum_Force_Rotor, Sum_Torque_Rotor

    def Compute_list_coeff(alpha_list, alpha_0_list, alpha_s, delta_0_list, delta_s, CL_1_sa, CD_1_fp, k_0, k_1, k_2, CD_0_fp, CD_0_sa, CD_1_sa):
        Cd_list = Matrix([0 for i in range(len(alpha_list))])
        Cl_list = Matrix([0 for i in range(len(alpha_list))])
        for count, alpha in enumerate(alpha_list):
            Cl_list[count],  Cd_list[count] =  compute_cl_cd(alpha, alpha_0_list[count], alpha_s, delta_0_list[count], delta_s, CL_1_sa, CD_1_fp, k_0, k_1, k_2, CD_0_fp, CD_0_sa, CD_1_sa)
        return Cl_list , Cd_list

    "liftdrag forces"
    t2=time.time()
    print("Elapsed : %f s , Prev step time: %f s \\ Solving lifrdrag model ..."%(t2-t0,t2-t1))
    # Ces équation servent uniquement pour le calcul de l'angle d'attaque pour le calcul des coeffficient aérodynamique Cd et Cl, il n'interviennent pas directement dans le calcul des efforts.
    VelinLDPlane, dragDirection, liftDirection=  GenDirectForceWing(Omega, X_cp, v_B, v_W, R, crossward_B)
    Cl_list, Cd_list = Compute_list_coeff(alpha_list, alpha0_list, alpha_s, delta0_list, delta_s, CL_1_sa, CD_1_fp, k_0, k_1, k_2, CD_0_fp, CD_0_sa, CD_1_sa)
    Sum_F_wing_complete, Sum_T_wing_complete = Generate_Sum_Force_wing(Aire_list, Omega, cp_list, R_list_sympy, v_B, v_W, Cd_list, Cl_list, crossward_B, r, r_neg)

    t3=time.time()
    print("Elapsed : %f s , Prev step time: %f s \\ Solving rotor model ..."%(t3-t0,t3-t2))
    ##################### Sommes des efforts des moteurs
    Sum_F_rotor_complete, Sum_T_rotor_complete = Generate_Sum_Force_Moteur(Omega, C_t, C_q, omega_rotor, cp_list_rotor, v_B, v_W, C_h, R, motor_axis_in_body_frame, spinning_sense_list)
    Sum_F_rotor_complete.simplify()
    Sum_T_rotor_complete.simplify()
    Effort_Aero_complete = [Sum_F_wing_complete + Sum_F_rotor_complete , Sum_T_wing_complete + Sum_T_rotor_complete]

    t35=time.time()
    print("Elapsed : %f s , Prev step time: %f s \\ Solving Dynamics ..."%(t35-t0,t35-t3))

    theta=[]
    for i in (CD_0_sa,
            CD_0_fp,
            CD_1_sa,
            CL_1_sa,
            CD_1_fp,
            k_0,
            k_1,
            k_2,
            C_t):
        theta.append(i)

    if id_wind:
        theta.append(Vw1)
        theta.append(Vw2)
        #theta.append(Vw3)

    theta = Matrix([theta])

    ########## Equation du gradient utilisé en simulation ####################
    VelinLDPlane_function = lambdify((Omega, X_cp, v_B, v_W, R), VelinLDPlane, 'numpy')
    dragDirection_function = lambdify((Omega, X_cp, v_B, v_W, R), dragDirection, 'numpy')
    liftDirection_function = lambdify((Omega, X_cp, v_B, v_W, R), liftDirection, 'numpy')
  #  Effort_Aero_complete_function = lambdify((Aire_list, Omega, R, v_B, v_W, cp_list, alpha_list, alpha0, alpha_s, delta0_list, delta_s, \
  #                                            CL_1_sa, CD_1_fp, k_0, k_1, k_2, CD_0_fp, CD_0_sa, CD_1_sa, C_t, C_q, C_h, omega_rotor), Effort_Aero_complete, 'numpy')

    t37=time.time()
    print("Elapsed : %f s , Prev step time: %f s \\ Generating costs ..."%(t37-t0,t37-t35))

    m=symbols('m', real=True)
    g1,g2,g3=symbols('g1,g2,g3', real=True)
    g=Matrix([g1,g2,g3])

    #Génération des équations finales pour la gradient du csout et des RMS error
    if bo==True:
        forces = (Sum_F_wing_complete + Sum_F_rotor_complete) + m*g
    else:
        forces = R@(Sum_F_wing_complete + Sum_F_rotor_complete) + m*g

    # torque = R@(Sum_T_wing_complete + Sum_T_rotor_complete)

    new_acc = forces/m
    new_v = v_B + new_acc*dt

    alog_i,alog_j,alog_k=symbols("alog_i,alog_j,alog_k",real=True)
    alog=Matrix([[alog_i],[alog_j],[alog_k]])

    err_a=Matrix(alog-new_acc)
    err_v=Matrix(v_log-new_v)

#     cost_scaler_a=symbols('C_sa',real=True,positive=True)
#     cost_scaler_v=symbols('C_sv',real=True,positive=True)

#     sqerr_a=Matrix([1.0/cost_scaler_a*(err_a[0,0]**2+err_a[1,0]**2+err_a[2,0]**2)])
#     sqerr_v=Matrix([1.0/cost_scaler_v*(err_v[0,0]**2+err_v[1,0]**2+err_v[2,0]**2)])

    sqerr_a=Matrix([err_a[0,0]**2+err_a[1,0]**2+err_a[2,0]**2])
    sqerr_v=Matrix([err_v[0,0]**2+err_v[1,0]**2+err_v[2,0]**2])

    t38=time.time()
    print("Elapsed : %f s , Prev step time: %f s \\ Generating Jacobian ..."%(t38-t0,t38-t37))

    Ja=sqerr_a.jacobian(theta)
    Jv=sqerr_v.jacobian(theta)

    Y=Matrix([new_acc,new_v,sqerr_a,sqerr_v,Ja.T,Jv.T])

    X =(alog,v_log,dt, Aire_list, Omega, R, v_pred, v_W, cp_list, alpha_list, alpha0, alpha_s, delta0_list, delta_s, \
                                CL_1_sa, CD_1_fp, k_0, k_1, k_2, CD_0_fp, CD_0_sa, CD_1_sa, C_t, C_q, C_h, omega_rotor, \
                                  g, m)

    function_physical_model =[VelinLDPlane_function, \
                             dragDirection_function, \
                             liftDirection_function,\
                             compute_alpha]
    model_func=lambdify(X,Y, modules='numpy')

    import dill as dill
    dill.settings['recurse'] = True

    dill.dump([model_func, function_physical_model], open("./.Funcs/model_func_"+str(used_logged_v_in_model)+"simple_"+str(bo), "wb"))
    print("Elapsed : %f s , Prev step time: %f s \\ Finished ..."%(time.time()-t0,time.time()-t3))

```

    Elapsed : 0.000357 s , Prev step time: -1 s \ Generating first symbols ...
    Elapsed : 0.052193 s , Prev step time: -1 s \ Generating dynamics function ...
    Elapsed : 0.052314 s , Prev step time: 0.000121 s \ Solving lifrdrag model ...
    Elapsed : 1.550169 s , Prev step time: 1.497855 s \ Solving rotor model ...
    Elapsed : 2.237926 s , Prev step time: 0.687757 s \ Solving Dynamics ...
    Elapsed : 3.460129 s , Prev step time: 1.222203 s \ Generating costs ...
    Elapsed : 3.558026 s , Prev step time: 0.097897 s \ Generating Jacobian ...
    Elapsed : 282.040830 s , Prev step time: 280.490661 s \ Finished ...

    Elapsed : 0.000273 s , Prev step time: -1 s \ Generating first symbols ...
    Elapsed : 0.011859 s , Prev step time: -1 s \ Generating dynamics function ...
    Elapsed : 0.011914 s , Prev step time: 0.000055 s \ Solving lifrdrag model ...
    Elapsed : 0.907588 s , Prev step time: 0.895674 s \ Solving rotor model ...
    Elapsed : 1.219560 s , Prev step time: 0.311972 s \ Solving Dynamics ...
    Elapsed : 1.936768 s , Prev step time: 0.717208 s \ Generating costs ...
    Elapsed : 1.940360 s , Prev step time: 0.003592 s \ Generating Jacobian ...
    Elapsed : 285.224708 s , Prev step time: 284.317121 s \ Finished ...

```python

```


# Last Mile Delivery Scheduling Problem

We will build an optimization model to solve this problem. Let's first write out the math formulation.

Sets:

Employees: $i \in I=\{1,...,19 \}$

Jobs: $j, k \in \Theta =\{1,...,49 \}$

Parameters:

$JS_j$: job $j$ starttime

$JE_j$: job $j$ endtime

$SS_i$: time of employee $i$ starts shift

$SE_i$: time of employee $i$ ends shift

Decision variables:

$
x_{ij} = \left\{
    \begin{array}\\
        1 & \text{if job } j \text{ is assigned to employee } i \\
        0 & otherwise \\
    \end{array}
\right.
$

$
z_{ijk} = \left\{
    \begin{array}\\
        1 & \text{ job } j \text{ starts before job } k,\text{ done by employee }i \\
        0 & \text{ job } j \text{ starts after job } k,\text{ done by employee }i \\
    \end{array}
\right.
$

The problem can be solved as the following MIP.

\begin{align}
	\text{Maximize:} \quad & \sum_{i \in I} \sum_{j \in \Theta} x_{ij} \\
	\text{Subject to:} \quad & \sum_{j \in \Theta} x_{ij} \leq 1, \quad\qquad\qquad\qquad\qquad\quad \forall j \\
    & x_{ij} \cdot (JS_j - SS_i) \geq 0, \qquad\qquad\qquad \forall i,j \\
    & x_{ij} \cdot (SE_i - JE_j) \geq 0, \qquad\qquad\qquad \forall i,j \\
	& x_{ij} \cdot JS_j \geq x_{ik} \cdot JE_k - M \cdot z_{ijk}, \quad\qquad \forall i,j, k \ and\  j <k\\
    & x_{ik} \cdot JS_k \geq x_{ij} \cdot JE_j - M \cdot (1-z_{ijk}), \quad \forall i,j, k \ and\  j <k\\
\end{align}

```python

```

```python
# This routine will need Gurobi to be installed
# conda config --add channels https://conda.anaconda.org/gurobi
# conda install gurobi
```

```python
import pandas as pd
import numpy as np
import gurobipy as gp
from gurobipy import GRB
import datetime
import matplotlib.pyplot as plt
```

```python
ev = pd.read_excel('input/data_carvana.xlsx',sheet_name='events')
dur = pd.read_excel('input/data_carvana.xlsx',sheet_name='duration')
res = pd.read_excel('input/data_carvana.xlsx',sheet_name='resources')
```

```python
res.head()
```

      ResourceId
      StartTime
      EndTime

      0
      9454310
      07:00:00
      19:00:00

      1
      10541233
      07:00:00
      17:00:00

      2
      9061975
      07:00:00
      17:00:00

      3
      7840175
      07:00:00
      17:00:00

      4
      10683867
      07:00:00
      17:00:00

```python
dur.columns
```

    Index(['OriginAddressId', 'DestinationAddressId',
           'TripDuration(hour:minute:second)', 'Unnamed: 3', 'Unnamed: 4',
           'Unnamed: 5', 'Unnamed: 6', 'Unnamed: 7', 'Unnamed: 8'],
          dtype='object')

```python
# Events to be scheduled
ev = ev.drop(['Unnamed: 4', 'Unnamed: 5', 'Unnamed: 6', 'Unnamed: 7'],axis=1)
```

```python
# Durations
dur = dur.drop(['Unnamed: 3', 'Unnamed: 4', 'Unnamed: 5', 'Unnamed: 6', 'Unnamed: 7', 'Unnamed: 8'], axis=1)
```

```python
ev.head()
```

      EventId
      Appointment Starttime
      Appointment Endtime
      AddressId

      0
      8366991
      08:15:00
      10:15:00
      A1

      1
      8536889
      08:15:00
      10:15:00
      A2

      2
      8293270
      08:22:30
      10:07:30
      A3

      3
      8318992
      08:22:30
      11:22:30
      A4

      4
      1563930
      08:57:30
      10:32:30
      A5

```python
dur.columns = ['OriginAddressId', 'AddressId', 'TripDuration']
```

```python
# Merge "Events" and "Durations" to be "jobs"
jobs = pd.merge(ev, dur, on='AddressId')
```

```python
jobs = jobs.drop('OriginAddressId',axis=1)
```

```python
jobs.head()
```

      EventId
      Appointment Starttime
      Appointment Endtime
      AddressId
      TripDuration

      0
      8366991
      08:15:00
      10:15:00
      A1
      00:30:00

      1
      8536889
      08:15:00
      10:15:00
      A2
      00:30:00

      2
      8293270
      08:22:30
      10:07:30
      A3
      00:22:30

      3
      8318992
      08:22:30
      11:22:30
      A4
      00:52:30

      4
      1563930
      08:57:30
      10:32:30
      A5
      00:27:30

```python
# Convert datetime.time format to seconds
def time_to_seconds(time):
    return (time.hour * 60 + time.minute) * 60 + time.second
```

```python
# resource = {resourceID: [ShiftStart, ShiftEnd], ... }
resource = {}
for i in range(len(res.index)):
    resource[res['ResourceId'][i]] = [time_to_seconds(res['StartTime'][i]), time_to_seconds(res['EndTime'][i]) ]
```

```python
# events = {eventID: [AppStart, AppEnd], ... }
events = {}
for i in range(len(ev.index)):
    events[jobs['EventId'][i]] = [time_to_seconds(jobs['Appointment Starttime'][i]),
                                  time_to_seconds(jobs['Appointment Endtime'][i]) ]
```

```python
# Pre-processing data for solving MIP in Gurobi
emp_num, shift_start, shift_end = gp.multidict(resource)
job_num, job_start, job_end = gp.multidict(events)
```

```python
availability=[]
for i in emp_num:
    for j in job_num:
        availability.append((i,j))
```

```python
bigM = 100000   # 1 day = 86400 seconds.
```

```python
# Model
m = gp.Model("carvana_schedule")
```

    Academic license - for non-commercial use only - expires 2021-06-18
    Using license file C:\Users\freed\gurobi.lic

```python
# Decision var: x[emp_num, job_num]
x = m.addVars(availability, vtype = GRB.BINARY, name="x")
```

```python
# Decision var: z[emp_num, job_num, job_num]
z = m.addVars(emp_num, job_num, job_num, vtype = GRB.BINARY, name="z")
```

```python
# Objective
m.setObjective(gp.quicksum(x[i] for i in availability), GRB.MAXIMIZE)
```

```python
# contranit (1): Each job can only be done by no more than ONE employee
m.addConstrs( (x.sum('*',s) <= 1 for s in job_num), "eachjob")
```

    {8366991: <gurobi.Constr *Awaiting Model Update*>,
     8536889: <gurobi.Constr *Awaiting Model Update*>,
     8293270: <gurobi.Constr *Awaiting Model Update*>,
     8318992: <gurobi.Constr *Awaiting Model Update*>,
     1563930: <gurobi.Constr *Awaiting Model Update*>,
     8528952: <gurobi.Constr *Awaiting Model Update*>,
     1566611: <gurobi.Constr *Awaiting Model Update*>,
     1566612: <gurobi.Constr *Awaiting Model Update*>,
     8502088: <gurobi.Constr *Awaiting Model Update*>,
     8455723: <gurobi.Constr *Awaiting Model Update*>,
     1546739: <gurobi.Constr *Awaiting Model Update*>,
     8513010: <gurobi.Constr *Awaiting Model Update*>,
     1547686: <gurobi.Constr *Awaiting Model Update*>,
     1540296: <gurobi.Constr *Awaiting Model Update*>,
     8526555: <gurobi.Constr *Awaiting Model Update*>,
     8495159: <gurobi.Constr *Awaiting Model Update*>,
     8511033: <gurobi.Constr *Awaiting Model Update*>,
     8530434: <gurobi.Constr *Awaiting Model Update*>,
     8370070: <gurobi.Constr *Awaiting Model Update*>,
     8550751: <gurobi.Constr *Awaiting Model Update*>,
     8532654: <gurobi.Constr *Awaiting Model Update*>,
     8563679: <gurobi.Constr *Awaiting Model Update*>,
     8563071: <gurobi.Constr *Awaiting Model Update*>,
     8206275: <gurobi.Constr *Awaiting Model Update*>,
     8556760: <gurobi.Constr *Awaiting Model Update*>,
     1546140: <gurobi.Constr *Awaiting Model Update*>,
     8566102: <gurobi.Constr *Awaiting Model Update*>,
     1328356: <gurobi.Constr *Awaiting Model Update*>,
     1559492: <gurobi.Constr *Awaiting Model Update*>,
     1566979: <gurobi.Constr *Awaiting Model Update*>,
     8288079: <gurobi.Constr *Awaiting Model Update*>,
     8548978: <gurobi.Constr *Awaiting Model Update*>,
     1568456: <gurobi.Constr *Awaiting Model Update*>,
     8572845: <gurobi.Constr *Awaiting Model Update*>,
     8577028: <gurobi.Constr *Awaiting Model Update*>,
     8560280: <gurobi.Constr *Awaiting Model Update*>,
     1558901: <gurobi.Constr *Awaiting Model Update*>,
     8572655: <gurobi.Constr *Awaiting Model Update*>,
     8544795: <gurobi.Constr *Awaiting Model Update*>,
     8566547: <gurobi.Constr *Awaiting Model Update*>,
     8539806: <gurobi.Constr *Awaiting Model Update*>,
     8569097: <gurobi.Constr *Awaiting Model Update*>,
     8519419: <gurobi.Constr *Awaiting Model Update*>,
     8517806: <gurobi.Constr *Awaiting Model Update*>,
     8605289: <gurobi.Constr *Awaiting Model Update*>,
     8545203: <gurobi.Constr *Awaiting Model Update*>,
     8586152: <gurobi.Constr *Awaiting Model Update*>,
     8368018: <gurobi.Constr *Awaiting Model Update*>,
     8497098: <gurobi.Constr *Awaiting Model Update*>}

```python
# contranit (2)(3): Each scheduled job must be within that employee's shift hours.
for i in emp_num:
    for j in job_num:
        m.addConstr( x[i, j] * (job_start[j] - shift_start[i]) >= 0 , "workhour1[%s, %s]"%(i, j) )
        m.addConstr( x[i, j] * (job_end[j] - shift_end[i]) <= 0 , "workhour2[%s, %s]"%(i, j) )
```

```python
# contranit (4)(5): Disjunctive constraints: for employee i, scheduled job j and k cannot overlap.
for i in emp_num:
    for j in job_num:
        for k in job_num:
            if j<k:
                #print(i,j,k)
                m.addConstr( x[i,j]*job_start[j] >= x[i,k]*job_end[k] - bigM*z[i,j,k] , "disjunctive1[%s, %s, %s]"%(i, j, k) )
                m.addConstr( x[i,k]*job_start[k] >= x[i,j]*job_end[j] - bigM*(1-z[i,j,k]) , "disjunctive2[%s, %s, %s]"%(i, j, k) )
```

```python
# Solve
m.optimize()
```

    Gurobi Optimizer version 9.1.1 build v9.1.1rc0 (win64)
    Thread count: 2 physical cores, 4 logical processors, using up to 4 threads
    Optimize a model with 46599 rows, 46550 columns and 136843 nonzeros
    Model fingerprint: 0x4635de26
    Variable types: 0 continuous, 46550 integer (46550 binary)
    Coefficient statistics:
      Matrix range     [1e+00, 1e+05]
      Objective range  [1e+00, 1e+00]
      Bounds range     [1e+00, 1e+00]
      RHS range        [1e+00, 1e+05]
    Found heuristic solution: objective 1.0000000
    Found heuristic solution: objective 2.0000000
    Presolve removed 46247 rows and 45773 columns
    Presolve time: 0.20s
    Presolved: 352 rows, 777 columns, 4417 nonzeros
    Variable types: 0 continuous, 777 integer (777 binary)

    Root relaxation: objective 4.900000e+01, 167 iterations, 0.00 seconds

        Nodes    |    Current Node    |     Objective Bounds      |     Work
     Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

    *    0     0               0      49.0000000   49.00000  0.00%     -    0s

    Explored 0 nodes (167 simplex iterations) in 0.27 seconds
    Thread count was 4 (of 4 available processors)

    Solution count 3: 49 2 1

    Optimal solution found (tolerance 1.00e-04)
    Best objective 4.900000000000e+01, best bound 4.900000000000e+01, gap 0.0000%

```python
# print results
solution = m.getAttr('x',x)

print('Total numbers of events scheduled: %g' % m.objVal)

schedule = pd.DataFrame(columns=['ResourceId','EventId','Appointment Starttime','Appointment Endtime'])

for i in emp_num:
    for j in job_num:
        if solution[i,j] == 1:
            schedule.loc[len(schedule.index)] = [i,j, ev.at[ev[ev['EventId'] == j].index[0], 'Appointment Starttime'], ev.at[ev[ev['EventId'] == j].index[0], 'Appointment Endtime']]
            #print('Employee %s takes event %s' % (i, j))

schedule.to_excel("LastMileDeliverySchedule_XH.xlsx")
schedule.head()
```

    Total numbers of events scheduled: 49

      ResourceId
      EventId
      Appointment Starttime
      Appointment Endtime

      0
      9454310
      8502088
      09:00:00
      11:00:00

      1
      9454310
      8550751
      11:45:00
      14:00:00

      2
      9454310
      8544795
      14:30:00
      17:30:00

      3
      10541233
      8293270
      08:22:30
      10:07:30

      4
      10541233
      8206275
      11:37:30
      15:07:30

```python
employees = sorted(list(schedule['ResourceId'].unique()))
```

```python
# Dictionary: emp_sch = {ResourceId1: [(AppStart1, AppDur1), (AppStart2, AppDur2), (AppStart3, AppDur3)... ],
#                        ResourceId2: [(), ()],
#                        ...               }
emp_sch = {}
for i in employees:
    emp_sch[i] = []
    for st in list( schedule[schedule['ResourceId'] == i]['Appointment Starttime']):
        emp_sch[i].append( (time_to_seconds(st), time_to_seconds(schedule.at[schedule[schedule['Appointment Starttime'] == st].index[0], 'Appointment Endtime'])- time_to_seconds(st) ) )
```

```python
fig, gnt = plt.subplots()

fig.set_size_inches(18.5, 10.5)
gnt.set_ylim(0, 10+10*len(employees))
gnt.set_xlim(30000, 70000)
plt.title("Carvana Last Mile Delivery Schedule")
gnt.set_xlabel('Time')
gnt.set_ylabel('EmployeeID')

# Setting x axis
gnt.set_xticks( list(range(25200,79200,3600)) ) # 7:00 - 22:00
gnt.set_xticklabels(['7:00', '8:00', '9:00', '10:00', '11:00', '12:00', '13:00', '14:00',
                     '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00'])

# Setting y axis
gnt.set_yticks( list(range(0, 10*len(employees)+20, 10 )) )
emp_labels = employees.copy()
emp_labels.insert(0,'')
emp_labels.append('')
gnt.set_yticklabels(emp_labels)

gnt.grid(True, color = 'tab:olive', linestyle = ':', linewidth = 0.5)

for h, i in enumerate(emp_sch):
    gnt.broken_barh(emp_sch[i], (5+10*h, 9), facecolors =('green'))

plt.savefig("CarvanaSchedule.png")
```

```python

```


```python
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn import metrics
```

# Decision Trees and Random Forests

In this notebook, We will introduce the concept behind methods of Machine Learning for classification, named Decision Tree.

The notebook assumes the reader has a previous knowledge of the main ideas behind Random Variables and Expectation of Random Variables.

****

# Entropy

Prior to understading what Decisions Trees are, it is necessary to be acquainted with the concept of Entropy.

Entropy is a measure for the uncertainty of a determined random variable. Let $X$ be a random variable, with state space $\mathcal{S}$, and probability density function $P(X = x), x \in \mathcal{S}$, we have that the Entropy for $X$, namely $H(X)$, is:

\begin{equation}
H(X) = - \sum_{x \in \mathcal{S}} P(X = x) log_2(P(X = x))
\end{equation}

For convenience, we assume that $0\cdot log_2(0) = 0$, which is explained by the fact that $\lim_{x \to 0} xlog_2(x) = 0$

Entropy's unit of measurement are bits. The unit of measurement depends entirely on the base we chose for the logarithm, in the case we use $log_{10}$ or $log_3$, Entropy would be measured in dits and trits, respectively.

Note: Notice that the definition of Entropy $H(X)$, is similar to the expectation of the function $g(X) = \frac{1}{log_2(P(X = X))}$ of a determined random variable $X$.

\begin{equation}
H(X) = E\left[\frac{1}{log_2(P(X = X))}\right]
\end{equation}

```python
#Function to calculate the entropy of a probability distribution
def calculateEntropy(prob_function):
    entropy = 0
    for i in range(len(prob_function)):
        if(prob_function[i] != 0):
            entropy += prob_function[i]*np.log2(prob_function[i])
    return -entropy

#We will use a Bernoulli's distribution where our random variable assumes the values 0 and 1
#with the probabilities (1 - p) and p, respectively.
#Note that Bernoulli's distribution, gives us a different distribution for each value of p,
#consequently, Entropy will also be different for each of those distributions.
x = np.linspace(0,1, 20)
y = [calculateEntropy([1 - p, p]) for p in x]
plt.plot(x, y, '-bo')
plt.xlabel("Parameter p for Bernourlli's distribution")
plt.ylabel("Entropy")
plt.show
```

Above we see an exemple of an Entropy graph, for each different value of the parameter p in Bernoulli's distribution. It's easy to realize that when $p = 0$ or $p = 1$, Entropy is equal to 0, which intuitively, makes total sense, since in those cases Bernoulli's distribution would only have 0 or 1 as a result, that is, there is no uncertainty over the distribution.

Furthermore, we have that Entropy is maximized when $p = 0.5$, which is exactly when values 0 and 1 have equal chances of being the outcome, in other words, the uncertainty about which of the two values we will get as a result is maximum.

# Mutual Information

Beyond the concept of Entropy, we also need to address how  Mutual Information  works:

\begin{equation}
I(X,Y) = H(X) - H(X|Y)
\end{equation}

Notice that now we have introduced Conditional Entropy $H(X|Y)$, prior to addressing this matter properly, we shall understand the intuition behind Mutual Information, we are calculating the amount reduced in the Entropy of X when conditioned on Random Variable Y. In other words, how much conditioning X on Y, reduced the uncertainty of X.

From this, it is possible to realize that Mutual Information is a measure of dependency between random variables, that is, if we have big reduction in the uncertainty of X, when conditioning the value of X on Y, it is possible to claim that Y and X are related to the deegre of how big that reduction is.

***

 Conditional Entropy $H(X|Y)$, comes from the definition of Entropy, shown previously, from the expectation of a function of Random Variables:

\begin{equation}
H(X|Y) = E\left[\frac{1}{log_2(P(X = x | Y = y))} \right] = - E\left[log_2(P(X = x | Y = y)) \right]
\end{equation}
\begin{equation}
= - \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y)\cdot log_2(P(X = x | Y = y))
\end{equation}

With $ \mathcal{Y}$ e $\mathcal{X}$ being the state spaces of the random variables Y and X, respectively.

Now that we know Conditional Entropy, we can get a better equation for the Mutual Information:

\begin{equation}
I(X,Y) = H(X) - H(X|Y) = - \sum_{x \in \mathcal{S}} P(X = x) log_2(P(X = x))  - \left(- \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y)\cdot log_2(P(X = x | Y = y)) \right)
\end{equation}
\begin{equation}
= - \sum_{x \in \mathcal{X}} P(X = x) log_2(P(X = x))  + \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y)\cdot log_2(P(X = x | Y = y))
\end{equation}
\begin{equation}
= - \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y) \cdot log_2(P(X = x))  + \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y)\cdot log_2(P(X = x | Y = y))
\end{equation}
\begin{equation}
= \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y) \cdot log_2\left(\frac{P(X = x | Y = y)}{P(X = x)}\right)
\end{equation}
\begin{equation}
I(X, Y)= \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y) \cdot log_2\left(\frac{P(X = x, Y = y)}{P(X = x)P(Y = y)}\right)
\end{equation}

Where the second to third equality, comes from the fact that:
\begin{equation}
P(X = x) = \sum_{y \in \mathcal{Y}} P(X = x|Y = y)P(Y=y) = \sum_{y \in \mathcal{Y}} P(X = x,Y = y)
\end{equation}
Which is the application of the Law of Total Probability

That way, we have for Mutual Information $I(X, Y)$:
\begin{equation}
    I(X, Y)= \sum_{x \in \mathcal{X}}\sum_{y \in \mathcal{Y}} P(X = x, Y = y) \cdot log_2\left(\frac{P(X = x, Y = y)}{P(X = x)P(Y = y)}\right)
\end{equation}

# Decision Trees
A Decision Tree is a structure of relation between categories. Each of the nodes of a Decision Tree represents a kind of "test" in a determined feature, the relations between each of the nodes ("branches" of the tree) represent the values that can be assigned to such feature.

---

For a better understanding, we will use the following dataset as an example, it contains data about how weather was in a specific day and, whether or not people played tennis that day.

The variable Outlook contains the values Sunny, Overcast and Rain.

The variable Temperature contains the values Hot, Mild and Cold.

The variable Humidity contains the values High e Normal.

The variable Wind contains the values Strong and Weak.

The variable PlayTennis contains the values Yes and No, indicating whether or not tennis was played that day.

```python
col_names = ['Outlook','Temperature','Humidity','Wind','PlayTennis']

example = pd.read_csv("exemplo_arvore.csv", header=None, names=col_names)
example.head()
```

      Outlook
      Temperature
      Humidity
      Wind
      PlayTennis

      0
      Sunny
      Hot
      High
      Weak
      No

      1
      Sunny
      Hot
      High
      Strong
      No

      2
      Overcast
      Hot
      High
      Weak
      Yes

      3
      Rain
      Mild
      High
      Weak
      Yes

      4
      Rain
      Cool
      Normal
      Weak
      Yes

It is possible to see a Decision Tree generated for this dataset in the figure below.
 Note that we are deciding whether we will play tennis or not based off the wheather information we have.

What the Decision Tree above is telling us is the following:

In case the Outlook is Sunny, we will play tennis if Humidity is Normal, in case it is High, we won't play.

In case the Outlook is Overcast, we will play tennis.

In case the Outlook is Rain, we will play tennis if Wind is Weak, in case it is Strong, we won't play.

Note that when we want to model a problem, where the column  target, in the example above 'PlayTennis', can get assigned only two values, we have a boolean function.

In the case above it is possible to map:

No $\rightarrow$ False

Yes $\rightarrow$ True

And just like that, build a Truth Table for the Decision Tree:

    Outlook = Sunny
    Outlook = Overcast
    Outlook = Rain
    Humidity = High
    Humidity = Normal
    Wind = Strong
    Wind = Weak
    PlayTennis

    True
    False
    False
    True
    False
    False
    False
    False

    True
    False
    False
    False
    True
    False
    False
    True

    False
    True
    False
    False
    False
    False
    False
    True

    False
    False
    True
    False
    False
    True
    False
    False

    False
    False
    True
    False
    False
    False
    True
    True

Many lines of the table above were omitted, otherwise, such table would be humongous. More specifically, since we have 7 columns used for classification, and each of those can get assigned two values: True or False. We have $2^7 = 128$ possible combinations of values, that is, this Truth Table complete, would have 128 lines.

# The problem with the best Decision Tree
Having defined how a Decision Tree works, it is easy to observe that modifying the features we choose for each of the nodes gives us different trees as a result.

From this observation, one can easily infer that there are many different Decision Trees for the same problem, in the example above, the Decision Tree is built using 7 attributes ('Outlook = Sunny', 'Outlook = Overcast', 'Outlook = Rain', 'Humidity = High', 'Humidity = Normal', 'Wind = Strong' e 'Wind = Weak') and we saw that the Truth Table for this tree would have $2^7$ lines.

Notice that given a determined number of attributes, let's say $n$, for a single Decision Tree $T$, the Truth Table of $T$ would have $2^n$ lines.

Intuitively if we were to get only the target column 'PlayTennis', we would have a vector with $2^n$ elements, as observed below:

    PlayTennis

    False

    True

    True

    False

    True

    ...

Different Decision Trees, that solve the same problem, use the same attributes for classification, the only thing that changes in those trees, is the classification itself. That is, for every different Decision Tree possible, we have the same Truth Table, except for the target column, in the example above the column 'PlayTennis'.

From that, the total quantity of Decision Trees for a determined problem, is the total combination of values that each of the elements of the target columns in the Truth Table can get assigned to. Since the vector generated by the target column is a vector of $2^n$ elements (being $n$ the number of attributes), we have that each of the elements in this vector can get assigned a value (True or False), therefore, the total number of combinations is $2^{2^n}$.

Thus, in the example above, since we are using 7 attributes, we would have $2^{2^7} = 3.4 \cdot 10^{38}$ possible Decision Trees that can be built (and we didn't even consider the Temperature feature).

To determine which Decision Tree is the best Decision Tree, it would be necessary to iterate over all $3.4 \cdot 10^{38}$ possible trees and check which one best classifies our target 'PlayTennis'. It is, definitely impracticable to iterate over so many trees, and because of that we need a kind of heuristic that will help us estimate a tree that is good enough.

# How to find a "good" tree

Notice that we are making use of the word "good", in quotes, since we are building an algorithm for finding a tree that is good and not optimal (as seen before the optimal tree is a intractable problem).

The idea is to put the most important attributes closer to the root of the tree, while the least important attributes get put in the lower levels of the tree. By "most important attributes" we refer to the attributes that better classify our target.

For example, in the problem above, we see that the attribute 'Outlook = Overcast', classifies well 'PlayTennis', since  in every case where 'Outlook = Overcast' we see 'PlayTennis' as True, in other word, conditioning 'PlayTennis' on 'Outlook = Overcast', would reduce uncertainty (or Entropy) in 'PlayTennis'.

In order to build the algorithm, instead of choosing the attributes that better classify the target, we shall choose the features, as seen previously, the features are the nodes of the trees and the attributes are the edges between the nodes.

Furthermore, we have a good way of determing how much uncertainty in a random variable is reduced, when we condition it on another one, and that is the Mutual Information.

In the example of 'PlayTennis', in order to choose the feature that better classifies 'PlayTennis', we would calculate I(PlayTennis, Outlook), I(PlayTennis, Humidity), I(PlayTennis, Wind) and I(PlayTennis, Temperature) and choose the feature that maximizes the Mutual Information.

After we have decided the first feature that will be used to classify the target, we use such feature, to split our dataset into $n$ datasets, where $n$ is the number of attributes that can get assigned to the chosen feature.

Aplying this idea to the example of 'PlayTennis', suppose that the chosen feature is 'Humidity', which has two attributes associated to it ('High' or 'Normal'), we build two dataframes que that together form the original dataframe, in one of them we will have every line of the original dataframe, where 'Humidity = High' e in the other one we will have every line of the original dataframe, where 'Humidity = Normal'.

Using the generated dataframes, from the split of the original dataframe, we shall do the same process of classification, choosing the feature that best classifies 'PlayTennis' in each of the generated dataframes gerados. Notice that, as soon as the original dataframe is split into $n$ dataframes, each of these new dataframes is a new Decision Tree problem, with less examples (or lines) and minus one feature.

We shall do such process of splitting the dataframe, until we don't have any more features to use in the classification, or until the Entropy in target is 0, that is, all the values in the column target are the same.

# Scikit-learn's Decision Tree

We will use the method 'DecisionTreeClassifier' from the scikit-learn library on python, to classify our dataset of 'PlayTennis'.

```python
X = example.loc[:, example.columns != 'PlayTennis']
y = example.PlayTennis
```

The data in our example is categorical and the Decision Tree classifier from scikit-lean does not work with this kind of data. We need to treat data before building our classifier. For this we will use the function from pandas "pd.get_dummies" that converts ctegorical attributes into attributes with values 0 and 1.

For example, the attribute Humidity that can receive the values High and Normal, is now represented by attributes Humidity_High e Humidity_Normal, that can get assigned the values 0 and 1.

Those new created variables are called dummy variables, a categorical variable with $n$ attributes, leads to $n$  dummy variables, one for each attribute.

```python
X_dummy = pd.get_dummies(X) #transforming the dataset
X_dummy.head()
```

      Outlook_Overcast
      Outlook_Rain
      Outlook_Sunny
      Temperature_Cool
      Temperature_Hot
      Temperature_Mild
      Humidity_High
      Humidity_Normal
      Wind_Strong
      Wind_Weak

      0
      0
      0
      1
      0
      1
      0
      1
      0
      0
      1

      1
      0
      0
      1
      0
      1
      0
      1
      0
      1
      0

      2
      1
      0
      0
      0
      1
      0
      1
      0
      0
      1

      3
      0
      1
      0
      0
      0
      1
      1
      0
      0
      1

      4
      0
      1
      0
      1
      0
      0
      0
      1
      0
      1

And in the following cell, we create the class for the Decision Tree classifier from scikit-learn, besides using the method 'fit' to create the tree for the data we have.

```python
# creating the variable for the Decision Tree classifier.
clf = DecisionTreeClassifier(criterion="entropy")

# We use the method fit to build the decision tree out of our data.
clf = clf.fit(X_dummy, y)
```

```python
clf.predict(X_dummy)
```

    array(['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes',
           'Yes', 'Yes', 'Yes', 'No'], dtype=object)

It's possible to use the method 'plot_tree', which is also from the scikit-learn library, to visualize the tree.

```python
fig = plt.figure(figsize=(25,20))
_ = plot_tree(clf, feature_names=X_dummy.columns, class_names=list(y.unique()), filled=True)

```

# Random Forests

As said previously, Decision Trees are built based off an heuristic of what is the best tree. Since such process comes from an heuristic, there is no guarantee that the generated tree is, in fact, the best tree, as a matter of fact, there are other heuristics for the construction of a Decision Tree, what was presented previously is just one of them.

From the idea that we can have many different Decision Trees that classify the target, comes the notion of Random Forests.

---
The idea behind Random Forests, relies on generating several different Decision Trees, that would classify the target differently, and in that way, make all of those Decision Trees vote in the best classification.

In order to start building a Random Forest, initially we shall decide how many trees there will be in that Forest.

After deciding the amount of trees in the Forest, we will make a new dataset from the original, selecting samples from the original dataset randomly, these selected samples will be present in this new dataset we are building. The technique is called Bagging.

For each tree in the Random Forest there will be a new dataset generated from the Bagging technique, which will also be the dataset used to train the tree it is associated with.

There are many different ways of choosing heuristics for building Decision Trees in a Random Forest. The default from the 'sklearn' library, is to select at each iteration a limited number of features that could be in the node of the current iteration.

In 'sklearn', it is possible to define a constant $n$, in a way that in each iteration, the algorithm can choose only $n$ features selected randomly. The 'sklearn' library also provides the option of defining a function of the available number of features.

For example, let $n$ be the number of available features in the current iteration, sklearn's default is to select $\sqrt{n}$ random features that can be chosen for that node.

# Scikit-learn's Random Forest

We will use the method 'RandomForestClassifier' from scikit-learn library on python, to classify our 'PlayTennis' dataset.

```python
#Building the Random Forest classifier
#The parameter n_estimators is how many trees are gonna be in the forest
#The parameter bootstrap is telling the algorithm to execute the process of Bagging for each tree.
#The parameter max_features is the number of features that can be chosen at each iteration, in the following case
#the number is the square root of how many features are available.
#The parameter criterion is the criteron used to build the tree, in this case Entropy/Mutual Information.
clf = RandomForestClassifier(n_estimators=100, bootstrap=True, max_features="sqrt", criterion="entropy")

# We use the method fit to build the classifier from the data provided.
clf = clf.fit(X_dummy, y)
```

```python
clf.predict(X_dummy)
```

    array(['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes', 'Yes',
           'Yes', 'Yes', 'Yes', 'No'], dtype=object)

The forest is composed by 100 trees (parameter that can be changed in 'n_estimators'), because of that it is not possible to visualize the entire forest, therefore, we pick one tree to be shown.

```python
fig = plt.figure(figsize=(25,20))
_ = plot_tree(clf.estimators_[25], feature_names=X_dummy.columns, class_names=list(y.unique()), filled=True)
```


## Running simulation with spin-transfer torque (STT)

[Weiwei Wang and Hans Fangohr for Aurelien Manchon's group, May 2014]

The implemented equation in finmag with STT is [1,2],

\begin{equation}
\frac{\partial \vec{m}}{\partial t} = - \gamma \vec{m} \times \vec{H} + \alpha \vec{m} \times  \frac{\partial \vec{m}}{\partial t}  + u (\vec{j}_s \cdot \nabla) \vec{m} - \beta u [\vec{m}\times (\vec{j}_s \cdot \nabla)\vec{m}]
\end{equation}

where $\vec{j}_s$ is the current density. $u$ is the material parameter, and in default,

$$u=u_{ZL}=\frac{u_0}{1+\beta^2}$$

There is an option "using_u0" in sim.set_zhangli method, u=u0 if "using_u0 = True" and

$$u_0=\frac{g \mu_B P}{2 |e| M_s}=\frac{g \mu_B P a^3}{2 |e| \mu_s}$$

where $\mu_B=|e|\hbar/(2m)$ is the Bohr magneton, P is the polarization rate, e is the electron charge.

The implemented Landau-Lifshitz-Gilbert equation with Slonczewski spin-transfer torque is [3],

\begin{equation}
\frac{\partial \vec{m}}{\partial t} = - \gamma \vec{m} \times \vec{H} + \alpha \vec{m} \times  \frac{\partial \vec{m}}{\partial t}
+ \gamma \beta \epsilon (\vec{m} \times \vec{m}_p \times \vec{m})
\end{equation}

where

\begin{align*}
\beta&=\left|\frac{\hbar}{\mu_0 e}\right|\frac{J}{tM_\mathrm{s}}\,\,\, \mathrm{and}\\
\epsilon&=\frac{P\Lambda^2}{(\Lambda^2+1)+(\Lambda^2-1)(\vec{m}\cdot\vec{m}_p)}
\end{align*}

[1] S. Zhang and Z. Li, Roles of nonequilibrium conduction electrons on the magnetization dynamics of ferromagnets, Phys. Rev. Lett. 93, 127204 (2004).

[2] A. Thiaville, Y. Nakatani, J. Miltat and Y. Suzuki, Micromagnetic understanding of current-driven domain wall motion in patterned nanowires, Europhys. Lett. 69, 990 (2005).

[3] J. Xiao, A. Zangwill, and M. D. Stiles, “Boltzmann test of Slonczewski’s theory of spin-transfer torque,” Phys. Rev. B, 70, 172405 (2004).

Import the related python modules and create a 2d mesh,

```
# show matplotlib plots inside this notebook
%matplotlib inline
import os
import matplotlib.pyplot as plt
import dolfin as df
import numpy as np
from finmag import Simulation as Sim
from finmag.energies import Exchange, DMI, UniaxialAnisotropy
from finmag.util.dmi_helper import find_skyrmion_center_2d
from finmag.util.helpers import set_logging_level
import finmag
#set_logging_level("INFO")  # use this to reduce DEBUG output if desired

mesh = df.RectangleMesh(0, 0, 200, 40, 200, 40)
```

We define a function to generate a skyrmion in the track,

```
def m_init_one(pos):
    x,y = pos
    if y > 35:
        return (1,0,0)
    elif y < 5:
        return (-1,0,0)

    x0 = 50
    y0 = 20
    if (x-x0)**2+(y-y0)**2<10**2:
        return (0,0,-1)
    else:
        return (0,0,1)
```

Similarly, we define a function for the current density profile, here the current density is uniformly distributed,

```
def init_J_x(pos):
    return (1e12,0,0)
```

Create function that can plot scalar field of one magnetisation component:

```
def plot_2d_comp(sim, comp='z', title=None):
    """expects a simulation object sim and a component to plot. Component can be
    'x' or 'y' or 'z'

    Not optimised for speed.
    """
    finmag.logger.info("plot_2d_comp: at t = {:g}".format(sim.t))
    comps = {'x': 0, 'y': 1, 'z': 2}
    assert comp in comps, "print unknown component {}, we know: {}".format(comp, comp.keys())
    m = sim.get_field_as_dolfin_function('m')

    # get mesh coordinates for plotting
    coords = mesh.coordinates()
    mym = []
    for coord in coords:
        mym.append(m(coord))

    import matplotlib.pyplot as plt
    import matplotlib.tri as tri
    import numpy as np

    x = [ r[0] for r in coords]
    y = [ r[1] for r in coords]
    # extract i-ith component of magnetisation
    mi = [ m[comps[comp]] for m in mym]

    # Create the Triangulation; no triangles so Delaunay triangulation created.
    triang = tri.Triangulation(x, y)

    # tripcolor plot.
    plt.figure()
    plt.gca().set_aspect('equal')
    plt.tripcolor(triang, mi, shading='flat', cmap=plt.cm.rainbow)
    plt.colorbar()
    if title:
        plt.title(title)
    else:
        plt.title('Plot of {} component of m at t={:.3f}ns'.format(comp, sim.t * 1e9))

```

We create function to relax the system, inside the function we create a simulation instance and considering the exchange, anisotropy and DM interactions, notice that we use D=3 mJ m$^{-2}$

```
def relax():
    #we use the 1d pbc in x direction, and pbc=None will get rid of the pbc
    sim = Sim(mesh, Ms=5.8e5, unit_length=1e-9, pbc='1d')

    sim.set_m(m_init_one)
    plot_2d_comp(sim, comp='z', title='initial magnetisation (z-comp)')

    sim.add(UniaxialAnisotropy(K1=6e5, axis=[0, 0, 1]))
    sim.add(Exchange(A=1.5e-11))
    sim.add(DMI(D=3e-3))
    sim.alpha = 0.5

    sim.relax(stopping_dmdt=0.05)
    plot_2d_comp(sim, comp='z', title='relaxed magnetisation (z-comp)')

    #after the relaxation, we save the magnetisation for next stage
    np.save('m00.npy',sim.m)

    return sim
```

```
sim = relax()
```

## We move the skyrmion by zhang-li spin transfer torque,

```
def move_skyrmion():

    sim = Sim(mesh, Ms=5.8e5, unit_length=1e-9, pbc='1d')
    sim.set_m(np.load('m00.npy'))

    sim.add(UniaxialAnisotropy(K1=6e5, axis=[0, 0, 1]))
    sim.add(Exchange(A=1.5e-11))
    sim.add(DMI(D=3e-3))
    sim.alpha = 0.3

    #We use the zhang-li spin-transfer torque with parameters that polarisation=0.5 and beta=0.01
    sim.set_zhangli(init_J_x, P=0.5, beta=0.01, using_u0=False)

    # every 0.1ns save vtk data
    sim.schedule('save_vtk', every=1e-10, filename='vtks/m.pvd', overwrite=True)
    # every 0.1ns save raw data
    sim.schedule('save_m', every=1e-10, filename='npys/m.pvd', overwrite=True)
    # every 0.1ns create plot for notebook
    sim.schedule(plot_2d_comp, every=1e-10)

    # now do the calculation (events scheduled above will be done automatically)
    sim.run_until(0.5e-9)
```

```
move_skyrmion()
```

### We now use the Slonczewski spin-transfer torque to move the skyrmion

```
def move_skyrmion_S():

    sim = Sim(mesh, Ms=5.8e5, unit_length=1e-9, pbc='1d')
    sim.set_m(np.load('m00.npy'))

    sim.add(UniaxialAnisotropy(K1=6e5, axis=[0, 0, 1]))
    sim.add(Exchange(A=1.5e-11))
    sim.add(DMI(D=3e-3))
    sim.alpha = 0.3

    #We using the Slonczewski spin-transfer torque with parameters polarisation=0.5 thickness=0.4e-9 and direction=(0,0,1)
    sim.set_stt(current_density=1e10, polarisation=0.5, thickness=0.4e-9, direction=(0,1,0))

    sim.schedule('save_vtk', every=1e-10, filename='vtks/m2.pvd', overwrite=True)
    sim.schedule('save_m', every=1e-10, filename='npys/m2', overwrite=True)
    # every 0.1ns create plot for notebook
    sim.schedule(plot_2d_comp, every=1e-10)

    sim.run_until(0.5e-9)
    return sim
```

```
move_skyrmion_S()
```

At last, we create a function to plot the skyrmion positions as a function of time:

```
def plot_skx_pos_t():

    sim = Sim(mesh, Ms=5.8e5, unit_length=1e-9, pbc='1d')

    ts = []
    xs = []
    ys = []

    for i in range(6):
        sim.set_m(np.load('npys/m2_%06d.npy'%i))
        x,y = find_skyrmion_center_2d(sim.llg._m)
        ts.append(1e-10*i)
        xs.append(x)
        ys.append(y)

    fig=plt.figure()
    plt.plot(ts,xs,'.-',label='x')
    plt.plot(ts,ys,'.-',label='y')

    #plt.xlim([0,6])
    #plt.ylim([-0.3,0.2])
    plt.xlabel('time (s)')
    plt.ylabel('pos (nm)')

    plt.legend()
```

Plot the data:

```
plot_skx_pos_t()
```

```
finmag.__version__
```

    '4648:ca9fb31f80d2421f5b51bf89660836dc9fdc7125'


```python
%matplotlib inline
```

# A Gentle Introduction to ``torch.autograd``
---------------------------------

``torch.autograd`` is PyTorch’s automatic differentiation engine that powers
neural network training. In this section, you will get a conceptual
understanding of how autograd helps a neural network train.

## Background

Neural networks (NNs) are a collection of nested functions that are
executed on some input data. These functions are defined by *parameters*
(consisting of weights and biases), which in PyTorch are stored in
tensors.

Training a NN happens in two steps:

**Forward Propagation**: In forward prop, the NN makes its best guess
about the correct output. It runs the input data through each of its
functions to make this guess.

**Backward Propagation**: In backprop, the NN adjusts its parameters
proportionate to the error in its guess. It does this by traversing
backwards from the output, collecting the derivatives of the error with
respect to the parameters of the functions (*gradients*), and optimizing
the parameters using gradient descent. For a more detailed walkthrough
of backprop, check out this `video from
3Blue1Brown <https://www.youtube.com/watch?v=tIeHLnjs5U8>`__.

## Usage in PyTorch

Let's take a look at a single training step.
For this example, we load a pretrained resnet18 model from ``torchvision``.
We create a random data tensor to represent a single image with 3 channels, and height & width of 64,
and its corresponding ``label`` initialized to some random values.

```python
import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
```

    Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to C:\Users\Administrator/.cache\torch\hub\checkpoints\resnet18-f37072fd.pth
    100.0%

Next, we run the input data through the model through each of its layers to make a prediction.
This is the **forward pass**.

```python
prediction = model(data) # forward pass
```

We use the model's prediction and the corresponding label to calculate the error (``loss``).
The next step is to backpropagate this error through the network.
Backward propagation is kicked off when we call ``.backward()`` on the error tensor.
Autograd then calculates and stores the gradients for each model parameter in the parameter's ``.grad`` attribute.

```python
loss = (prediction - labels).sum()
loss.backward() # backward pass
```

Next, we load an optimizer, in this case SGD with a learning rate of 0.01 and momentum of 0.9.
We register all the parameters of the model in the optimizer.

```python
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
```

Finally, we call ``.step()`` to initiate gradient descent. The optimizer adjusts each parameter by its gradient stored in ``.grad``.

```python
optim.step() #gradient descent
```

At this point, you have everything you need to train your neural network.
The below sections detail the workings of autograd - feel free to skip them.

--------------

## Differentiation in Autograd

Let's take a look at how ``autograd`` collects gradients. We create two tensors ``a`` and ``b`` with
``requires_grad=True``. This signals to ``autograd`` that every operation on them should be tracked.

```python
import torch

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
```

We create another tensor ``Q`` from ``a`` and ``b``.

\begin{align}Q = 3a^3 - b^2\end{align}

```python
Q = 3*a**3 - b**2
```

Let's assume ``a`` and ``b`` to be parameters of an NN, and ``Q``
to be the error. In NN training, we want gradients of the error
w.r.t. parameters, i.e.

\begin{align}\frac{\partial Q}{\partial a} = 9a^2\end{align}

\begin{align}\frac{\partial Q}{\partial b} = -2b\end{align}

When we call ``.backward()`` on ``Q``, autograd calculates these gradients
and stores them in the respective tensors' ``.grad`` attribute.

We need to explicitly pass a ``gradient`` argument in ``Q.backward()`` because it is a vector.
``gradient`` is a tensor of the same shape as ``Q``, and it represents the
gradient of Q w.r.t. itself, i.e.

\begin{align}\frac{dQ}{dQ} = 1\end{align}

Equivalently, we can also aggregate Q into a scalar and call backward implicitly, like ``Q.sum().backward()``.

```python
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
```

Gradients are now deposited in ``a.grad`` and ``b.grad``

```python
# check if collected gradients are correct
print(9*a**2 == a.grad)
print(-2*b == b.grad)
```

    tensor([True, True])
    tensor([True, True])

## Optional Reading - Vector Calculus using ``autograd``

Mathematically, if you have a vector valued function
$\vec{y}=f(\vec{x})$, then the gradient of $\vec{y}$ with
respect to $\vec{x}$ is a Jacobian matrix $J$:

\begin{align}J
     =
      \left(\begin{array}{cc}
      \frac{\partial \bf{y}}{\partial x_{1}} &
      ... &
      \frac{\partial \bf{y}}{\partial x_{n}}
      \end{array}\right)
     =
     \left(\begin{array}{ccc}
      \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\
      \vdots & \ddots & \vdots\\
      \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}}
      \end{array}\right)\end{align}

Generally speaking, ``torch.autograd`` is an engine for computing
vector-Jacobian product. That is, given any vector $\vec{v}$, compute the product
$J^{T}\cdot \vec{v}$

If $\vec{v}$ happens to be the gradient of a scalar function $l=g\left(\vec{y}\right)$:

\begin{align}\vec{v}
   =
   \left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\end{align}

then by the chain rule, the vector-Jacobian product would be the
gradient of $l$ with respect to $\vec{x}$:

\begin{align}J^{T}\cdot \vec{v}=\left(\begin{array}{ccc}
      \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\
      \vdots & \ddots & \vdots\\
      \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}}
      \end{array}\right)\left(\begin{array}{c}
      \frac{\partial l}{\partial y_{1}}\\
      \vdots\\
      \frac{\partial l}{\partial y_{m}}
      \end{array}\right)=\left(\begin{array}{c}
      \frac{\partial l}{\partial x_{1}}\\
      \vdots\\
      \frac{\partial l}{\partial x_{n}}
      \end{array}\right)\end{align}

This characteristic of vector-Jacobian product is what we use in the above example;
``external_grad`` represents $\vec{v}$.

Computational Graph
~~~~~~~~~~~~~~~~~~~

Conceptually, autograd keeps a record of data (tensors) & all executed
operations (along with the resulting new tensors) in a directed acyclic
graph (DAG) consisting of
`Function <https://pytorch.org/docs/stable/autograd.html#torch.autograd.Function>`__
objects. In this DAG, leaves are the input tensors, roots are the output
tensors. By tracing this graph from roots to leaves, you can
automatically compute the gradients using the chain rule.

In a forward pass, autograd does two things simultaneously:

- run the requested operation to compute a resulting tensor, and
- maintain the operation’s *gradient function* in the DAG.

The backward pass kicks off when ``.backward()`` is called on the DAG
root. ``autograd`` then:

- computes the gradients from each ``.grad_fn``,
- accumulates them in the respective tensor’s ``.grad`` attribute, and
- using the chain rule, propagates all the way to the leaf tensors.

Below is a visual representation of the DAG in our example. In the graph,
the arrows are in the direction of the forward pass. The nodes represent the backward functions
of each operation in the forward pass. The leaf nodes in blue represent our leaf tensors ``a`` and ``b``.

.. figure:: /_static/img/dag_autograd.png

Note

**DAGs are dynamic in PyTorch**
  An important thing to note is that the graph is recreated from scratch; after each
  ``.backward()`` call, autograd starts populating a new graph. This is
  exactly what allows you to use control flow statements in your model;
  you can change the shape, size and operations at every iteration if
  needed.

Exclusion from the DAG
^^^^^^^^^^^^^^^^^^^^^^

``torch.autograd`` tracks operations on all tensors which have their
``requires_grad`` flag set to ``True``. For tensors that don’t require
gradients, setting this attribute to ``False`` excludes it from the
gradient computation DAG.

The output tensor of an operation will require gradients even if only a
single input tensor has ``requires_grad=True``.

```python
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)

a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")
```

    Does `a` require gradients? : False
    Does `b` require gradients?: True

In a NN, parameters that don't compute gradients are usually called **frozen parameters**.
It is useful to "freeze" part of your model if you know in advance that you won't need the gradients of those parameters
(this offers some performance benefits by reducing autograd computations).

Another common usecase where exclusion from the DAG is important is for
`finetuning a pretrained network <https://pytorch.org/tutorials/beginner/finetuning_torchvision_models_tutorial.html>`__

In finetuning, we freeze most of the model and typically only modify the classifier layers to make predictions on new labels.
Let's walk through a small example to demonstrate this. As before, we load a pretrained resnet18 model, and freeze all the parameters.

```python
from torch import nn, optim

model = torchvision.models.resnet18(pretrained=True)

# Freeze all the parameters in the network
for param in model.parameters():
    param.requires_grad = False
```

Let's say we want to finetune the model on a new dataset with 10 labels.
In resnet, the classifier is the last linear layer ``model.fc``.
We can simply replace it with a new linear layer (unfrozen by default)
that acts as our classifier.

```python
model.fc = nn.Linear(512, 10)
```

Now all parameters in the model, except the parameters of ``model.fc``, are frozen.
The only parameters that compute gradients are the weights and bias of ``model.fc``.

```python
# Optimize only the classifier
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
```

Notice although we register all the parameters in the optimizer,
the only parameters that are computing gradients (and hence updated in gradient descent)
are the weights and bias of the classifier.

The same exclusionary functionality is available as a context manager in
`torch.no_grad() <https://pytorch.org/docs/stable/generated/torch.no_grad.html>`__

--------------

Further readings:
~~~~~~~~~~~~~~~~~~~

-  `In-place operations & Multithreaded Autograd <https://pytorch.org/docs/stable/notes/autograd.html>`__
-  `Example implementation of reverse-mode autodiff <https://colab.research.google.com/drive/1VpeE6UvEPRz9HmsHh1KS0XxXjYu533EC>`__