\title{Floating Point (Q) format and Floating Point Rounding in myHDL}
\author{Steven K Armour}
\maketitle

# Referances
https://timetoexplore.net/blog/fixed-point-numbers-in-verilog

```python
from myhdl import *
from myhdlpeek import Peeker
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sympy import *
init_printing()

from bitstring import BitArray

#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information myhdl, myhdlpeek, numpy, pandas, matplotlib, sympy, bitstring
```

Software	Version

Python	3.6.5 64bit [GCC 7.2.0]

IPython	6.4.0

OS	Linux 4.15.0 30 generic x86_64 with debian buster sid

myhdl	0.10

myhdlpeek	0.0.7

numpy	1.14.3

pandas	0.23.0

matplotlib	2.2.2

sympy	1.1.1

bitstring	3.1.5

Sat Aug 25 17:54:05 2018 MDT

```python
#helper  functions to read in the .v and .vhd generated files into python
def VerilogTextReader(loc, printresult=True):
    with open(f'{loc}.v', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***Verilog modual from {loc}.v***\n\n', VerilogText)
    return VerilogText

def VHDLTextReader(loc, printresult=True):
    with open(f'{loc}.vhd', 'r') as vText:
        VerilogText=vText.read()
    if printresult:
        print(f'***VHDL modual from {loc}.vhd***\n\n', VerilogText)
    return VerilogText
```

```python
#4 bit int, 4 bit float
Q=(4,4)
Qlen=Q[0]+Q[1]
Qscale=2**(Q[1]); Qscale
```

# Postive Addition

```python
a=3.6250; b=4.0625
c=a+b; c
```

```python
aQ=int(a*Qscale); bQ=int(b*Qscale)
f'aQ:{aQ}; bA:{bQ}'
```

    'aQ:58; bA:65'

```python
aQBV=intbv(aQ)[Qlen:]; bQBV=intbv(bQ)[Qlen:]
f'aQBV: {bin(aQBV, Qlen)}; bQBV: {bin(bQBV, Qlen)}'
```

    'aQBV: 00111010; bQBV: 01000001'

```python
cQ=aQBV+bQBV; cQ
```

```python
c==cQ/Qscale
```

    True

```python
class AddPosTVGen():
    """
    Class to generate postive random numbers to be Qed for testing
    """
    def __init__(self, Q, N):
        """
        Take in arguments and create output holds
        Args:
            Q(tuple): Q notation tuple where Q[0] is int bit len and Q[1] is
            dec bit len
            N(int): number of values to generate
        """
        self.Q=Q; self.N=N
        self.Qlen=self.Q[0]+self.Q[1]; self.Qmax=2**self.Qlen
        self.Qscale=2**self.Q[1]

        self.aTV=np.zeros(0); self.aTVQ=np.zeros(0)
        self.bTV=np.zeros(0); self.bTVQ=np.zeros(0)
        self.cK=np.zeros(0); self.cKQ=np.zeros(0)

    def Genrator(self):
        """
        Random Number genrator in floating point and supsequent Qed version
        """
        self.V1=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        #needed to force np.random to generate a differint random num
        np.random.seed(np.random.randint(self.Qmax))

        self.V2=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        self.V1Q=(self.V1*self.Qscale).astype(int)
        self.V2Q=(self.V2*self.Qscale).astype(int)

    def GenratorCheckAndAdd(self):
        """
        Cheacks if the sum of the two randome numbers generated are going to break the Qmax
        if they do dont append to retrun holds
        """
        self.V1pV2=(self.V1+self.V2).round(decimals=self.Q[1])
        self.V1pV2Q=(self.V1pV2*self.Qscale).astype(int)
        if (self.V1Q+self.V2Q)<self.Qmax:
            self.aTV=np.append(self.aTV, self.V1); self.aTVQ=np.append(self.aTVQ, self.V1Q).astype(int)
            self.bTV=np.append(self.bTV, self.V1); self.bTVQ=np.append(self.bTVQ, self.V1Q).astype(int)
            self.cK=np.append(self.cK, self.V1pV2); self.cKQ=np.append(self.cKQ, self.V1pV2Q).astype(int)

    def MakeTVs(self):
        """
        Automates the generating, testing and appending to make the TVs

        Returns:
            self.aTV(np.array): floating point numbers for a
            self.aTVQ(np.array): fixed point Qed from self.aTV
            self.bTV(np.array): floating point numbers for b
            self.bTVQ(np.array): fixed point Qed from self.bTV
            self.cK(np.array): known floating point rounded sum of self.aTV, self.bTV
            self.cKQ(np.array): known fixed point Qed from self.cK

        """
        while len(self.aTV)<=self.N:
            self.Genrator()
            self.GenratorCheckAndAdd()
        #print('Done')
```

```python
@block
def AdderBehaverial(a, b, c):

    @always_comb
    def logic():
        c.next=a+b

    return instances()
```

```python
Peeker.clear()
a=Signal(intbv(0)[Qlen:]); Peeker(a, 'a')
b=Signal(intbv(0)[Qlen:]); Peeker(b, 'b')
c=Signal(intbv(0)[Qlen:]); Peeker(c, 'c')

TVG=AddPosTVGen(Q, 100); TVG.MakeTVs()
aTV=TVG.aTV; aTVQ=TVG.aTVQ
bTV=TVG.bTV; bTVQ=TVG.bTVQ
cKTV=TVG.cK; cKTVQ=TVG.cKQ

DUT=AdderBehaverial(a, b, c)

def Adder_TB():

    @instance
    def simules():
        for i in range(len(aTVQ)):
            a.next=int(aTVQ[i])
            b.next=int(bTVQ[i])

            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, Adder_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
PAOData=Peeker.to_dataframe()
#load in the source floating values
PAOData['aTV']=aTV; PAOData['bTV']=bTV
#get the predicted floating Point Sum
PAOData['aTV+bTV']=aTV+bTV
#get the predicted fixed point sum
PAOData['aQ+bQ']=aTVQ+bTVQ
#reorder
PAOData=PAOData[['a', 'aTV', 'b', 'bTV', 'aTV+bTV', 'aQ+bQ',  'c']]
#load the sourced Qed sum
PAOData['cKTVQ']=cKTVQ
#de Q the testbench gen sum
PAOData['cdQ']=PAOData['c']/Qscale
#load the sourced floting sum
PAOData['cKTV']=cKTV
PAOData
```

      a
      aTV
      b
      bTV
      aTV+bTV
      aQ+bQ
      c
      cKTVQ
      cdQ
      cKTV

      0
      33
      2.0671
      33
      2.0671
      4.1342
      66
      66
      76
      4.125
      4.7942

      1
      25
      1.5798
      25
      1.5798
      3.1596
      50
      50
      50
      3.125
      3.1322

      2
      42
      2.6264
      42
      2.6264
      5.2528
      84
      84
      97
      5.250
      6.0666

      3
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      4
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      5
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      6
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      7
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      8
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      9
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      10
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      11
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      12
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      13
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      14
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      15
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      16
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      17
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      18
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      19
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      20
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      21
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      22
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      23
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      24
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      25
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      26
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      27
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      28
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      29
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      71
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      72
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      73
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      74
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      75
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      76
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      77
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      78
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      79
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      80
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      81
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      82
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      83
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      84
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      85
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      86
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      87
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      88
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      89
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      90
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      91
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      92
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      93
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      94
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      95
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      96
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      97
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      98
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

      99
      20
      1.2822
      20
      1.2822
      2.5644
      40
      40
      49
      2.500
      3.1142

      100
      18
      1.1643
      18
      1.1643
      2.3286
      36
      36
      228
      2.250
      14.2690

101 rows × 10 columns

```python
#dataframe of error measures
PAODataErr=pd.DataFrame()
PAODataErr['aQ+bQ_c']=np.abs(PAOData['aQ+bQ']-PAOData['c'])
PAODataErr['c_cKTVQ']=np.abs(PAOData['c']-PAOData['cKTVQ'])
PAODataErr['cdQ_cKTV']=np.abs(PAOData['cdQ']-PAOData['cKTV'])
PAODataErr['c_cKTVQ__cdQ_cKTV']=np.abs((PAODataErr['c_cKTVQ']/ Qscale)- PAODataErr['cdQ_cKTV'])
PAODataErr
```

      aQ+bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      0
      0
      10
      0.6692
      0.0442

      1
      0
      0
      0.0072
      0.0072

      2
      0
      13
      0.8166
      0.0041

      3
      0
      9
      0.6142
      0.0517

      4
      0
      192
      12.0190
      0.0190

      5
      0
      9
      0.6142
      0.0517

      6
      0
      192
      12.0190
      0.0190

      7
      0
      9
      0.6142
      0.0517

      8
      0
      192
      12.0190
      0.0190

      9
      0
      9
      0.6142
      0.0517

      10
      0
      192
      12.0190
      0.0190

      11
      0
      9
      0.6142
      0.0517

      12
      0
      192
      12.0190
      0.0190

      13
      0
      9
      0.6142
      0.0517

      14
      0
      192
      12.0190
      0.0190

      15
      0
      9
      0.6142
      0.0517

      16
      0
      192
      12.0190
      0.0190

      17
      0
      9
      0.6142
      0.0517

      18
      0
      192
      12.0190
      0.0190

      19
      0
      9
      0.6142
      0.0517

      20
      0
      192
      12.0190
      0.0190

      21
      0
      9
      0.6142
      0.0517

      22
      0
      192
      12.0190
      0.0190

      23
      0
      9
      0.6142
      0.0517

      24
      0
      192
      12.0190
      0.0190

      25
      0
      9
      0.6142
      0.0517

      26
      0
      192
      12.0190
      0.0190

      27
      0
      9
      0.6142
      0.0517

      28
      0
      192
      12.0190
      0.0190

      29
      0
      9
      0.6142
      0.0517

      ...
      ...
      ...
      ...
      ...

      71
      0
      9
      0.6142
      0.0517

      72
      0
      192
      12.0190
      0.0190

      73
      0
      9
      0.6142
      0.0517

      74
      0
      192
      12.0190
      0.0190

      75
      0
      9
      0.6142
      0.0517

      76
      0
      192
      12.0190
      0.0190

      77
      0
      9
      0.6142
      0.0517

      78
      0
      192
      12.0190
      0.0190

      79
      0
      9
      0.6142
      0.0517

      80
      0
      192
      12.0190
      0.0190

      81
      0
      9
      0.6142
      0.0517

      82
      0
      192
      12.0190
      0.0190

      83
      0
      9
      0.6142
      0.0517

      84
      0
      192
      12.0190
      0.0190

      85
      0
      9
      0.6142
      0.0517

      86
      0
      192
      12.0190
      0.0190

      87
      0
      9
      0.6142
      0.0517

      88
      0
      192
      12.0190
      0.0190

      89
      0
      9
      0.6142
      0.0517

      90
      0
      192
      12.0190
      0.0190

      91
      0
      9
      0.6142
      0.0517

      92
      0
      192
      12.0190
      0.0190

      93
      0
      9
      0.6142
      0.0517

      94
      0
      192
      12.0190
      0.0190

      95
      0
      9
      0.6142
      0.0517

      96
      0
      192
      12.0190
      0.0190

      97
      0
      9
      0.6142
      0.0517

      98
      0
      192
      12.0190
      0.0190

      99
      0
      9
      0.6142
      0.0517

      100
      0
      192
      12.0190
      0.0190

101 rows × 4 columns

```python
PAODataErr.describe()
```

      aQ+bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      count
      101.0
      101.000000
      101.000000
      101.000000

      mean
      0.0
      97.742574
      6.143760
      0.034850

      std
      0.0
      91.959736
      5.732047
      0.016739

      min
      0.0
      0.000000
      0.007200
      0.004100

      25%
      0.0
      9.000000
      0.614200
      0.019000

      50%
      0.0
      10.000000
      0.669200
      0.019000

      75%
      0.0
      192.000000
      12.019000
      0.051700

      max
      0.0
      192.000000
      12.019000
      0.051700

```python
DUT.convert()
VerilogTextReader('AdderBehaverial');
```

    ***Verilog modual from AdderBehaverial.v***

     // File: AdderBehaverial.v
    // Generated by MyHDL 0.10
    // Date: Sat Aug 25 17:54:15 2018

    `timescale 1ns/10ps

    module AdderBehaverial (
        a,
        b,
        c
    );

    input [7:0] a;
    input [7:0] b;
    output [7:0] c;
    wire [7:0] c;

    assign c = (a + b);

    endmodule

# Negative Values

```python
a=3.6250; aQ=int(a*Qscale);a, aQ
```

```python
b=-1.5; bMagQ=int(abs(b)*Qscale); bMagQ
```

```python
bMagQBV=bin(bMagQ, Qlen); bMagQBV
```

    '00011000'

```python
bQBVComp="".join([str(int(not(int(i)))) for i in bMagQBV]); bQBVComp
```

    '11100111'

```python
bQComp=int(bQBVComp, 2); bQComp
```

```python
bQ2Comp=bQComp+1; bQ2Comp
```

```python
bQBV2Comp=bin(bQ2Comp, 2); bQBV2Comp
```

    '11101000'

```python
(BitArray(bin=bQBV2Comp).int)/ Qscale
```

```python
aQBV=intbv(aQ)[Qlen:].signed()
aQBV, bin(aQBV, Qlen), aQBV.min, aQBV.max
```

    (intbv(58), '00111010', -128, 128)

```python
bQBV=intbv(int(b*Qscale))[Qlen:].signed()
bQBV, bin(bQBV, Qlen)
```

    (intbv(-24), '11101000')

```python
bQBV2Comp==bin(bQBV, Qlen)
```

    True

```python
a+b
```

```python
c=aQBV+bQBV; c, c/Qscale
```

```python
class AddPosNegTVGen():
    """
    Class to generate postive random numbers to be Qed for testing
    """
    def __init__(self, Q, N):
        """
        Take in arguments and create output holds
        Args:
            Q(tuple): Q notation tuple where Q[0] is int bit len and Q[1] is
            dec bit len
            N(int): number of values to generate
        """
        self.Q=Q; self.N=N
        self.Qlen=self.Q[0]+self.Q[1]
        self.Qmin=-(2**(Qlen-1)); self.Qmax=2**(self.Qlen-1) -1
        self.Qscale=2**self.Q[1]

        self.aTV=np.zeros(0); self.aTVQ=np.zeros(0)
        self.bTV=np.zeros(0); self.bTVQ=np.zeros(0)
        self.cK=np.zeros(0); self.cKQ=np.zeros(0)

    def Genrator(self):
        """
        Random Number genrator in floating point and supsequent Qed version
        """
        self.V1=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        #needed to force np.random to generate a differint random num
        np.random.seed(np.random.randint(self.Qmax))

        self.V2=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        #needed to force np.random to generate a differint random num
        np.random.seed(np.random.randint(self.Qmax))

        self.Sign=np.random.randint(2)
        if self.Sign==1:
            self.V2=-self.V2
        self.V1Q=(self.V1*self.Qscale).astype(int)
        self.V2Q=(self.V2*self.Qscale).astype(int)

    def GenratorCheckAndAdd(self):
        """
        Cheacks if the sum of the two randome numbers generated are going to break the Qmax
        if they do dont append to retrun holds
        """
        self.V1pV2=(self.V1+self.V2).round(decimals=self.Q[1])
        self.V1pV2Q=(self.V1pV2*self.Qscale).astype(int)

        check=self.V1Q+self.V2Q
        if self.V1Q>self.Qmin and self.V1Q<self.Qmax:
            if self.V2Q>self.Qmin and self.V2Q<self.Qmax:
                if check>self.Qmin and check<self.Qmax:
                    self.aTV=np.append(self.aTV, self.V1); self.aTVQ=np.append(self.aTVQ, self.V1Q).astype(int)
                    self.bTV=np.append(self.bTV, self.V2); self.bTVQ=np.append(self.bTVQ, self.V1Q).astype(int)
                    self.cK=np.append(self.cK, self.V1pV2); self.cKQ=np.append(self.cKQ, self.V1pV2Q).astype(int)

    def MakeTVs(self):
        """
        Automates the generating, testing and appending to make the TVs

        Returns:
            self.aTV(np.array): floating point numbers for a
            self.aTVQ(np.array): fixed point Qed from self.aTV
            self.bTV(np.array): floating point numbers for b
            self.bTVQ(np.array): fixed point Qed from self.bTV
            self.cK(np.array): known floating point rounded sum of self.aTV, self.bTV
            self.cKQ(np.array): known fixed point Qed from self.cK

        """
        while len(self.aTV)<=self.N:
            self.Genrator()
            self.GenratorCheckAndAdd()
        #print('Done')

```

```python
Peeker.clear()
a=Signal(intbv(0)[Qlen:].signed()); Peeker(a, 'a')
b=Signal(intbv(0)[Qlen:].signed()); Peeker(b, 'b')
c=Signal(intbv(0)[Qlen:].signed()); Peeker(c, 'c')

TVG=AddPosNegTVGen(Q, 100); TVG.MakeTVs()
aTV=TVG.aTV; aTVQ=TVG.aTVQ
bTV=TVG.bTV; bTVQ=TVG.bTVQ
cKTV=TVG.cK; cKTVQ=TVG.cKQ

DUT=AdderBehaverial(a, b, c)

def Adder_TB():

    @instance
    def simules():
        for i in range(len(aTVQ)):
            a.next=int(aTVQ[i])
            b.next=int(bTVQ[i])

            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, Adder_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
AOData=Peeker.to_dataframe()
#load in the source floating values
AOData['aTV']=aTV; AOData['bTV']=bTV
#get the predicted floating Point Sum
AOData['aTV+bTV']=aTV+bTV
#get the predicted fixed point sum
AOData['aQ+bQ']=aTVQ+bTVQ
#reorder
AOData=AOData[['a', 'aTV', 'b', 'bTV', 'aTV+bTV', 'aQ+bQ',  'c']]
#load the sourced Qed sum
AOData['cKTVQ']=cKTVQ
#de Q the testbench gen sum
AOData['cdQ']=AOData['c']/Qscale
#load the sourced floting sum
AOData['cKTV']=cKTV
AOData
```

      a
      aTV
      b
      bTV
      aTV+bTV
      aQ+bQ
      c
      cKTVQ
      cdQ
      cKTV

      0
      20
      1.2822
      20
      1.8320
      3.1142
      40
      40
      49
      2.500
      3.1142

      1
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      2
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      3
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      4
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

      5
      49
      3.0806
      49
      -1.8052
      1.2754
      98
      98
      20
      6.125
      1.2754

      6
      23
      1.4397
      23
      -1.1493
      0.2904
      46
      46
      4
      2.875
      0.2904

      7
      19
      1.2206
      19
      -3.4958
      -2.2752
      38
      38
      -36
      2.375
      -2.2752

      8
      48
      3.0613
      48
      3.3755
      6.4368
      96
      96
      102
      6.000
      6.4368

      9
      52
      3.2753
      52
      1.2965
      4.5718
      104
      104
      73
      6.500
      4.5718

      10
      23
      1.4900
      23
      -2.3142
      -0.8242
      46
      46
      -13
      2.875
      -0.8242

      11
      37
      2.3231
      37
      -1.3656
      0.9575
      74
      74
      15
      4.625
      0.9575

      12
      19
      1.2145
      19
      1.2858
      2.5003
      38
      38
      40
      2.375
      2.5003

      13
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      14
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      15
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      16
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

      17
      49
      3.0806
      49
      -1.8052
      1.2754
      98
      98
      20
      6.125
      1.2754

      18
      23
      1.4397
      23
      -1.1493
      0.2904
      46
      46
      4
      2.875
      0.2904

      19
      19
      1.2206
      19
      -3.4958
      -2.2752
      38
      38
      -36
      2.375
      -2.2752

      20
      48
      3.0613
      48
      3.3755
      6.4368
      96
      96
      102
      6.000
      6.4368

      21
      52
      3.2753
      52
      1.2965
      4.5718
      104
      104
      73
      6.500
      4.5718

      22
      23
      1.4900
      23
      -2.3142
      -0.8242
      46
      46
      -13
      2.875
      -0.8242

      23
      37
      2.3231
      37
      -1.3656
      0.9575
      74
      74
      15
      4.625
      0.9575

      24
      19
      1.2145
      19
      1.2858
      2.5003
      38
      38
      40
      2.375
      2.5003

      25
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      26
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      27
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      28
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

      29
      49
      3.0806
      49
      -1.8052
      1.2754
      98
      98
      20
      6.125
      1.2754

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      71
      37
      2.3231
      37
      -1.3656
      0.9575
      74
      74
      15
      4.625
      0.9575

      72
      19
      1.2145
      19
      1.2858
      2.5003
      38
      38
      40
      2.375
      2.5003

      73
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      74
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      75
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      76
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

      77
      49
      3.0806
      49
      -1.8052
      1.2754
      98
      98
      20
      6.125
      1.2754

      78
      23
      1.4397
      23
      -1.1493
      0.2904
      46
      46
      4
      2.875
      0.2904

      79
      19
      1.2206
      19
      -3.4958
      -2.2752
      38
      38
      -36
      2.375
      -2.2752

      80
      48
      3.0613
      48
      3.3755
      6.4368
      96
      96
      102
      6.000
      6.4368

      81
      52
      3.2753
      52
      1.2965
      4.5718
      104
      104
      73
      6.500
      4.5718

      82
      23
      1.4900
      23
      -2.3142
      -0.8242
      46
      46
      -13
      2.875
      -0.8242

      83
      37
      2.3231
      37
      -1.3656
      0.9575
      74
      74
      15
      4.625
      0.9575

      84
      19
      1.2145
      19
      1.2858
      2.5003
      38
      38
      40
      2.375
      2.5003

      85
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      86
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      87
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      88
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

      89
      49
      3.0806
      49
      -1.8052
      1.2754
      98
      98
      20
      6.125
      1.2754

      90
      23
      1.4397
      23
      -1.1493
      0.2904
      46
      46
      4
      2.875
      0.2904

      91
      19
      1.2206
      19
      -3.4958
      -2.2752
      38
      38
      -36
      2.375
      -2.2752

      92
      48
      3.0613
      48
      3.3755
      6.4368
      96
      96
      102
      6.000
      6.4368

      93
      52
      3.2753
      52
      1.2965
      4.5718
      104
      104
      73
      6.500
      4.5718

      94
      23
      1.4900
      23
      -2.3142
      -0.8242
      46
      46
      -13
      2.875
      -0.8242

      95
      37
      2.3231
      37
      -1.3656
      0.9575
      74
      74
      15
      4.625
      0.9575

      96
      19
      1.2145
      19
      1.2858
      2.5003
      38
      38
      40
      2.375
      2.5003

      97
      38
      2.3776
      38
      -1.5560
      0.8216
      76
      76
      13
      4.750
      0.8216

      98
      62
      3.8753
      62
      2.6655
      6.5408
      124
      124
      104
      7.750
      6.5408

      99
      22
      1.4366
      22
      -1.1781
      0.2585
      44
      44
      4
      2.750
      0.2585

      100
      35
      2.2410
      35
      1.9735
      4.2145
      70
      70
      67
      4.375
      4.2145

101 rows × 10 columns

```python
#dataframe of error measures
AODataErr=pd.DataFrame()
AODataErr['aQ+bQ_c']=np.abs(AOData['aQ+bQ']-AOData['c'])
AODataErr['c_cKTVQ']=np.abs(AOData['c']-AOData['cKTVQ'])
AODataErr['cdQ_cKTV']=np.abs(AOData['cdQ']-AOData['cKTV'])
AODataErr['c_cKTVQ__cdQ_cKTV']=np.abs((AODataErr['c_cKTVQ']/ Qscale)- AODataErr['cdQ_cKTV'])
AODataErr
```

      aQ+bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      0
      0
      9
      0.6142
      0.0517

      1
      0
      63
      3.9284
      0.0091

      2
      0
      20
      1.2092
      0.0408

      3
      0
      40
      2.4915
      0.0085

      4
      0
      3
      0.1605
      0.0270

      5
      0
      78
      4.8496
      0.0254

      6
      0
      42
      2.5846
      0.0404

      7
      0
      74
      4.6502
      0.0252

      8
      0
      6
      0.4368
      0.0618

      9
      0
      31
      1.9282
      0.0093

      10
      0
      59
      3.6992
      0.0117

      11
      0
      59
      3.6675
      0.0200

      12
      0
      2
      0.1253
      0.0003

      13
      0
      63
      3.9284
      0.0091

      14
      0
      20
      1.2092
      0.0408

      15
      0
      40
      2.4915
      0.0085

      16
      0
      3
      0.1605
      0.0270

      17
      0
      78
      4.8496
      0.0254

      18
      0
      42
      2.5846
      0.0404

      19
      0
      74
      4.6502
      0.0252

      20
      0
      6
      0.4368
      0.0618

      21
      0
      31
      1.9282
      0.0093

      22
      0
      59
      3.6992
      0.0117

      23
      0
      59
      3.6675
      0.0200

      24
      0
      2
      0.1253
      0.0003

      25
      0
      63
      3.9284
      0.0091

      26
      0
      20
      1.2092
      0.0408

      27
      0
      40
      2.4915
      0.0085

      28
      0
      3
      0.1605
      0.0270

      29
      0
      78
      4.8496
      0.0254

      ...
      ...
      ...
      ...
      ...

      71
      0
      59
      3.6675
      0.0200

      72
      0
      2
      0.1253
      0.0003

      73
      0
      63
      3.9284
      0.0091

      74
      0
      20
      1.2092
      0.0408

      75
      0
      40
      2.4915
      0.0085

      76
      0
      3
      0.1605
      0.0270

      77
      0
      78
      4.8496
      0.0254

      78
      0
      42
      2.5846
      0.0404

      79
      0
      74
      4.6502
      0.0252

      80
      0
      6
      0.4368
      0.0618

      81
      0
      31
      1.9282
      0.0093

      82
      0
      59
      3.6992
      0.0117

      83
      0
      59
      3.6675
      0.0200

      84
      0
      2
      0.1253
      0.0003

      85
      0
      63
      3.9284
      0.0091

      86
      0
      20
      1.2092
      0.0408

      87
      0
      40
      2.4915
      0.0085

      88
      0
      3
      0.1605
      0.0270

      89
      0
      78
      4.8496
      0.0254

      90
      0
      42
      2.5846
      0.0404

      91
      0
      74
      4.6502
      0.0252

      92
      0
      6
      0.4368
      0.0618

      93
      0
      31
      1.9282
      0.0093

      94
      0
      59
      3.6992
      0.0117

      95
      0
      59
      3.6675
      0.0200

      96
      0
      2
      0.1253
      0.0003

      97
      0
      63
      3.9284
      0.0091

      98
      0
      20
      1.2092
      0.0408

      99
      0
      40
      2.4915
      0.0085

      100
      0
      3
      0.1605
      0.0270

101 rows × 4 columns

```python
AODataErr.describe()
```

      aQ+bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      count
      101.0
      101.000000
      101.000000
      101.000000

      mean
      0.0
      39.118812
      2.438137
      0.023496

      std
      0.0
      26.385711
      1.646784
      0.016939

      min
      0.0
      2.000000
      0.125300
      0.000300

      25%
      0.0
      9.000000
      0.614200
      0.009100

      50%
      0.0
      40.000000
      2.491500
      0.025200

      75%
      0.0
      59.000000
      3.699200
      0.040400

      max
      0.0
      78.000000
      4.849600
      0.061800

```python
DUT.convert()
VerilogTextReader('AdderBehaverial');
```

    ***Verilog modual from AdderBehaverial.v***

     // File: AdderBehaverial.v
    // Generated by MyHDL 0.10
    // Date: Sat Aug 25 17:54:24 2018

    `timescale 1ns/10ps

    module AdderBehaverial (
        a,
        b,
        c
    );

    input signed [7:0] a;
    input signed [7:0] b;
    output signed [7:0] c;
    wire signed [7:0] c;

    assign c = (a + b);

    endmodule

# Multiblication

```python
#Q4.4 *Q4.4 -> Q8.8
Q2=(Q[0]*2, Q[1]*2)
Q2len=Q2[0]+Q2[1]
Q2scale=2**(Q2[1]); Q2scale
```

```python
a=3.2500; aQ=int(a*Qscale)
b=-2.065; bQ=int(b*Qscale)
aQ, bQ
bin(aQ, Qlen), bin(bQ, Qlen)
```

    ('00110100', '11011111')

```python
ab=a*b; ab
abQ=int(ab*Qscale); abQ
abdQ=abQ/ Qscale; abdQ, ab
```

```python
aQBV=intbv(aQ)[Qlen:].signed(); bQBV=intbv(bQ)[Qlen:].signed()
f'aQBV: {bin(aQBV, Qlen)}; bQBV: {bin(bQBV, Qlen)}'
```

    'aQBV: 00110100; bQBV: 11011111'

```python
abQ=aQBV*bQBV; abQ
```

```python
abdQ=abQ/ Qscale; abdQ, ab
```

```python
abdQ=abQ/ Q2scale; abdQ,ab
```

```python
class MultPosNegTVGen():
    """
    Class to generate postive random numbers to be Qed for testing
    """
    def __init__(self, Q, N):
        """
        Take in arguments and create output holds
        Args:
            Q(tuple): Q notation tuple where Q[0] is int bit len and Q[1] is
            dec bit len
            N(int): number of values to generate
        """
        self.Q=Q; self.N=N
        self.Qlen=self.Q[0]+self.Q[1]
        self.Qmin=-(2**(self.Qlen-1)); self.Qmax=2**(self.Qlen-1) -1
        self.Qscale=2**self.Q[1]

        #Q4.4 *Q4.4 -> Q8.8
        self.Q2=(self.Q[0]*2, self.Q[1]*2)
        self.Q2len=self.Q2[0]+self.Q2[1]
        self.Q2min=-(2**(self.Q2len-1)); self.Q2max=2**(self.Q2len-1) -1
        self.Q2scale=2**(Q2[1])

        self.aTV=np.zeros(0); self.aTVQ=np.zeros(0)
        self.bTV=np.zeros(0); self.bTVQ=np.zeros(0)
        self.cK=np.zeros(0); self.cKQ=np.zeros(0)

    def Genrator(self):
        """
        Random Number genrator in floating point and supsequent Qed version
        """
        self.V1=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        #needed to force np.random to generate a differint random num
        np.random.seed(np.random.randint(self.Qmax))

        self.V2=np.array((1/np.random.ranf())).round(decimals=self.Q[1])

        #needed to force np.random to generate a differint random num
        np.random.seed(np.random.randint(self.Qmax))

        self.Sign=np.random.randint(2)
        if self.Sign==1:
            self.V2=-self.V2
        self.V1Q=(self.V1*self.Qscale).astype(int)
        self.V2Q=(self.V2*self.Qscale).astype(int)

    def GenratorCheckAndMul(self):
        """
        Cheacks if the sum of the two randome numbers generated are going to break the Qmax
        if they do dont append to retrun holds
        """
        self.V1tV2=(self.V1*self.V2).round(decimals=self.Q2[1])
        self.V1tV2Q=(self.V1tV2*self.Q2scale).astype(int)
        check=self.V1Q*self.V2Q
        if self.V1Q>self.Qmin and self.V1Q<self.Qmax:
            if self.V2Q>self.Qmin and self.V2Q<self.Qmax:
                if check>self.Q2min and check<self.Q2max:
                    self.aTV=np.append(self.aTV, self.V1); self.aTVQ=np.append(self.aTVQ, self.V1Q).astype(int)
                    self.bTV=np.append(self.bTV, self.V2); self.bTVQ=np.append(self.bTVQ, self.V2Q).astype(int)
                    self.cK=np.append(self.cK, self.V1tV2); self.cKQ=np.append(self.cKQ, self.V1tV2Q).astype(int)

    def MakeTVs(self):
        """
        Automates the generating, testing and appending to make the TVs

        Returns:
            self.aTV(np.array): floating point numbers for a
            self.aTVQ(np.array): fixed point Qed from self.aTV
            self.bTV(np.array): floating point numbers for b
            self.bTVQ(np.array): fixed point Qed from self.bTV
            self.cK(np.array): known floating point rounded sum of self.aTV, self.bTV
            self.cKQ(np.array): known fixed point Qed from self.cK

        """
        while len(self.aTV)<=self.N:
            self.Genrator()
            self.GenratorCheckAndMul()
        #print('Done')

```

```python
@block
def MultiBehaverial(a, b, c):

    @always_comb
    def logic():
        c.next=a*b

    return instances()
```

```python
Peeker.clear()
a=Signal(intbv(0)[Qlen:].signed()); Peeker(a, 'a')
b=Signal(intbv(0)[Qlen:].signed()); Peeker(b, 'b')
c=Signal(intbv(0)[Q2len:].signed()); Peeker(c, 'c')

TVG=MultPosNegTVGen(Q, 100); TVG.MakeTVs()
aTV=TVG.aTV; aTVQ=TVG.aTVQ
bTV=TVG.bTV; bTVQ=TVG.bTVQ
cKTV=TVG.cK; cKTVQ=TVG.cKQ

DUT=MultiBehaverial(a, b, c)

def Multi_TB():

    @instance
    def simules():
        for i in range(len(aTVQ)):

            a.next=int(aTVQ[i])
            b.next=int(bTVQ[i])

            yield delay(1)

        raise StopSimulation()

    return instances()

sim=Simulation(DUT, Multi_TB(), *Peeker.instances()).run()
```

```python
Peeker.to_wavedrom()
```

```python
MultiData=Peeker.to_dataframe()
#load in the source floating values
MultiData['aTV']=aTV; MultiData['bTV']=bTV
#get the predicted floating Point Sum
MultiData['aTV*bTV']=aTV*bTV
#get the predicted fixed point sum
MultiData['aQ*bQ']=aTVQ*bTVQ
#reorder
MultiData=MultiData[['a', 'aTV', 'b', 'bTV', 'aTV*bTV', 'aQ*bQ',  'c']]
#load the sourced Qed sum
MultiData['cKTVQ']=cKTVQ
#de Q the testbench gen sum
MultiData['cdQ']=MultiData['c']/Q2scale
#load the sourced floting sum
MultiData['cKTV']=cKTV
MultiData
```

      a
      aTV
      b
      bTV
      aTV*bTV
      aQ*bQ
      c
      cKTVQ
      cdQ
      cKTV

      0
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      1
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      2
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      3
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      4
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

      5
      23
      1.4900
      -37
      -2.3142
      -3.448158
      -851
      -851
      -882
      -3.324219
      -3.448158

      6
      37
      2.3231
      -21
      -1.3656
      -3.172425
      -777
      -777
      -812
      -3.035156
      -3.172425

      7
      19
      1.2145
      20
      1.2858
      1.561604
      380
      380
      399
      1.484375
      1.561604

      8
      38
      2.3776
      -24
      -1.5560
      -3.699546
      -912
      -912
      -947
      -3.562500
      -3.699546

      9
      62
      3.8753
      42
      2.6655
      10.329612
      2604
      2604
      2644
      10.171875
      10.329612

      10
      22
      1.4366
      -18
      -1.1781
      -1.692458
      -396
      -396
      -433
      -1.546875
      -1.692458

      11
      35
      2.2410
      31
      1.9735
      4.422614
      1085
      1085
      1132
      4.238281
      4.422613

      12
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      13
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      14
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      15
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      16
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

      17
      23
      1.4900
      -37
      -2.3142
      -3.448158
      -851
      -851
      -882
      -3.324219
      -3.448158

      18
      37
      2.3231
      -21
      -1.3656
      -3.172425
      -777
      -777
      -812
      -3.035156
      -3.172425

      19
      19
      1.2145
      20
      1.2858
      1.561604
      380
      380
      399
      1.484375
      1.561604

      20
      38
      2.3776
      -24
      -1.5560
      -3.699546
      -912
      -912
      -947
      -3.562500
      -3.699546

      21
      62
      3.8753
      42
      2.6655
      10.329612
      2604
      2604
      2644
      10.171875
      10.329612

      22
      22
      1.4366
      -18
      -1.1781
      -1.692458
      -396
      -396
      -433
      -1.546875
      -1.692458

      23
      35
      2.2410
      31
      1.9735
      4.422614
      1085
      1085
      1132
      4.238281
      4.422613

      24
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      25
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      26
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      27
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      28
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

      29
      23
      1.4900
      -37
      -2.3142
      -3.448158
      -851
      -851
      -882
      -3.324219
      -3.448158

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      71
      35
      2.2410
      31
      1.9735
      4.422614
      1085
      1085
      1132
      4.238281
      4.422613

      72
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      73
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      74
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      75
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      76
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

      77
      23
      1.4900
      -37
      -2.3142
      -3.448158
      -851
      -851
      -882
      -3.324219
      -3.448158

      78
      37
      2.3231
      -21
      -1.3656
      -3.172425
      -777
      -777
      -812
      -3.035156
      -3.172425

      79
      19
      1.2145
      20
      1.2858
      1.561604
      380
      380
      399
      1.484375
      1.561604

      80
      38
      2.3776
      -24
      -1.5560
      -3.699546
      -912
      -912
      -947
      -3.562500
      -3.699546

      81
      62
      3.8753
      42
      2.6655
      10.329612
      2604
      2604
      2644
      10.171875
      10.329612

      82
      22
      1.4366
      -18
      -1.1781
      -1.692458
      -396
      -396
      -433
      -1.546875
      -1.692458

      83
      35
      2.2410
      31
      1.9735
      4.422614
      1085
      1085
      1132
      4.238281
      4.422613

      84
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      85
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      86
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      87
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      88
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

      89
      23
      1.4900
      -37
      -2.3142
      -3.448158
      -851
      -851
      -882
      -3.324219
      -3.448158

      90
      37
      2.3231
      -21
      -1.3656
      -3.172425
      -777
      -777
      -812
      -3.035156
      -3.172425

      91
      19
      1.2145
      20
      1.2858
      1.561604
      380
      380
      399
      1.484375
      1.561604

      92
      38
      2.3776
      -24
      -1.5560
      -3.699546
      -912
      -912
      -947
      -3.562500
      -3.699546

      93
      62
      3.8753
      42
      2.6655
      10.329612
      2604
      2604
      2644
      10.171875
      10.329612

      94
      22
      1.4366
      -18
      -1.1781
      -1.692458
      -396
      -396
      -433
      -1.546875
      -1.692458

      95
      35
      2.2410
      31
      1.9735
      4.422614
      1085
      1085
      1132
      4.238281
      4.422613

      96
      49
      3.0806
      -28
      -1.8052
      -5.561099
      -1372
      -1372
      -1423
      -5.359375
      -5.561099

      97
      23
      1.4397
      -18
      -1.1493
      -1.654647
      -414
      -414
      -423
      -1.617188
      -1.654647

      98
      19
      1.2206
      -55
      -3.4958
      -4.266973
      -1045
      -1045
      -1092
      -4.082031
      -4.266973

      99
      48
      3.0613
      54
      3.3755
      10.333418
      2592
      2592
      2645
      10.125000
      10.333418

      100
      52
      3.2753
      20
      1.2965
      4.246426
      1040
      1040
      1087
      4.062500
      4.246426

101 rows × 10 columns

```python
#dataframe of error measures
MultiDataErr=pd.DataFrame()
MultiDataErr['aQ*bQ_c']=np.abs(MultiData['aQ*bQ']-MultiData['c'])
MultiDataErr['c_cKTVQ']=np.abs(MultiData['c']-MultiData['cKTVQ'])
MultiDataErr['cdQ_cKTV']=np.abs(MultiData['cdQ']-MultiData['cKTV'])
MultiDataErr['c_cKTVQ__cdQ_cKTV']=np.abs((MultiDataErr['c_cKTVQ']/ Q2scale)- MultiDataErr['cdQ_cKTV'])
MultiDataErr
```

      aQ*bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      0
      0
      51
      0.201724
      0.002505

      1
      0
      9
      0.037460
      0.002303

      2
      0
      47
      0.184942
      0.001348

      3
      0
      53
      0.208418
      0.001387

      4
      0
      47
      0.183926
      0.000333

      5
      0
      31
      0.123939
      0.002845

      6
      0
      35
      0.137269
      0.000550

      7
      0
      19
      0.077229
      0.003010

      8
      0
      35
      0.137046
      0.000327

      9
      0
      40
      0.157737
      0.001487

      10
      0
      37
      0.145583
      0.001052

      11
      0
      47
      0.184332
      0.000738

      12
      0
      51
      0.201724
      0.002505

      13
      0
      9
      0.037460
      0.002303

      14
      0
      47
      0.184942
      0.001348

      15
      0
      53
      0.208418
      0.001387

      16
      0
      47
      0.183926
      0.000333

      17
      0
      31
      0.123939
      0.002845

      18
      0
      35
      0.137269
      0.000550

      19
      0
      19
      0.077229
      0.003010

      20
      0
      35
      0.137046
      0.000327

      21
      0
      40
      0.157737
      0.001487

      22
      0
      37
      0.145583
      0.001052

      23
      0
      47
      0.184332
      0.000738

      24
      0
      51
      0.201724
      0.002505

      25
      0
      9
      0.037460
      0.002303

      26
      0
      47
      0.184942
      0.001348

      27
      0
      53
      0.208418
      0.001387

      28
      0
      47
      0.183926
      0.000333

      29
      0
      31
      0.123939
      0.002845

      ...
      ...
      ...
      ...
      ...

      71
      0
      47
      0.184332
      0.000738

      72
      0
      51
      0.201724
      0.002505

      73
      0
      9
      0.037460
      0.002303

      74
      0
      47
      0.184942
      0.001348

      75
      0
      53
      0.208418
      0.001387

      76
      0
      47
      0.183926
      0.000333

      77
      0
      31
      0.123939
      0.002845

      78
      0
      35
      0.137269
      0.000550

      79
      0
      19
      0.077229
      0.003010

      80
      0
      35
      0.137046
      0.000327

      81
      0
      40
      0.157737
      0.001487

      82
      0
      37
      0.145583
      0.001052

      83
      0
      47
      0.184332
      0.000738

      84
      0
      51
      0.201724
      0.002505

      85
      0
      9
      0.037460
      0.002303

      86
      0
      47
      0.184942
      0.001348

      87
      0
      53
      0.208418
      0.001387

      88
      0
      47
      0.183926
      0.000333

      89
      0
      31
      0.123939
      0.002845

      90
      0
      35
      0.137269
      0.000550

      91
      0
      19
      0.077229
      0.003010

      92
      0
      35
      0.137046
      0.000327

      93
      0
      40
      0.157737
      0.001487

      94
      0
      37
      0.145583
      0.001052

      95
      0
      47
      0.184332
      0.000738

      96
      0
      51
      0.201724
      0.002505

      97
      0
      9
      0.037460
      0.002303

      98
      0
      47
      0.184942
      0.001348

      99
      0
      53
      0.208418
      0.001387

      100
      0
      47
      0.183926
      0.000333

101 rows × 4 columns

```python
MultiDataErr.describe()
```

      aQ*bQ_c
      c_cKTVQ
      cdQ_cKTV
      c_cKTVQ__cdQ_cKTV

      count
      101.0
      101.000000
      101.000000
      101.000000

      mean
      0.0
      37.772277
      0.149043
      0.001495

      std
      0.0
      12.915015
      0.050054
      0.000920

      min
      0.0
      9.000000
      0.037460
      0.000327

      25%
      0.0
      35.000000
      0.137046
      0.000738

      50%
      0.0
      40.000000
      0.157737
      0.001387

      75%
      0.0
      47.000000
      0.184942
      0.002303

      max
      0.0
      53.000000
      0.208418
      0.003010

```python
DUT.convert()
VerilogTextReader('MultiBehaverial');
```

    ***Verilog modual from MultiBehaverial.v***

     // File: MultiBehaverial.v
    // Generated by MyHDL 0.10
    // Date: Sat Aug 25 17:54:33 2018

    `timescale 1ns/10ps

    module MultiBehaverial (
        a,
        b,
        c
    );

    input signed [7:0] a;
    input signed [7:0] b;
    output signed [15:0] c;
    wire signed [15:0] c;

    assign c = (a * b);

    endmodule

# Trunction (Unsighned)

```python
#Q4.4 *Q4.4 -> Q8.8
Q2=(Q[0]*2, Q[1]*2)
Q2len=Q2[0]+Q2[1]
Q2scale=2**(Q2[1]); Q2scale
```

```python
a=3.2500; aQ=int(a*Qscale)
b=2.0625; bQ=int(b*Qscale)
aQ, bQ
#bin(aQ, Qlen), bin(bQ, Qlen)
```

```python
ab=a*b; ab
abQ=int(ab*Qscale); abQ
abdQ=abQ/ Qscale; abdQ, ab
```

```python
aQBV=intbv(aQ)[Qlen:]; bQBV=intbv(bQ)[Qlen:]
f'aQBV: {bin(aQBV, Qlen)}; bQBV: {bin(bQBV, Qlen)}'
```

    'aQBV: 00110100; bQBV: 00100001'

```python
abQ=aQBV*bQBV; abQ
```

```python
abQBV=intbv(abQ)[Q2len:].signed(); abQBV, bin(abQBV), len(bin(abQBV))
```

    (intbv(1716), '11010110100', 11)

```python
for j in range(Q2[1]):
    Trunc=abQBV[Q2len:j]
    TruncDQ=Trunc/(2**(Q2[1]-j))
    print(bin(Trunc), TruncDQ, np.abs(ab-TruncDQ))
```

    11010110100 6.703125 0.0
    1101011010 6.703125 0.0
    110101101 6.703125 0.0
    11010110 6.6875 0.015625
    1101011 6.6875 0.015625
    110101 6.625 0.078125
    11010 6.5 0.203125
    1101 6.5 0.203125

# Trunction (sighned)

```python
a=3.2500; aQ=int(a*Qscale)
b=-2.0625; bQ=int(b*Qscale)
aQ, bQ
#bin(aQ, Qlen), bin(bQ, Qlen)
```

```python
ab=a*b; ab
abQ=int(ab*Qscale); abQ
abdQ=abQ/ Qscale; abdQ, ab
```

```python
aQBV=intbv(aQ)[Qlen:].signed(); bQBV=intbv(bQ)[Qlen:].signed()
f'aQBV: {bin(aQBV, Qlen)}; bQBV: {bin(bQBV, Qlen)}'
```

    'aQBV: 00110100; bQBV: 11011111'

```python
abQ=aQBV*bQBV; abQ
```

```python
abQBV=intbv(abQ)[Q2len:].signed(); abQBV, bin(abQBV), len(bin(abQBV))
```

    (intbv(-1716), '100101001100', 12)

```python
for j in range(Q2[1]):
    Trunc=abQBV[Q2len:j].signed()
    TruncDQ=Trunc/(2**(Q2[1]-j))
    print(bin(Trunc), TruncDQ, np.abs(ab-TruncDQ))
```

    100101001100 -6.703125 0.0
    10010100110 -6.703125 0.0
    1001010011 -6.703125 0.0
    100101001 -6.71875 0.015625
    10010100 -6.75 0.046875
    1001010 -6.75 0.046875
    100101 -6.75 0.046875
    10010 -7.0 0.296875

```python
for j in range(Q2[1]):
    Trunc=(abQBV>>j).signed()
    TruncDQ=Trunc/(2**(Q2[1]-j))
    print(bin(Trunc), TruncDQ, np.abs(ab-TruncDQ))
```

    100101001100 -6.703125 0.0
    10010100110 -6.703125 0.0
    1001010011 -6.703125 0.0
    100101001 -6.71875 0.015625
    10010100 -6.75 0.046875
    1001010 -6.75 0.046875
    100101 -6.75 0.046875
    10010 -7.0 0.296875

# Round Half Up

```python
a=3.2500; aQ=int(a*Qscale)
b=-2.0625; bQ=int(b*Qscale)
aQ, bQ
bin(aQ, Qlen), bin(bQ, Qlen)
```

    ('00110100', '11011111')

```python
ab=a*b; ab
abQ=int(ab*Qscale); abQ
abdQ=abQ/ Qscale; abdQ, ab
```

```python
aQBV=intbv(aQ)[Qlen:].signed(); bQBV=intbv(bQ)[Qlen:].signed()
f'aQBV: {bin(aQBV, Qlen)}; bQBV: {bin(bQBV, Qlen)}'
```

    'aQBV: 00110100; bQBV: 11011111'

```python
abQ=aQBV*bQBV; abQ
```

```python
abQBV=intbv(abQ)[Q2len:].signed(); abQBV, bin(abQBV), len(bin(abQBV))
```

    (intbv(-1716), '100101001100', 12)

```python
ab, floor(ab+.5), -ceiling(-ab-.5), ceiling(floor(2*ab)/2)
```

```python
Round=abQBV[Q2len-1:0].signed()
RoundDQ=Round/(2**(Q2[1]))
print(bin(Round), RoundDQ, np.abs(ab-RoundDQ))
```

    100101001100 -6.703125 0.0

`{ {(OWID){1'b0}}, 1'b1, {(IWID-OWID-1){1'b0}} }`, .5

 `i_data[(IWID-1):0]+ { {(OWID){1'b0}}, 1'b1, {(IWID-OWID-1){1'b0}} }`, x+.5

 `w_halfup[(IWID-1):(IWID-OWID)]`, floor(x+.5)

```python
concat(intbv(0)[8:], True, intbv(0)[16-8-1:])
```

    intbv(128)

```python
PointFive=intbv(int(.5*Q2scale))[16:]; PointFive, bin(PointFive, 16)
```

    (intbv(128), '0000000010000000')

```python
abQBVP5=intbv(abQBV+PointFive)[16:].signed()
abQBVP5
```

    intbv(-1588)

```python
abQBVP5=abQBVP5[Q2len-1:Q2len-Qlen].signed(); abQBVP5
```

    intbv(-7)

```python
abQBVP5, floor(ab+.5)
```

    (intbv(-7), -7)

# Round towards zero

```python

```

# Round Away from Zero

# Round Half to Even

```python

```


# Realization of Recursive Filters

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Quantization of Filter Coefficients

The finite numerical resolution of digital number representations has impact on the properties of filters, as already discussed for [non-recursive filters](../nonrecursive_filters/quantization_effects.ipynb#Quantization-Effects). The quantization of coefficients, state variables, algebraic operations and signals plays an important role in the design of recursive filters. Compared to non-recursive filters, the impact of quantization is often more prominent due to the feedback. Severe degradations from the desired characteristics and instability are potential consequences of a finite word length in practical implementations.

A recursive filter of order $N \geq 2$ can be [decomposed into second-order sections (SOS)](../recursive_filters/cascaded_structures.ipynb). Due to the grouping of poles/zeros to filter coefficients with a limited amplitude range, a realization by cascaded SOS is favorable in practice. We therefore limit our investigation of quantization effects to SOS. The transfer function of a SOS is given as

\begin{equation}
H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}}
\end{equation}

This can be [split into a non-recursive part and a recursive part](../recursive_filters/introduction.ipynb#Recursive-Filters). The quantization effects of non-recursive filters have already been discussed. We therefore focus here on the recursive part given by the transfer function

\begin{equation}
H(z) = \frac{1}{1 + a_1 z^{-1} + a_2 z^{-2}}
\end{equation}

This section investigates the consequences of quantization in recursive filters. As for non-recursive filters, we first take a look at the quantization of filter coefficients. The structure used for the realization of the filter has impact on the quantization effects. We begin with the direct form followed by the coupled form, as example for an alternative structure.

### Direct Form

Above transfer function of the recursive part of a SOS can be rewritten in terms of its complex conjugate poles $z_{\infty}$ and $z_{\infty}^*$ as

\begin{equation}
H(z) = \frac{1}{(z-z_{\infty}) (z-z_{\infty}^*)} = \frac{z^{-2}}{ 1 \underbrace{- 2 r \cos(\varphi)}_{a_1} \; z^{-1} + \underbrace{r^2}_{a_2} \; z^{-2} }
\end{equation}

where $r = |z_{\infty}|$ and $\varphi = \arg \{z_{\infty}\}$ denote the absolute value and phase of the pole $z_{\infty}$, respectively. Let's assume a [linear uniform quantization](../quantization/linear_uniform_quantization_error.ipynb#Quantization-Error-of-a-Linear-Uniform-Quantizer) of the coefficients $a_1$ and $a_2$ with quantization step $Q$. Discarding clipping, the following relations for the locations of the poles can be found

\begin{align}
r_n &= \sqrt{n \cdot Q} \\
\varphi_{nm} &= \arccos \left( \sqrt{\frac{m^2 Q}{4 n}} \right)
\end{align}
for $n \in \mathbb{N}_0$ and $m \in \mathbb{Z}$. Quantization of the filter coefficients $a_1$ and $a_2$ into a finite number of amplitude values leads to a finite number of pole locations. In the $z$-plane the possible pole locations are given by the intersections of

* circles whose radii $r_n$ are given by $r_n = \sqrt{n \cdot Q}$ with
* equidistant vertical lines which intersect the horizontal axis at $\frac{1}{2} m \cdot Q$.

The finite number of pole locations may lead to deviations from a desired filter characteristic since a desired pole location is moved to the next possible pole location. The filter may even get unstable, when poles are moved outside the unit circle. For illustration, the resulting pole locations for a SOS realized in direct form are computed and plotted.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import scipy.signal as sig
import itertools

def compute_pole_locations(Q):
    a1 = np.arange(-2, 2+Q, Q)
    a2 = np.arange(0, 1+Q, Q)

    p = np.asarray([np.roots([1, n, m]) for (n,m) in itertools.product(a1, a2)])
    p = p[np.imag(p)!=0]

    return p

def plot_pole_locations(p, Q):
    ax = plt.gca()
    for n in np.arange(np.ceil(2/Q)+1):
        circle = Circle((0,0), radius=np.sqrt(n*Q), fill=False, color='black', ls='solid', alpha=0.05)
        ax.add_patch(circle)
        ax.axvline(.5*n*Q, color='0.95')
        ax.axvline(-.5*n*Q, color='0.95')

    unit_circle = Circle((0,0), radius=1, fill=False, color='red', ls='solid')
    ax.add_patch(unit_circle)

    plt.plot(np.real(p), np.imag(p), 'b.', ms = 4)
    plt.xlabel(r'Re{$z$}')
    plt.ylabel(r'Im{$z$}')
    plt.axis([-1.1, 1.1, -1.1, 1.1])

# compute and plot pole locations
for w in [5,6]:
    Q = 2/(2**(w-1))  # quantization stepsize
    plt.figure(figsize=(5, 5))
    p = compute_pole_locations(Q)
    plot_pole_locations(p, Q)
    plt.title(r'Direct form coefficient quantization to $w=%d$ bits'%w)
```

**Exercise**

* What consequences does the distribution of pole locations on the desired characteristics of a filter have for e.g. low/high frequencies?

Solution: Quantization of the original filter coefficients leads to a limited number of possible pole and zero locations. These locations are not uniformly distributed over the $z$-plane, as can be observed from above illustrations. The density of potential locations is especially low for low frequencies and close to the Nyquist frequency. The properties of a designed filter having poles and/or zeros at low/high frequencies will potentially deviate more when quantizing its coefficients, as a consequence.

### Coupled Form

Besides the quantization step $Q$, the pole distribution depends also on the topology of the filter. In order to gain a different distribution of pole locations after quantization, one has to derive structures where the coefficients of the multipliers are given by other values than the direct form coefficients $a_1$ and $a_2$.

One of these alternative structures is the coupled form (also known as Gold & Rader structure)

where $\Re\{z_\infty\} = r \cdot \cos \varphi$ and $\Im\{z_\infty\} = r \cdot \sin \varphi$ denote the real- and imaginary part of the complex pole $z_\infty$, respectively. Analysis of the structure reveals its difference equation as

\begin{align}
w[k] &= x[k] + \Re\{z_\infty\} \, w[k-1] - \Im\{z_\infty\} \, y[k-1] \\
y[k] &= \Im\{z_\infty\} \, w[k-1] + \Re\{z_\infty\} \, y[k-1]
\end{align}

and its transfer function as

\begin{equation}
H(z) = \frac{\Im\{z_\infty\} \; z^{-1}}{ 1 - 2 \Re\{z_\infty\} \; z^{-1} + (\Re\{z_\infty\}^2 + \Im\{z_\infty\}^2) \; z^{-2} }
\end{equation}

Note that the numerator of the transfer function differs from the recursive only SOS given above. However, this can be considered in the design of the transfer function of a general SOS.

The real- and imaginary part of the pole $z_\infty$ occur directly as coefficients for the multipliers in the coupled form. Quantization of these coefficients results therefore in a Cartesian grid of possible pole locations in the $z$-plane. This is illustrated in the following.

```python
def compute_pole_locations(w):
    Q = 1/(2**(w-1))  # quantization stepsize
    a1 = np.arange(-1, 1+Q, Q)
    a2 = np.arange(-1, 1+Q, Q)

    p = np.asarray([n+1j*m for (n,m) in itertools.product(a1, a2) if n**2+m**2 <= 1])

    return p

def plot_pole_locations(p):
    ax = plt.gca()

    unit_circle = Circle((0,0), radius=1, fill=False, color='red', ls='solid')
    ax.add_patch(unit_circle)

    plt.plot(np.real(p), np.imag(p), 'b.', ms = 4)
    plt.xlabel(r'Re{$z$}')
    plt.ylabel(r'Im{$z$}')
    plt.axis([-1.1, 1.1, -1.1, 1.1])

# compute and plot pole locations
for w in [5,6]:
    plt.figure(figsize=(5, 5))
    p = compute_pole_locations(w)
    plot_pole_locations(p)
    plt.title(r'Coupled form coefficient quantization to $w=%d$ bits'%w)
```

**Excercise**

* What is the benefit of this representation in comparison to the direct from discussed in the previous section?

Solution: A befit of the coupled form is a uniform distribution of potential pole and zero locations in the $z$-plane. This holds especially for low frequencies and close to the Nyquist frequency.

### Example - Influence of coefficient quantization

The following example illustrates the effects of coefficient quantization for a recursive [Butterworth filter](https://en.wikipedia.org/wiki/Butterworth_filter) realized in cascaded SOSs in transposed direct form II.

```python
w = 16  # wordlength of filter coefficients
N = 7  # order of filter

def uniform_midtread_quantizer(x, w, xmin=1):
    # quantization step
    Q = xmin/(2**(w-1))
    # limiter
    x = np.copy(x)
    idx = np.where(x <= -xmin)
    x[idx] = -1
    idx = np.where(x > xmin - Q)
    x[idx] = 1 - Q
    # linear uniform quantization
    xQ = Q * np.floor(x/Q + 1/2)

    return xQ

def zplane(z, p, title='Poles and Zeros'):
    "Plots zero and pole locations in the complex z-plane"
    ax = plt.gca()

    ax.plot(np.real(z), np.imag(z), 'bo', fillstyle='none', ms = 10)
    ax.plot(np.real(p), np.imag(p), 'rx', fillstyle='none', ms = 10)
    unit_circle = Circle((0,0), radius=1, fill=False,
                         color='black', ls='solid', alpha=0.9)
    ax.add_patch(unit_circle)
    ax.axvline(0, color='0.7')
    ax.axhline(0, color='0.7')

    plt.title(title)
    plt.xlabel(r'Re{$z$}')
    plt.ylabel(r'Im{$z$}')
    plt.axis('equal')
    plt.xlim((-2, 2))
    plt.ylim((-2, 2))
    plt.grid()

# coefficients of recursive filter
b, a = sig.butter(N, 0.2, 'low')
# decomposition into SOS
sos = sig.tf2sos(b, a, pairing='nearest')
sos = sos/np.amax(np.abs(sos))
# quantization of SOS coefficients
sosq = uniform_midtread_quantizer(sos, w, xmin=1)
# compute overall transfer function of (quantized) filter
H = np.ones(512)
Hq = np.ones(512)
for n in range(sos.shape[0]):
    Om, Hn = sig.freqz(sos[n, 0:3], sos[n, 3:6])
    H = H * Hn
    Om, Hn = sig.freqz(sosq[n, 0:3], sosq[n, 3:6])
    Hq = Hq * Hn

# plot magnitude responses
plt.figure(figsize=(10, 3))
plt.plot(Om, 20 * np.log10(abs(H)), label='continuous')
plt.plot(Om, 20 * np.log10(abs(Hq)), label='quantized')
plt.title('Magnitude response')
plt.xlabel(r'$\Omega$')
plt.ylabel(r'$|H(e^{j \Omega})|$ in dB')
plt.legend(loc=3)
plt.grid()
# plot phase responses
plt.figure(figsize=(10, 3))
plt.plot(Om, np.unwrap(np.angle(H)), label='continuous')
plt.plot(Om, np.unwrap(np.angle(Hq)), label='quantized')
plt.title('Phase')
plt.xlabel(r'$\Omega$')
plt.ylabel(r'$\varphi (\Omega)$ in rad')
plt.legend(loc=3)
plt.grid()
```

**Exercise**

* Decrease the word length `w` of the filter. What happens? At what word length does the filter become unstable?
* Increase the order `N` of the filter for a fixed word length `w`. What happens?

Solution: The deviations from the continuous (desired) realization of the filter increase with decreasing word length. The filter with order `N=5` becomes unstable for `w < 10`. Increasing the order `N` of the filter for a fixed word length results also in instabilities. Consequently, for a high order filter also a higher word length is required.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2018*.


# Summas de Riemann: punto intermedio.
## Definición de suma de Riemann.
Las sumas de Riemman son una aproximacion para obtener el area bajo una curva entre dos puntos especificos. Esta aproximacion se realiza usando poligonos regulares que cubren el area objetivo, de los cuales se puede hallar su area con mayor facilidad.

##Suma de punto medio.
Son una aproximación de Riemann que toman la evaluacion de cada punto de los intervalos en donde este punto es la mitad del ancho de la figura geometrica.
Se observa que esta aproximacion, respecto a la derecha y a la izquierda, es mas precisa debido a que intenta distribuir las areas no cubiertas con las areas sobrantes de la figura semejante proxima.
Se comprende mejor al observar la siguiente grafica:

Grafica tomada de https://es.khanacademy.org/math/ap-calculus-ab/ab-integration-new/ab-6-2/v/midpoint-sums

###Referencias bibliográficas.
- Khan Academy (s.f). Sumas de punto medio. tomado de https://es.khanacademy.org/math/ap-calculus-ab/ab-integration-new/ab-6-2/v/midpoint-sums

###Bibliografía.

- Khan Academy (s.f). Introducción a la aproximación de Riemann. tomado de
https://es.khanacademy.org/math/ap-calculus-ab/ab-integration-new/ab-6-2/v/simple-riemann-approximation-using-rectangles

- Khan Academy (s.f). Sumas de punto medio. tomado de https://es.khanacademy.org/math/ap-calculus-ab/ab-integration-new/ab-6-2/v/midpoint-sums

```

# ej.  B  ::  punto medio :: funcion = x**3
##   Ejecutar :: imprime todo el cuadro de una vez.

from sympy import integrate, init_printing
from sympy.abc import x
#init_printing(use_latex="mathjax")

# valor real: se halla con sympy: con la funcion integrate
integ_v_exacto = integrate( x**3, (x, lim_inf, lim_sup) )

# en una funcion obtiene las sumas de Riemman para todas las particiones 3, 10, 25, 100 que estan en la tupla

def todas_las_sumas_Rnn( lim_inf, lim_sup, n_particiones ) :
  global particiones_ , evaluacion_Particion
  particiones_ , evaluacion_Particion = {}, {}
  sumasRiemann = []
  for n_particion in n_particiones :
    append = []
    append.append(n_particion)
    delta            =  (lim_sup - lim_inf) / n_particion
    ###                |---  I N T E R M E D I A   !!   ---|
    valores_x_delta  = [ ((lim_inf + delta / 2) + (i * delta)) for i in range( 0, n_particion, 1) ]
    particiones_[n_particion] = valores_x_delta
    eval_func_parti  =  [ (  valor  **3 )  for valor in valores_x_delta   ]
    evaluacion_Particion[n_particion]   = eval_func_parti
    suma = 0
    for altura in eval_func_parti   :   suma += delta * altura
    append.append(suma)
    error            = ( (integ_v_exacto - suma) / integ_v_exacto ) * 100
    append.append(error)
    sumasRiemann.append(append)
  return sumasRiemann

lim_sup = 2
lim_inf = 0
n_particiones = ( 3, 10, 25, 100 )

sumasRiemann = todas_las_sumas_Rnn( lim_inf, lim_sup, n_particiones )

from tabulate import tabulate
print(tabulate( sumasRiemann ,  headers=['Partición', 'Suma', 'Error'], tablefmt= 'francy_grid'))
print('')

```

      Partición     Suma    Error
    -----------  -------  -------
              3  3.77778  5.55556
             10  3.98     0.5
             25  3.9968   0.08
            100  3.9998   0.005

```

import matplotlib.pyplot as plot
import numpy as np

def F_particionInter( n_part ):
  ret = particiones_[ n_part ]
  #print(ret)
  return ret

def F_evaluacionParticion( n_part ):
  print('n_part: ', n_part)
  ret = evaluacion_Particion[ n_part ]
  print('ret',ret)
  return ret

x1 = np.arange(0, 10, 0.01)
y1 = x1**3

print('particione_ : ', particiones_)
print('evaluacion_Particion: ', evaluacion_Particion)

for n_part in n_particiones:
  x1 = np.arange(0, n_part, 0.01)
  y1 = x1**3
  x = F_particionInter( n_part )
  y = F_evaluacionParticion(n_part )
  print('x: ', x)
  print('y: ', y)
  #y=[0.03703703703703703, 1.0, 4.629629629629628]
  plot.plot( x, y , label = ' particiones ' )
  for i in range( 0, len(x) ):
    the_x = x[i]
    print('the x: ', the_x)
    the_y = [  ]
    since = y[i]
    init = y[i]
    delt = init/9
    for i in range(since,10,-delt ):
      the_y.append(init)
      init -= delt
    x_s = [ the_x for k in range(10) ]
    print('x_s: ', len(x_s))
    print('the_y: ' , len(the_y))
    plot.plot(x_s, the_y, label="pruebas" )
  plot.plot(x1, y1, label=' funcion objetivo ... ')

  plot.xlabel(' intervalos - delta ')
  plot.ylabel(' evaluacion de la funcion ')
  plot.title(' sima Riemann: intermedias ' )
  plot.legend()
  plot.show()
```


# Standalone Fishbone-Moncrief C Code

We start with the NRPy+ expressions generated in the [Tutorial-FishboneMoncriefID](Tutorial-FishboneMoncriefID.ipynb), and output them to the C file "FishboneMoncriefID/FMstandalone.h".

Further, $\Gamma = \alpha u^0$ is given by (as shown [here](Tutorial-u0_smallb_Poynting-Cartesian.ipynb)):
$$
\Gamma = \alpha u^0 = \sqrt{\frac{1}{1 - \gamma_{ij}v^i_{(n)}v^j_{(n)}}}.
$$

```python
import sympy as sp               # SymPy: The Python computer algebra package upon which NRPy+ depends
from outputC import lhrh         # NRPy+: Core C code output module
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import finite_difference as fin  # NRPy+: Finite difference C code generation module
import FishboneMoncriefID.FishboneMoncriefID as fmid

# Step 1: Set up the Fishbone-Moncrief initial data. This sets all the ID gridfunctions.
fmid.FishboneMoncriefID("Spherical")

gammaDD = ixp.zerorank2()

DIM = 3
for i in range(DIM):
    for j in range(DIM):
        if i<=j:
            gammaDD[i][j] = fmid.IDgammaDD[i][j]
        else:
            gammaDD[i][j] = fmid.IDgammaDD[j][i]

# gamma_{ij} v^i_{(n)} v^j_{(n)}
Gammacontraction = sp.sympify(0)
for i in range(DIM):
    for j in range(DIM):
        Gammacontraction += gammaDD[i][j] * fmid.IDValencia3velocityU[i] * fmid.IDValencia3velocityU[j]

Gammafactor = sp.sqrt(1 / (1 - Gammacontraction))

# -={ F-M quantities: Generate C code from expressions and output to file }=-
FishboneMoncrief_to_print = [\
                     lhrh(lhs="alpha",rhs=fmid.IDalpha),\
                     lhrh(lhs="betaU0",rhs=fmid.IDbetaU[0]),\
                     lhrh(lhs="betaU1",rhs=fmid.IDbetaU[1]),\
                     lhrh(lhs="betaU2",rhs=fmid.IDbetaU[2]),\
                     lhrh(lhs="Gammafactor",rhs=Gammafactor),\
                     lhrh(lhs="Gamma_times_ValenciavU0",rhs=Gammafactor*fmid.IDValencia3velocityU[0]),\
                     lhrh(lhs="Gamma_times_ValenciavU1",rhs=Gammafactor*fmid.IDValencia3velocityU[1]),\
                     lhrh(lhs="Gamma_times_ValenciavU2",rhs=Gammafactor*fmid.IDValencia3velocityU[2]),\
                     lhrh(lhs="uKS4U1",rhs=fmid.uKS4U[1]),\
                     lhrh(lhs="uKS4U2",rhs=fmid.uKS4U[2]),\
                     lhrh(lhs="uKS4U3",rhs=fmid.uKS4U[3]),\
                     lhrh(lhs="uBL4U1",rhs=fmid.uBL4U[1]),\
                     lhrh(lhs="uBL4U2",rhs=fmid.uBL4U[2]),\
                     lhrh(lhs="uBL4U3",rhs=fmid.uBL4U[3])
                     ]
# print(fmid.uKS4U[3])
fin.FD_outputC("FishboneMoncriefID/FM_standalone.h",FishboneMoncrief_to_print,params="outCverbose=False,CSE_enable=False")
```

    Wrote to file "FishboneMoncriefID/FM_standalone.h"

```python
%%writefile FishboneMoncriefID/FM_standalone.c

#include "stdio.h"
#include "stdlib.h"
#include "math.h"

const double a = 0.9375;
const double M = 1.0;
const double r_at_max_density = 12.0;
const double r_in = 6.0;

int main(int argc, const char *argv[]) {

    // Step 0a: Read command-line input, error out if nonconformant
    double xx0,xx1,xx2;
/*
    if(argc != 4) {
        printf("Error: Expected three command-line arguments: ./FM_standalone r theta phi\n");
        exit(1);
    }
    xx0 = strtod(argv[1],NULL);
    xx1 = strtod(argv[2],NULL);
    xx2 = strtod(argv[3],NULL);
*/

//    printf("# Output: r,th,ph, alpha, betaU0, betaU1, betaU2, Gamma, Gamma*vValenciaU0, Gamma*vValenciaU1, Gamma*vValenciaU2\n");
    for(double xx0=1.6;xx0<50.0;xx0+=0.2) {
        xx1 = 1.56463634120e0; //M_PI/2.0;
        xx2 = 0.0;
        double alpha,betaU0,betaU1,betaU2,Gammafactor,Gamma_times_ValenciavU0,Gamma_times_ValenciavU1,Gamma_times_ValenciavU2;
        double uKS4U1,uKS4U2,uKS4U3,uBL4U1,uBL4U2,uBL4U3;
#include "FM_standalone.h"
        if(xx0 < r_in) {
            Gammafactor = 1.0;
            Gamma_times_ValenciavU0 = Gamma_times_ValenciavU1 = Gamma_times_ValenciavU2 = 0.0;
            uKS4U1 = uKS4U2 = uKS4U3 = 0.0;
            uBL4U1 = uBL4U2 = uBL4U3 = 0.0;
        }
        printf("%e %e %e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e\n",
               xx0,xx1,xx2,
               alpha,betaU0,betaU1,betaU2,
               Gammafactor,
               Gamma_times_ValenciavU0, // util1(1) in FMtorus.f90; util(1,i,j,k) near the write statement
               Gamma_times_ValenciavU1, // util1(3) in FMtorus.f90.
               Gamma_times_ValenciavU2, // util1(2) in FMtorus.f90.
               uKS4U1,uKS4U2,uKS4U3,
               uBL4U1,uBL4U2,uBL4U3);
    }
    return 0;
}
```

    Writing FishboneMoncriefID/FM_standalone.c

```python
!gcc -O2 FishboneMoncriefID/FM_standalone.c -o FM_standalone -lm
```

```python
!./FM_standalone > out.txt
```

```python
%matplotlib inline

import matplotlib.pyplot as plt
import mpmath as mp
import csv

# Download torus_cuts.csv:
URL     = "http://astro.phys.wvu.edu/zetienne/torus_cuts.csv"
outfile = "torus_cuts.csv"
try:
    with open(outfile,"w") as file:
        file.write(urllib.request.urlopen(URL).read().decode("utf-8"))
except:
    try:
        with open(outfile,"w") as file:
            file.write(urllib.urlopen(URL).read().decode("utf-8"))
    except:
        # If all else fails, hope wget does the job
        !wget -O $outfile $URL

def file_reader(filename,list_of_cols,delim=" "):
    with open(filename) as file:
        reader = csv.reader(file, delimiter=delim)
        data  = list(zip(*reader))
#         print(data)
        # data is a tuple of strings. Tuples are immutable, and we need to perform math on
        #   the data, so here we convert tuple to lists of floats:
#        data_output = [[sp.sympify(0) for i in range(len(list_of_cols))] for j in range(len(data[0]))]
        data_output = [[sp.sympify(0) for i in range(len(data[0]))] for j in range(len(list_of_cols))]
        for i in range(len(data[0])):
            for j in range(len(list_of_cols)):
#                 print(i,j,data[list_of_cols[j]][i])
                data_output[j][i] = float(data[list_of_cols[j]][i])
        return data_output

NRPy_data_output = file_reader('out.txt',       [0,7,8,9,10])
std_data_output  = file_reader('torus_cuts.csv',[0,4,1,3,2])

ylabels = ['Lorentz Gamma_{KS}=G','G*v^r_{KS,Val.}','G*v^{\\theta}_{KS,Val.}','G*v^{\phi}_{KS,Val.}']

for i in range(len(ylabels)):
    # https://matplotlib.org/gallery/text_labels_and_annotations/legend.html#sphx-glr-gallery-text-labels-and-annotations-legend-py
    fig, ax = plt.subplots()
    plt.title("NRPy's FM solve with FMtorus.f90: "+ylabels[i])
    plt.xlabel("r/M")
    plt.ylabel(ylabels[i])
    ax.plot(NRPy_data_output[0], NRPy_data_output[i+1], 'k--', label='NRPyFMSolve')
    ax.plot(std_data_output[0],   std_data_output[i+1], 'k-',  label='FMtorus.f90')
    legend = ax.legend(loc='upper right', shadow=True, fontsize='x-large')
    legend.get_frame().set_facecolor('C1')
    plt.show()
```


# Building Better Models for Inference:

# How to construct practical models for existing tools

In this notebook, we will walk through fitting an observed optical light curve from a tidal disruption event (TDE), the destruction and accretion of a star by a supermassive black hole, using two different approaches.

As mentioned in the lecture, there are different kinds of models one can apply to a set of data. A code I have written, MOSFiT, is an attempt to provide a framework for building models that can be used within other optimizers/samplers. While MOSFiT can run independently with its own built-in samplers, in the notebook below we will simple be using it as a "black box" function for use in external optimization routines.

Our first approach will be using the `tde` model in MOSFiT. This model uses both interpolation tables and integrations, making an analytical derivative not available. Our second approach will be to construct a simple analytical function to fit the same data. We will then be comparing performance, both in terms of the quality of the resulting solution, but also the speed by which the solution was computed, and in how we relate our solution to what transpired in this event.

* * *

By J Guillochon (Harvard)

*We will be mostly using the mosfit package and scipy routines. Both are available via conda.*

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

# Disable "retina" line below if your monitor doesn't support it.
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
```

## Problem 1) Fitting data with a blackbox model

In this first cell, we load the data of a particularly well-sampled tidal disruption event from the Pan-STARRS survey, PS1-10jh. This even is notable because it was caught on the rise, peak, and decay, with solid cadence.

The datafile can be aquired from https://northwestern.app.box.com/s/ekwpbf8ufe1ivogpxq9yyex302zx0t96.

```python
# Load the data from the Open Supernova Catalog.
# Note: if loading the data doesn't work, I have a local copy.
my_printer = mosfit.printer.Printer(quiet=True)  # just to avoid spamming from MOSFiT routines.
my_fetcher = mosfit.fetcher.Fetcher()

fetched = my_fetcher.fetch('PS1-10jh.json')[0]

my_model = mosfit.model.Model(model='tde', printer=my_printer)
fetched_data = my_fetcher.load_data(fetched)
my_model.load_data(
    fetched_data, event_name=fetched['name'],
    exclude_bands=['u', 'r', 'i', 'z', 'F225W', 'NUV'],  # ignore all bands but g when computing ln_likelihood.
    smooth_times=100,  # for plotting smooth fits later
    user_fixed_parameters=['covariance'])  # don't use GP objective function.

# Generate 100 random parameter realizations.
x = np.random.rand(100, my_model.get_num_free_parameters())

# Compute time per function call.
start_time = time.time()
ln_likes = [my_model.ln_likelihood(xx) for xx in x]
stop_time = time.time()

print('{}s per function call.'.format((stop_time - start_time)/100.0))
```

**Problem 1a**

First, let's visualize the data we have downloaded. MOSFiT loads data in a format conforming to the OAC schema specification, which is a JSON dictionary where the top level of the structure is each event's name. The code snippet below will load a JSON dictionary for the event in question, plot the full time series of photometric data (with error bars) within the `photometry` key below.

*Hint: The photometry is a mixture of different data types, and not every entry has the same set of keys. Optical/UV/IR photometry will always have a `band` key. Ignore upper limits (indicated with the `upperlimit` attribute). Use the `.get()` function liberally, and make sure everything is a `float`!*

```python
times = []
mags = []
errs = []
for x in fetched_data[fetched['name']]['photometry']:
    # complete

plt.errorbar(times, mags, yerr=errs, fmt='o')
plt.gca().invert_yaxis()
plt.show()
```

**Problem 1b**

We know what the data looks like, and we've loaded a model that can be used to fit the data which computes a likelihood. Let's minimize the parameters of this model using various `scipy.optimize` routines. Note that since we are trying to **maximize** the likelihood, we have constructed a wrapper function around `ln_likelihood`, `my_func`, to reverse its sign, and to handle bad function evaluations.

Most `optimize` routines in `scipy` require a derivative. Since we don't have this available, `scipy` must construct an approximate one, unless the method doesn't require a gradient to be computed (like `differential_evolution`). For this first sub-problem, optimize `my_func` using `differential_evolution`.

*Hints: Each variable is bounded to the range (0, 1), but problems can arise if an optimizer attempts to compute values outside or right at the boundaries. Therefore, it is recommended to use a bounded optimizer in `scipy`, where the bounds do **not include** 0 or 1.*

```python
import scipy

def my_func(x):
    try:
        fx = -float(my_model.ln_likelihood(x))
    except:
        fx = np.inf
    return fx

eps = 0.00001
bounds = # complete
results = scipy.optimize.differential_evolution(  # complete
best_x = results.x

print('All done! Best score: `{}`.'.format(-results.fun))
```

This might take a while; try to limit the execution time of the above to ~5 minutes by playing with the `maxiter` and similar options of the `scipy` optimizers.

Once the above has finished evaluating, compare the score you got to your neighbors. Is there a significant difference between your scores? Let's plot your result against the data.

Model output is provided in the `output` object below, the format is a dictionary of arrays of the same length. The times of observation are in the `times` array, and magnitudes are in the `model_observations` array.

*Hint: `times` is given relative to the time of the first detection, so add `min(times)` to your time to overplot onto the data.*

```python
output = my_model.run_stack(best_x, root='output')

for ti, t in enumerate(output['times']):
    # complete

plt.errorbar(  # complete
plt.plot(  # complete
plt.gca().invert_yaxis()
plt.show()
```

**Problem 1c**

Try optimizing the same function using **another** minimization routine in scipy that can take a derivative as an input (examples: `L-BFGS-B`, `SLSQP`, `basinhopping`, etc.).

```python
results = scipy.optimize.basinhopping( #complete
best_x = results.x

print('All done! Best score: `{}`.'.format(-results.fun))
```

Now, plot the results of the above minimization alongside your original `differential_evolution` solution.

```python
# complete

for ti, t in enumerate(output['times']):
    # complete

# complete
```

After this process, **some** of you **might** have gotten a good solution with a runtime of a few minutes. In practice, guaranteed convergence to the best solution can take a very long time. Whats more, we only attempted to find the **best** solution available, usually we are interested in posterior distributions that (usually) include the best solution. These take even longer to compute (tens of thousands of function evaluations for a problem of this size).

## Problem 2

Now, we'll construct our own simpler model that is **analytically differentiable**. We'll partly motivate the shape of this function based upon our knowledge of how tidal disruption events are expected to behave theoretically, but there will be limitations.

First, let's define a function that loosely mimics a tidal disruption event's temporal evolution. Tidal disruption events rise exponentially, then decay as a power-law. Canonically, the decay rate is -5/3, and the rise is very unconstrained, being mediated by complicated dynamics and accretion physics that have yet to be determined. So, we use the following agnostic form,

$$L(t) = L_0 \left(1-e^{-\frac{t}{t_0}}\right)^{\alpha } \left(\frac{t}{t_0}\right)^{-\beta }.$$

Tidal disruption observations are usually reported in magnitudes, thus the expression we'll actually compare against observations is

$$m(t) = m_0 - 2.5 \log_{10}\left[\left(1-e^{-\frac{t}{t_0}}\right)^{\alpha } \left(\frac{t}{t_0}\right)^{-\beta }\right].$$

To calculate the likelihood, we want to subtract the above from the observations. We'll make the gross assumption that the color of a tidal disruption is constant in time (which turns out to not be a terrible assumption) and thus $L_{\rm g}(t) \propto L(t)$.

Our likelihood function will be defined as the product of the squares of differences between our model and observation,

$$p = \prod_i \frac{1}{\sqrt{2\pi (\sigma_i^2 + \sigma^2)}} \left[\frac{\left(m_{{\rm g}, i} - \bar{m}_{{\rm g}, i}\right)^2}{2\left(\sigma_i^2 + \sigma^2\right)}\right],$$

and thus our log likelihood is the sum of these squared differences, plus a separate sum for the variances,

$$\log p = -\frac{1}{2} \left\{\sum_i \left[\frac{\left(m_{{\rm g}, i} - \bar{m}_{{\rm g}, i}\right)^2}{\sigma_i^2 + \sigma^2}\right] + \log 2\pi \left(\sigma_i^2 + \sigma^2\right)\right\}.$$

**Problem 2a**

Write the above expression as a python function:

```python
def analytic_f(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    # complete
```

** Problem 2a **

Compute the derivative for $\log p$ (above expression) with respect to $m_0$ (Mathematica might be helpful here). Below are the derivatives for the other five free parameters $\alpha$, $\beta$, $\tau$, $t_0$, and $\sigma$:

$$
\begin{align}
\frac{\partial\log p}{\partial \alpha} &= \sum_i -\frac{5 \log \left(1-e^{-\frac{t+t_0}{\tau }}\right) \left\{\log (100) (\bar{m}-m_0)+5 \log \left[\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right]\right\}}{4 \log ^2(10) \left(\sigma_i^2+\sigma^2\right)}\\
\frac{\partial\log p}{\partial \beta} &= \sum_i \frac{5 \log \left(\frac{t+t_0}{\tau }\right) \left\{\log (100) (\bar{m}-m_0)+5 \log \left[\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right]\right\}}{4 \log ^2(10) \left(\sigma_i^2+\sigma^2\right)}\\
\frac{\partial\log p}{\partial \tau} &= \sum_i \frac{5 \left(\alpha  (t+t_0)-\beta  \tau  \left(e^{\frac{t+t_0}{\tau }}-1\right)\right) \left(\log (100) (\bar{m}-m_0)+5 \log \left(\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right)\right)}{4 \tau ^2 \log ^2(10) \left(\sigma_i^2 + \sigma^2\right) \left(e^{\frac{t+t_0}{\tau }}-1\right)}\\
\frac{\partial\log p}{\partial t_0} &= \sum_i \frac{5 \left(\alpha  (t+t_0)-\beta  \tau  \left(e^{\frac{t+t_0}{\tau }}-1\right)\right) \left(\log (100) (m_0-\bar{m})-5 \log \left(\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right)\right)}{4 \tau  \log ^2(10) \left(\sigma_i^2+\sigma^2\right) (t+t_0) \left(e^{\frac{t+t_0}{\tau }}-1\right)}\\
\frac{\partial\log p}{\partial \sigma} &= \sum_i \frac{\sigma_i}{4 \log ^2(10) \left(\sigma_i^2+\sigma^2\right)^2} \left\{5 \log \left[\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right]\right.\\&\times\left.\left(4 \log (10) (\bar{m}-m_0)+5 \log \left[\left(1-e^{-\frac{t+t_0}{\tau }}\right)^{\alpha } \left(\frac{t+t_0}{\tau }\right)^{-\beta }\right]\right)+4 \log ^2(10) \left((m_0-\bar{m})^2-\sigma_i^2-\sigma^2\right)\right\}
\end{align}
$$

**Problem 2b**

We now need to write each of these derivatives as python functions. These functions should accept a single vector argument `x` with length equal to the number of free parameters, plus a vector $t$ (the times of the observation) vector $m$ (the magnitudes of each observation), and finally errors $v$ (the measurement error of each observation). Again, 5 of the 6 parameters have already been written for you (you must provide the 6th).

```python
def dlogp_dalpha(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    derivs = np.sum(-5.0 * np.log(1.0 - np.exp(-(t + t0) / tau)) * (np.log(100.0) * (m - m0) + 5.0 * lf(
        alpha, beta, tau, t0, t)) / (4.0 * np.log(10.0) ** 2 * (v ** 2 + sigma ** 2)))

    return derivs

def dlogp_dbeta(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    derivs = np.sum(5.0 * np.log((t + t0) / tau) * (np.log(100.0) * (m - m0) + 5.0 * lf(
        alpha, beta, tau, t0, t)) / (4.0 * np.log(10.0) ** 2 * (v ** 2 + sigma ** 2)))

    return derivs

def dlogp_dtau(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    derivs = np.sum(5.0 * (alpha * (t + t0) - beta * tau * (np.exp((t + t0)/tau) - 1.0)) * (
        np.log(100.0) * (m - m0) + 5.0 * lf(
            alpha, beta, tau, t0, t)) / (4.0 * tau ** 2 * np.log(10.0) ** 2 * (v ** 2 + sigma ** 2) * (
        np.exp((t + t0)/tau) - 1.0)))

    return derivs

def dlogp_dt0(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    derivs = np.sum(-5.0 * (alpha * (t + t0) - beta * tau * (np.exp((t + t0)/tau) - 1.0)) * (
        np.log(100.0) * (m - m0) + 5.0 * lf(
            alpha, beta, tau, t0, t)) / (4.0 * tau * (t + t0) * np.log(10.0) ** 2 * (v ** 2 + sigma ** 2) * (
        np.exp((t + t0)/tau) - 1.0)))

    return derivs

def dlogp_dsigma(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    derivs = np.sum(sigma/(4.0 * np.log(10.0) ** 2 * (v**2 + sigma**2)**2) * (5.0 * lf(
        alpha, beta, tau, t0, t) * (4.0 * np.log(10.0) * (m - m0) + 5.0 * lf(
        alpha, beta, tau, t0, t)) + 4.0 * np.log(10.0) ** 2 * ((m0 - m) ** 2 - v ** 2 - sigma ** 2)))

    return derivs
```

**Problem 2c**

Make sure the derivatives for all the above function are consistent with the finite differences of the objective function. How large is the error for an `eps = 1e-8` (the default distance used when no Jacobian is provided)? Make a histogram for each derivative of these errors for 100 random parameter combinations drawn from the bounds (in other words, six plots with 100 samples each).

*Hint: you will likely have to remove some nans.*

```python
# Set up bounds/test parameters.
abounds = [
    [0.0, 30.0],
    [0.1, 50.0],
    [0.1, 10.0],
    [0.1, 200.0],
    [0.1, 200.0],
    [0.001, 1.0]
]

test_times = [1.0, 20.0]
test_mags = [23.0, 19.0]
test_errs = [0.1, 0.2]

# Draw a random parameter combo to test with.
n = 100
dm0_diff = np.zeros(n)
for p in range(n):
    test_x = [abounds[i][0] + x * (abounds[i][1] - abounds[i][0]) for i, x in enumerate(np.random.rand(6))]

    # Check that every derivative expression is close to finite difference.
    teps = 1e-10

    xp = list(test_x)
    xp[0] += teps
    exactd = dlogp_dm0(test_x, test_times, test_mags, test_errs)
    dm0_diff[p] = (exactd - (
        analytic_f(test_x, test_times, test_mags, test_errs) - analytic_f(
            xp, test_times, test_mags, test_errs)) / teps) / exactd

    # complete for rest of parameters

plt.subplot(321)
plt.hist(dm0_diff[~np.isnan(dm0_diff)]);
# complete for rest of parameters
```

Which derivatives seem to have the least accurate finite differences? Why?

## Problem 3

Now we have an analytical function with analytical derivatives that should be accurate to near-machine precision.

** Problem 3a **

First, let's optimize our function using `differential_evolution`, as we did above with the MOSFiT output, without using the derivatives we have constructed (as `differential_evolution` does not use them).

```python
import scipy

times0 = np.array(times) - min(times)
results = scipy.optimize.differential_evolution(# complete
best_x = results.x

print('All done! Best score: `{}`, took `{}` function evaluations.'.format(results.fun, results.nfev))
```

Now plot the result:

```python
# complete
```

How good is the approximation?

** Problem 3b **

Let's minimize using the `basinhopping` algorithm now, again not using our derivatives.

```python
times0 = np.array(times) - min(times)
results = scipy.optimize.basinhopping(  # complete
best_x = results.x

print('All done! Best score: `{}`, took `{}` function evaluations.'.format(results.fun, results.nfev))
```

This algorithm, which depends on finite differencing, seems to have taken more function evaluations than `differential_evolution`. Let's give it some help: construct a jacobian using the derivative functions defined above.

*Hint: mind the sign of the Jacobian since we are **minimizing** the function.*

```python
def jac(x, tt, mm, vv):
    m0, alpha, beta, tau, t0, sigma = tuple(x)
    t = np.array(tt)
    m = np.array(mm)
    v = np.array(vv)

    # complete

    return jac

results = scipy.optimize.basinhopping(  # complete
best_x = results.x

print('All done! Best score: `{}`, took `{}` function evaluations.'.format(results.fun, results.nfev))

# plot the resulting fit
```

If all went well, the jacobian version of the optimization should have taken ~8x fewer function evaluations. But is it faster?

**Problem 3c**

Compute how many times the Jacobian was called, and estimate how expensive the Jacobian is to compute relative to the objective function. How does this compare to the run that only used finite differencing?

```python
global jcount
jcount = 0

# complete
```

Can you think of a reason why using the Jacobian version may be preferable, even if it is slower?

## Challenge Problem(s)

**Select one (or more) of the following:**

- Fit a different event using either MOSFiT or the analytical formula. Any supernova can be loaded by name from the internet via the `fetch` method of the `Fetcher` class. Examples: SN1987A, SN2011fe, PTF09ge. If you are using the analytical model, exclude all but one of the bands in the dataset.
- Optimize the Jacobian function to reuse common functions that are shared between each derivative component (example: $1 - e^{((t + t_0)/\tau)}$ appears frequently in the expressions, it only needs to be computed once).
- Sample the posterior in a Monte Carlo framework (using priors of your choice). Samplers like `emcee` are versatile and work even when derivatives aren't available, but we **do** have derivatives, so more powerful methods like Hamiltonian MCMC are available to us. A simple HMC for our purposes is available via `pip install pyhmc`, see the README for instructions on how to construct the input function: https://github.com/rmcgibbo/pyhmc. Plot the resulting samples using the `corner` package.

```python
from pyhmc import hmc
# complete
```

## Concluding questions

- What did we learn from fitting the analytic model about the **physics** of the disruption?
- Does the analytic function have utility for generating simulated data?
- Where else might the analytic function have a use?