Massimo Nocentini

May 2018: intro

Abstract

Translation of code in the introduction chapter of Functional Differential Geometry book.

```python
from operator import attrgetter
from sympy import *
init_printing()
```

---

```python
f, L, w = Function('f'), Function('L'), Function('w') # abstract, Lagrangian and path functions, respectively
t, q, q_point = symbols(r't q \dot{q}') # symbols for the Leibniz notation
```

```python
Lagrangian_eq = Eq(Derivative(Derivative(L(t, q, q_point),q_point,evaluate=False),
                              t, evaluate=False) - L(t,q,q_point).diff(q),0)
Lagrangian_eq # compact Lagrangian equation, implicit indeed
```

```python
Lagrangian_eq = Eq(Derivative(Subs(L(t, q, q_point).diff(q_point), [q,q_point], [w(t),w(t).diff(t)]),t) -
                   Subs(L(t,q,q_point).diff(q), [q,q_point], [w(t),w(t).diff(t)]),0)
Lagrangian_eq
```

```python
Lagrangian_eq.doit() # a complex explosion by automatic computation
```

```python
def diff_positional(f, i):
    a = IndexedBase('a')
    der = f.diff(f.args[i])
    def D(*args):
        #return Lambda([a[i] for i in range(len(f.args))],
        return der.subs({f.args[i]:args[i] for i in range(len(f.args))}, simultaneous=True)
    return D # function D is a function of the meta-language, not in the object language
```

```python
diff_positional(L(t, q, q_point), 2)(t,w(t),w(t).diff(t)) # :/
```

```python
def diff_positional(f, i):
    return Function(r'\partial_{{{}}}{}'.format(i, str(f))) # the \partial is just a symbol, it hasn't meaning
```

```python
(Derivative(diff_positional(L, 2)(t,w(t), w(t).diff(t)), t) -
 diff_positional(L, 1)(t,w(t), w(t).diff(t))) # :(
```

```python
Lagrangian_eq = Eq(Derivative(L(t,w(t), w(t).diff(t)).fdiff(argindex=3),t, evaluate=False) -
                   L(t,w(t), w(t).diff(t)).fdiff(argindex=2),
                   0, evaluate=False)
Lagrangian_eq # :) , although it doesn't use "positional derivative" operator explicitly
```

```python
def Derivative_eq(f, argindex=1):
    d = Dummy()
    args = [d if i+1 == argindex else a for i,a in enumerate(f.args)]
    lhs = f.fdiff(argindex)
    rhs = Subs(f.func(*args).diff(d), d, f.args[argindex-1])
    return Eq(lhs, rhs, evaluate=False)

Derivative_eq(f(t,q,q_point),2) # applicative Derivative operator
```

```python
def Gamma(w):
    return Lambda([t], (t, w(t), w(t).diff(t))) # state-space function
```

```python
Gamma(w), Gamma(w)(t)
```

```python
Lagrangian_eq = Eq(Derivative(L(*Gamma(w)(t)).fdiff(argindex=3),t, evaluate=False) -
                   L(t,w(t), w(t).diff(t)).fdiff(argindex=2),
                   0, evaluate=False)
Lagrangian_eq
```

```python
class FunctionsComposition(Function):

    nargs = 2

    def _latex(self, sexp):
        return r' \circ '.join(map(latex,
                                   map(lambda a: a.func if isinstance(a, Function) else a,
                                       self.args)))

    def _eval_subs(self, old, new):
        f, g = self.args
        if old == f:
            return new.subs({f.args[0]:g}, simultaneous=True)

```

```python
F_o_w = FunctionsComposition(Function(r'\mathcal{F}')(t), w(t))
F_o_w
```

```python
F_o_w.subs({Function(r'\mathcal{F}')(t):1/(1-t)})
```

```python
F_o_w.subs({w(t):2*t})
```

```python
_.subs({Function(r'\mathcal{F}')(t):2*t+1})
```

```python
FunctionsComposition(L(t, w(t), w(t).diff(t)).fdiff(argindex=3),Gamma(w))
```

```python
m,v,k,q = symbols('m v k q')
Lagrangian_eq.subs({L:Lambda([t,q,v], (m*v**2)/2-(k*q**2)/2)}) # plug in the Lagrangian function
```

```python
eq = _.doit() # do derivatives
eq
```

```python
a, omega, phi = symbols(r'a \omega \phi')
_.subs({w(t):a*cos(omega*t+phi)})
```

```python
_.doit().factor()
```

```python
solve(_, omega)
```

```python
dsolve(eq, w(t)) # solve with respect to path function w(t)
```

---

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.


### Purpose

Decompose changes in Australian employment levels over 5 yearly intervals due to:
* australian born
* migrants that arrived in the last five years, and
* migrants that were already in Australia 5 years ago.

The 5 year period reflects that ABS labour force data identifies overseas born employed by time since arrival in 5 year intervals.

### Data

The LM7 datacube from [ABS 6291.0.55.001 (Labour Force, Detailed)](http://www.abs.gov.au/ausstats/abs@.nsf/mf/6291.0.55.001) contains monthly data on the numbers of:
* emloyed full-time
* employed part-time
* unemployed looked for full-time work
* unemployed looked for only part-time work
* not in the labour force (nilf)

by
* the number of years since arrival for migrants (in 5 year intervals up to 20 years, and > 20 years)
* gender
* place of birth (Australia, main english speaking countries, other than main enslish speaking countries and 'Not Stated / Inadequately Described / Born at sea')
* state

This analysis uses additional derived data from this dataset. The loaded dataframe includes:
* employed_total (sum of full-, and part-time employed)
* labor_force (sum of employed_total and and unemployed)
* population (sum of labor_force and nilf)
* COB (where place of birth is mapped to 'Australia', 'overseas' and 'Unkown')

### Deriving Australian born and migrant contributions to 5 year employment changes

Identifying the contributions of australian born, recent arrivals and established migrants can be derived by:

\begin{align}
\Delta E_5 & = E_{t} - E_{t-5} \\
& = (aus\_born_{t} + migrant_{t}) - (aus\_born_{t-5} + migrant_{t-5}) \\
& = (aus\_born_{t} - aus\_born_{t-5}) + (migrant_{t} - migrant_{t-5}) \\
& = \Delta aus\_born_5 + \Delta migrant_5
\end{align}

Where, in any given month t,  $E$ is the total number employed, $aus\_born$ is the number of australian born employed, $migrant$ is the number of overseas born employed.

$t-5$ refers to the month 5 years prior to the month $t$.

The change in employment levels of migrants, $\Delta migrant_5$, can be seperated into addtions from recent arrivals and changes in employment levels for established migrants.

\begin{equation}
\Delta migrant_5 = migrant_{arrived\_in\_last\_5\_years} + \Delta migrant_{arrived\_more\_than\_5\_years\_ago}
\end{equation}

That is, the decomposition of changes in 5 yearly total employment levels is:

\begin{equation}
\Delta E_5 = \Delta aus\_born_5 + migrant_{arrived\_in\_last\_5\_years} + \Delta migrant_{arrived\_more\_than\_5\_years\_ago}
\end{equation}

As the LM7 data cube contains data on the number of people employed by whether australian born or overseas born, together with 5 yearly arrival intervals for overseas born, $\Delta migrant_{arrived\_more_than\_5\_years\_ago}$  can be derived by substitution:

\begin{equation}
\Delta migrant_{arrived\_more\_than\_5\_years\_ago} = \Delta E_5 - \Delta aus\_born_5 - migrant_{arrived\_in\_last\_5\_years}
\end{equation}

### Calculations

#### Required libraries

```python
import pandas as pd

from pathlib import Path

```

#### Get LM7 data

```python
# Set the file path to the folder containing the data

# Assume the LM7 dataset is in the same folder as this notebook
# data_folder = Path('.')

# The path for my file structure for data sets and should be ignored
data_folder = Path(f'{Path.home()}/Documents/Analysis/Australian economy/Data/ABS')

```

```python
# The data contained in sheet 'Data 1' in the LM7 datacube has been extracted, the additional items defined above calculated (such as employment_total, etc)
# and stored as a parquet file
# This statement reads in that created datafile
# You could do this direcctly from the LM7 with pd.read_excel() statement: see last cell in this notebook for an example

df = pd.read_parquet(data_folder / 'LM7.parquet')

df.tail(3)
```

    /Users/christopher/anaconda3/lib/python3.7/site-packages/pyarrow/pandas_compat.py:708: FutureWarning: .labels was deprecated in version 0.24.0. Use .codes instead.
      labels = getattr(columns, 'labels', None) or [
    /Users/christopher/anaconda3/lib/python3.7/site-packages/pyarrow/pandas_compat.py:735: FutureWarning: the 'labels' keyword is deprecated, use 'codes' instead
      return pd.MultiIndex(levels=new_levels, labels=labels, names=columns.names)
    /Users/christopher/anaconda3/lib/python3.7/site-packages/pyarrow/pandas_compat.py:752: FutureWarning: .labels was deprecated in version 0.24.0. Use .codes instead.
      labels, = index.labels

      sex
      MESC
      elapsed_years_since_arrival
      state
      employed_full_time
      employed_part_time
      unemployed_looked_full_time
      unemployed_looked_part_time_only
      nilf
      COB
      labor_force
      employed_total
      population

      date

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Tasmania
      0.00
      0.00
      0.00
      0.00
      3.81
      unknown
      0.00
      0.00
      3.81

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Northern Territory
      0.00
      0.00
      0.00
      0.00
      1.92
      unknown
      0.00
      0.00
      1.92

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Australian Capital Territory
      0.00
      0.00
      0.00
      0.00
      1.32
      unknown
      0.00
      0.00
      1.32

```python
# Check the data read in
df.info()
```

    <class 'pandas.core.frame.DataFrame'>
    DatetimeIndex: 70373 entries, 1991-01-31 to 2018-12-31
    Data columns (total 13 columns):
    sex                                 70373 non-null object
    MESC                                70373 non-null object
    elapsed_years_since_arrival         70373 non-null object
    state                               70373 non-null object
    employed_full_time                  70373 non-null float64
    employed_part_time                  70373 non-null float64
    unemployed_looked_full_time         70373 non-null float64
    unemployed_looked_part_time_only    70373 non-null float64
    nilf                                70373 non-null float64
    COB                                 70373 non-null object
    labor_force                         70373 non-null float64
    employed_total                      70373 non-null float64
    population                          70373 non-null float64
    dtypes: float64(8), object(5)
    memory usage: 7.5+ MB

```python
(df
                  .groupby([df.index, 'elapsed_years_since_arrival'])['employed_total']
                  .sum()
                  .unstack('elapsed_years_since_arrival')
                  .drop(columns=['Not stated / Inadequately described / Born at sea'])
                  .sort_index(axis=1, ascending=False)
)
# #                   .reindex(labels=arrived_order, axis='columns')
#                   .assign(total = lambda x: x.sum(axis='columns'))
#                   .rename_axis(None, axis='columns')
# ).diff(60)
```

      elapsed_years_since_arrival
      Born in Australia
      Arrived within last 5 years
      Arrived 5-9 years ago
      Arrived 20 or more years ago
      Arrived 15-19 years ago
      Arrived 10-14 years ago

      date

      1991-01-31
      5,710.67
      240.08
      205.80
      1,030.70
      227.00
      227.02

      1991-02-28
      5,760.22
      241.76
      210.86
      1,038.47
      233.13
      224.35

      1991-03-31
      5,764.85
      262.07
      216.76
      1,026.49
      231.62
      213.60

      1991-04-30
      5,757.59
      265.37
      218.26
      1,031.50
      232.52
      211.97

      1991-05-31
      5,742.82
      271.69
      213.77
      1,019.89
      232.39
      223.15

      1991-06-30
      5,736.70
      262.77
      210.60
      1,020.43
      228.62
      218.94

      1991-07-31
      5,653.47
      261.13
      212.85
      996.39
      233.28
      225.87

      1991-08-31
      5,692.37
      275.97
      207.12
      996.33
      222.26
      226.65

      1991-09-30
      5,753.18
      287.98
      209.83
      1,001.03
      229.22
      231.99

      1991-10-31
      5,694.33
      302.26
      201.94
      983.66
      222.92
      232.85

      1991-11-30
      5,670.63
      293.15
      206.60
      980.54
      224.45
      239.57

      1991-12-31
      5,773.21
      293.57
      200.74
      968.50
      228.10
      248.06

      1992-01-31
      5,601.26
      226.83
      214.19
      1,016.43
      213.67
      246.56

      1992-02-29
      5,684.95
      225.05
      213.11
      1,022.36
      214.73
      236.98

      1992-03-31
      5,707.89
      239.91
      219.78
      1,024.71
      211.56
      242.31

      1992-04-30
      5,704.51
      238.19
      217.41
      1,014.78
      219.53
      249.66

      1992-05-31
      5,712.62
      239.20
      208.85
      1,016.54
      218.36
      235.84

      1992-06-30
      5,702.09
      241.36
      218.51
      1,026.84
      213.01
      235.49

      1992-07-31
      5,728.44
      239.39
      211.37
      1,036.08
      214.31
      238.66

      1992-08-31
      5,705.58
      232.70
      210.26
      1,021.52
      198.16
      245.18

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

      2017-05-31
      8,474.24
      551.53
      706.14
      1,513.65
      402.15
      608.92

      2017-06-30
      8,495.23
      557.69
      708.88
      1,490.04
      398.00
      605.12

      2017-07-31
      8,531.98
      569.53
      708.58
      1,492.24
      393.42
      564.75

      2017-08-31
      8,548.84
      574.17
      688.54
      1,486.07
      392.05
      555.25

      2017-09-30
      8,571.02
      616.84
      699.20
      1,487.82
      406.29
      551.98

      2017-10-31
      8,586.14
      608.14
      722.11
      1,482.85
      399.46
      551.53

      2017-11-30
      8,649.29
      633.41
      741.79
      1,479.07
      405.35
      544.24

      2017-12-31
      8,790.30
      638.67
      736.50
      1,455.38
      406.07
      540.40

      2018-01-31
      8,565.96
      538.92
      683.28
      1,499.73
      398.34
      618.22

      2018-02-28
      8,704.88
      556.01
      696.29
      1,524.78
      406.56
      638.86

      2018-03-31
      8,612.54
      581.33
      690.17
      1,536.43
      426.70
      646.58

      2018-04-30
      8,604.71
      612.20
      689.53
      1,517.61
      424.77
      676.14

      2018-05-31
      8,637.00
      608.90
      690.49
      1,519.64
      434.94
      679.76

      2018-06-30
      8,644.71
      613.59
      698.48
      1,522.16
      438.57
      689.25

      2018-07-31
      8,649.85
      608.65
      677.58
      1,506.91
      426.63
      702.85

      2018-08-31
      8,618.83
      628.32
      676.07
      1,501.64
      421.51
      697.68

      2018-09-30
      8,639.85
      673.29
      682.34
      1,491.32
      426.82
      697.59

      2018-10-31
      8,646.52
      692.89
      695.90
      1,499.97
      420.53
      696.22

      2018-11-30
      8,730.16
      678.67
      692.23
      1,540.51
      403.96
      693.67

      2018-12-31
      8,860.54
      685.40
      686.42
      1,522.64
      396.68
      686.62

336 rows × 6 columns

```python
# check no 'countries' missed
df.COB.unique()
```

    [overseas, Australia, unknown]
    Categories (3, object): [overseas, Australia, unknown]

#### Australian born & migrant contribution to employment growth

```python
def make_employed_by_duration(df, month=6):
    '''
    A function to extract employment levels for Aus. born, and OS born by time in Australia into a simple matrix from the LM7 datacube

    Parameters:
    -----------
        df: the LM7 dataset (ie sheet: Data 1 from LM7 loaded in a dataframe)
        month: integer or None
            the month to use (eg 6 for financial) if doing annual calculations, if None then return all data

    Returns
    -------
        employed: pandas dataframe
    '''

    # Remove unknown COB
    idx = df.MESC != 'Not Stated / Inadequately Described / Born at sea'  # or idx = df.COB != 'unknown'

    arrived_order = ['Born in Australia',
                     'Arrived within last 5 years',
                     'Arrived 5-9 years ago',
                     'Arrived 10-14 years ago',
                     'Arrived 15-19 years ago',
                     'Arrived 20 or more years ago',
                     'total'
                    ]

    employed = (df.loc[idx]
                  .groupby(['date', 'elapsed_years_since_arrival'])['employed_total']
                  .sum()
                  .unstack('elapsed_years_since_arrival')
                  .drop(columns=['Not stated / Inadequately described / Born at sea'])
                  .sort_index(axis=1, ascending=False)
                  .reindex(labels=arrived_order, axis='columns')
                  .assign(total = lambda x: x.sum(axis='columns'))
                  .rename_axis(None, axis='columns')
        )

    if month is None:
        return employed
    else:
        idx = employed.index.month == month
        return employed[idx]

```

```python
month = 11 #8 # use 6 for  analysis on an Australian financial year basis; use 12 for calendar, or any month as suits your analysis

employed = make_employed_by_duration(df, month=month)
employed.tail()
```

      Born in Australia
      Arrived within last 5 years
      Arrived 5-9 years ago
      Arrived 10-14 years ago
      Arrived 15-19 years ago
      Arrived 20 or more years ago
      total

      date

      2014-11-30
      8,203.07
      552.03
      696.32
      393.98
      285.64
      1,448.12
      11,579

      2015-11-30
      8,457.76
      543.94
      661.22
      433.44
      355.13
      1,479.17
      11,931

      2016-11-30
      8,417.88
      608.23
      694.28
      506.45
      393.61
      1,430.19
      12,051

      2017-11-30
      8,649.29
      632.69
      741.53
      543.72
      404.26
      1,476.52
      12,448

      2018-11-30
      8,730.16
      678.67
      690.07
      693.67
      403.96
      1,539.80
      12,736

```python
685.40 / 12836
```

    0.053396696790277344

#### Contribution in level terms

```python
# if the 'employed' dataframe data is on an annual basis, set time_delta to 5 (years).
# else if 'employed' is on a monthly basis, set the time_delta to 60 (months == 5 years)
# Comment out the unused time_delta below

if month is not None:
    # employed has annual year data
    time_delta = 5
else:
    # employed has monthly data
    time_delta = 60

idx = ['Born in Australia', 'total' ]

delta = (employed[idx]
             .diff(time_delta)
        )

delta_order = ['Born in Australia',
               'Arrived within last 5 years',
               'arrived_more_than_5_years',
               'total'
              ]

delta = (pd
             .concat([delta, employed['Arrived within last 5 years']], axis='columns')
             .assign(arrived_more_than_5_years = lambda x: x.total - x['Born in Australia'] - x['Arrived within last 5 years'])
             .reindex(labels=delta_order, axis='columns')
        )

delta.tail(7)
```

      Born in Australia
      Arrived within last 5 years
      arrived_more_than_5_years
      total

      date

      2012-11-30
      371.70
      628.86
      -157.17
      843.38

      2013-11-30
      162.93
      559.53
      -33.47
      688.99

      2014-11-30
      294.44
      552.03
      -94.57
      751.90

      2015-11-30
      349.41
      543.94
      -104.58
      788.76

      2016-11-30
      311.29
      608.23
      -105.64
      813.88

      2017-11-30
      514.86
      632.69
      -75.27
      1,072.27

      2018-11-30
      663.60
      678.67
      -51.06
      1,291.21

#### Contribution in percentage terms

```python
idx = ['Born in Australia', 'Arrived within last 5 years', 'arrived_more_than_5_years']

delta_share = (delta[idx]
                   .divide(delta.total, axis='rows') * 100
              )

(delta_share
     .dropna(axis='index', how='any')
     .round(0)
     .astype(int)
     .tail(21)
)
```

      Born in Australia
      Arrived within last 5 years
      arrived_more_than_5_years

      date

      1998-11-30
      78
      28
      -6

      1999-11-30
      74
      31
      -5

      2000-11-30
      71
      45
      -15

      2001-11-30
      72
      36
      -7

      2002-11-30
      74
      31
      -5

      2003-11-30
      74
      32
      -6

      2004-11-30
      72
      34
      -6

      2005-11-30
      69
      31
      0

      2006-11-30
      67
      34
      -1

      2007-11-30
      60
      38
      2

      2008-11-30
      60
      41
      -1

      2009-11-30
      57
      51
      -8

      2010-11-30
      57
      49
      -5

      2011-11-30
      48
      58
      -6

      2012-11-30
      44
      75
      -19

      2013-11-30
      24
      81
      -5

      2014-11-30
      39
      73
      -13

      2015-11-30
      44
      69
      -13

      2016-11-30
      38
      75
      -13

      2017-11-30
      48
      59
      -7

      2018-11-30
      51
      53
      -4

#### How to: read in LM7 as an excel file

```python
# label defintions

col_names = {'Month': 'date',
             'Sex': 'sex',
             'Main English-speaking countries': 'MESC',
             'Elapsed years since arrival': 'elapsed_years_since_arrival',
             'State and territory (STT): ASGS (2011)': 'state',
             "Employed full-time ('000)": 'employed_full_time',
             "Employed part-time ('000)": 'employed_part_time',
             "Unemployed looked for full-time work ('000)": 'unemployed_looked_full_time',
             "Unemployed looked for only part-time work ('000)": 'unemployed_looked_part_time_only',
             "Not in the labour force (NILF) ('000)": 'nilf',
             }

OSB = {'Main English-speaking countries': 'overseas',
       'Other than main English-speaking countries': 'overseas',
       'Australia (includes External Territories)': 'Australia',
       'Not Stated / Inadequately Described / Born at sea': 'unknown'
       }

idx_labor_force = ['employed_full_time', 'employed_part_time', 'unemployed_looked_full_time',
                   'unemployed_looked_part_time_only']
```

```python
%%time
# get data
# The top 3 rows in the sheet 'Data 1' of the LM7 notebook should be unmerged, otherwise, it will take several minutes to read in the data (as opposed to ~6s on my machine machine)

df = (pd
          .read_excel(data_folder / 'LM7.xlsx',
                      usecols='A:J',
                      sheet_name='Data 1',
                      skiprows=3,
                      parse_dates=[0], infer_datetime_format=True,
                      )
          .rename(columns=col_names)
          # derive additional variables
          .assign(date=lambda x: x.date + pd.offsets.MonthEnd(0))
          .assign(COB=lambda x: x.MESC.map(OSB))
          .assign(labor_force=lambda x: x[idx_labor_force].sum(axis=1))
          .assign(employed_total=lambda x: x.employed_full_time + x.employed_part_time)
          .assign(population=lambda x: x.nilf + x.labor_force)
          .set_index('date')
          )

for col in df.select_dtypes(include='object').columns:
        df[col] = df[col].astype('category')

df.tail()
```

    CPU times: user 8.39 s, sys: 75.8 ms, total: 8.46 s
    Wall time: 8.04 s

```python
df.tail()
```

      sex
      MESC
      elapsed_years_since_arrival
      state
      employed_full_time
      employed_part_time
      unemployed_looked_full_time
      unemployed_looked_part_time_only
      nilf
      COB
      labor_force
      employed_total
      population

      date

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      South Australia
      0.00
      0.00
      0.00
      0.00
      15.12
      unknown
      0.00
      0.00
      15.12

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Western Australia
      0.00
      0.00
      0.00
      0.00
      27.58
      unknown
      0.00
      0.00
      27.58

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Tasmania
      0.00
      0.00
      0.00
      0.00
      3.81
      unknown
      0.00
      0.00
      3.81

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Northern Territory
      0.00
      0.00
      0.00
      0.00
      1.92
      unknown
      0.00
      0.00
      1.92

      2018-12-31
      Females
      Not Stated / Inadequately Described / Born at sea
      Not stated / Inadequately described / Born at sea
      Australian Capital Territory
      0.00
      0.00
      0.00
      0.00
      1.32
      unknown
      0.00
      0.00
      1.32

```python

```


# From Variational Monte Carlo to Boltzmann Machines and Machine Learning

**Morten Hjorth-Jensen  Email hjensen@msu.edu  Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University, East Lansing, 48824 MI, USA**

Date: **Notebook 1:  Variational Monte Carlo**

## Introduction

### Structure and Aims

These notebooks serve the aim of linking traditional variational Monte
Carlo VMC calculations methods with recent progress on solving
many-particle problems using Machine Learning algorithms.

Furthermore, when linking with Machine Learning algorithms, in particular
so-called Boltzmann Machines, there are interesting connections between
these algorithms and so-called [Shadow Wave functions (SWFs)](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.90.053304) (and references therein). The implications of the latter have been explored in various Monte Carlo calculations.

In total there are three notebooks:
1. the one you are reading now on Variational Monte Carlo methods,

2. notebook 2 on Machine Learning and quantum mechanical problems and in particular on Boltzmann Machines,

3. and finally notebook 3 on the link between Boltzmann machines and SWFs.

### This notebook

In this notebook the aim is to give you an introduction as well as an
understanding of the basic elements that are needed in order to
develop a professional variational Monte Carlo code. We will focus on
a simple system of two particles in an oscillator trap (or
alternatively two fermions moving in a Coulombic potential) which can
interact via repulsive or attrative force.

The advantage of these systems is that for two particles (boson or
fermions) we have analytical solutions for the eigenpairs for the
non-interacting case. Furthermore, for a two- or three-dimensional
system of two electrons moving in a harmonic oscillator trap, we have
[analytical solutions for the interacting case as well](https://iopscience.iop.org/article/10.1088/0305-4470/27/3/040/meta).

Having analytical eigenpairs is an invaluable feature that allow us
to assess the physical relevance of the trial wave functions, be
these either from a standard VMC procedure, from Boltzmann Machines or
from Shadow Wave functions.

In this notebook we start with the basics of a VMC calculation and
introduce concepts like Markow Chain Monte Carlo methods and the
Metropolis algorithm, importance sampling and Metropolis-Hastings
algorithm, resampling methods to obtain better estimates of the
statistical errors and minimization of the expectation values of the
energy and the variance. The latter is done in order to obtain the
best possible variational parameters. Furthermore it will define the
so-called **cost** function, a commonly encountered quantity in Machine
Learning algorithms. Minimizing the latter is the one which leads to
the determination of the optimal parameters in basically all Machine Learning algorithms.
For our purposes, it will serve as the first link between VMC methods and Machine Learning methods.

Topics like Markov Chain Monte Carlo and various resampling techniques
are also central to Machine Learning methods. Presenting them in the
context of VMC approaches leads hopefully to an easier starting point
for the understanding of these methods.

Finally, the reader may ask what do we actually want to achieve with
complicating life with Machine Learning methods when we can easily
study interacting systems with standard Monte Carlo approaches.  Our
hope is that by adding additional degrees of freedom via Machine
Learning algorithms, we can let the algorithms we employ learn the
parameters of the model via a given optimization algorithm. In
standard Monte Carlo calculations the practitioners end fine tuning
the trial wave function using all possible insights about the system
understudy. This may not always lead to the best possible ansatz and
can in the long run be rather time-consuming. In fields like nuclear
many-body physics with complicated interaction terms, guessing an
analytical form for the trial wave fuction can be difficult. Letting
the machine learn the form of the trial function or find the optimal
parameters may lead to insights about the problem which cannot be
obtained by selecting various trial wave functions.

The emerging and rapidly expanding fields of Machine Learning and Quantum Computing hold also great promise in tackling the
dimensionality problems (the so-called dimensionality curse in many-body problems) we encounter when studying
complicated many-body problems.
The approach to Machine Learning we will focus on
 is inspired by the idea of representing the wave function with
a restricted Boltzmann machine (RBM), presented recently by [G. Carleo and M. Troyer, Science **355**, Issue 6325, pp. 602-606 (2017)](http://science.sciencemag.org/content/355/6325/602). They
named such a wave function/network a *neural network quantum state* (NQS). In their article they apply it to the quantum mechanical
spin lattice systems of the Ising model and Heisenberg model, with
encouraging results.

Machine learning (ML) is an extremely rich field, in spite of its young age. The
increases we have seen during the last three decades in computational
capabilities have been followed by developments of methods and
techniques for analyzing and handling large date sets, relying heavily
on statistics, computer science and mathematics.  The field is rather
new and developing rapidly.
Machine learning is the science of giving computers the ability to
learn without being explicitly programmed.  The idea is that there
exist generic algorithms which can be used to find patterns in a broad
class of data sets without having to write code specifically for each
problem. The algorithm will build its own logic based on the data.

Machine learning is a subfield of computer science, and is closely
related to computational statistics.  It evolved from the study of
pattern recognition in artificial intelligence (AI) research, and has
made contributions to AI tasks like computer vision, natural language
processing and speech recognition. It has also, especially in later
years, found applications in a wide variety of other areas, including
bioinformatics, economy, physics, finance and marketing.
An excellent reference we will come to back to is [Mehta *et al.*, arXiv:1803.08823](https://arxiv.org/abs/1803.08823).

Our focus will first be on the basics of VMC calculations.

## Basic Quantum Monte Carlo

We start with the variational principle.
Given a hamiltonian $H$ and a trial wave function $\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})$, the variational principle states that the expectation value of $\langle H \rangle$, defined through

$$
E[H]= \langle H \rangle =
   \frac{\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R};\boldsymbol{\alpha})H(\boldsymbol{R})\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})}
        {\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R};\boldsymbol{\alpha})\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})},
$$

is an upper bound to the ground state energy $E_0$ of the hamiltonian $H$, that is

$$
E_0 \le \langle H \rangle .
$$

In general, the integrals involved in the calculation of various  expectation values  are multi-dimensional ones. Traditional integration methods such as the Gauss-Legendre will not be adequate for say the  computation of the energy of a many-body system.

Here we have defined the vector $\boldsymbol{R} =[\boldsymbol{r}_1,\boldsymbol{r}_2,\dots,\boldsymbol{r}_n}]$  as an array that contains the positions of all particles $n$ while the vector $\boldsymbol{\alpha} = [\alpha_1,\alpha_2,\dots,\alpha_m]$ contains the variational parameters of the model, $m$ in total.

The trial wave function can be expanded in the eigenstates of the hamiltonian since they form a complete set, viz.,

$$
\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})=\sum_i a_i\Psi_i(\boldsymbol{R}),
$$

and assuming the set of eigenfunctions to be normalized one obtains

$$
\frac{\sum_{nm}a^*_ma_n \int d\boldsymbol{R}\Psi^{\ast}_m(\boldsymbol{R})H(\boldsymbol{R})\Psi_n(\boldsymbol{R})}
        {\sum_{nm}a^*_ma_n \int d\boldsymbol{R}\Psi^{\ast}_m(\boldsymbol{R})\Psi_n(\boldsymbol{R})} =\frac{\sum_{n}a^2_n E_n}
        {\sum_{n}a^2_n} \ge E_0,
$$

where we used that $H(\boldsymbol{R})\Psi_n(\boldsymbol{R})=E_n\Psi_n(\boldsymbol{R})$.
In general, the integrals involved in the calculation of various  expectation
values  are multi-dimensional ones.
The variational principle yields the lowest state of a given symmetry.

In most cases, a wave function has only small values in large parts of
configuration space, and a straightforward procedure which uses
homogenously distributed random points in configuration space
will most likely lead to poor results. This may suggest that some kind
of importance sampling combined with e.g., the Metropolis algorithm
may be  a more efficient way of obtaining the ground state energy.
The hope is then that those regions of configurations space where
the wave function assumes appreciable values are sampled more
efficiently.

The tedious part in a VMC calculation is the search for the variational
minimum. A good knowledge of the system is required in order to carry out
reasonable VMC calculations. This is not always the case,
and often VMC calculations
serve rather as the starting
point for so-called diffusion Monte Carlo calculations (DMC). DMC is a way of
solving exactly the many-body Schroedinger equation by means of
a stochastic procedure. A good guess on the binding energy
and its wave function is however necessary.
A carefully performed VMC calculation can aid in this context.

The basic procedure of a Variational Monte Carlo calculations consists thus of

1. Construct first a trial wave function $\psi_T(\boldsymbol{R};\boldsymbol{\alpha})$,  for a many-body system consisting of $n$ particles located at positions  $\boldsymbol{R}=(\boldsymbol{R}_1,\dots ,\boldsymbol{R}_n)$. The trial wave function depends on $\alpha$ variational parameters $\boldsymbol{\alpha}=(\alpha_1,\dots ,\alpha_M)$.

2. Then we evaluate the expectation value of the hamiltonian $H$

$$
\overline{E}[\boldsymbol{alpha}]=\frac{\int d\boldsymbol{R}\Psi^{\ast}_{T}(\boldsymbol{R},\boldsymbol{\alpha})H(\boldsymbol{R})\Psi_{T}(\boldsymbol{R},\boldsymbol{\alpha})}
        {\int d\boldsymbol{R}\Psi^{\ast}_{T}(\boldsymbol{R},\boldsymbol{\alpha})\Psi_{T}(\boldsymbol{R},\boldsymbol{\alpha})}.
$$

1. Thereafter we vary $\boldsymbol{\alpha}$ according to some minimization algorithm and return eventually to the first step if we are not satisfied with the results.

Here we have used the notation $\overline{E}$ to label the expectation value of the energy.

### Linking with standard statistical expressions for expectation values

In order to bring in the Monte Carlo machinery, we define first a likelihood distribution, or probability density distribution (PDF). Using our ansatz for the trial wave function $\psi_T(\boldsymbol{R};\boldsymbol{\alpha})$.

$$
P(\boldsymbol{R})= \frac{\left|\psi_T(\boldsymbol{R};\boldsymbol{\alpha})\right|^2}{\int \left|\psi_T(\boldsymbol{R};\boldsymbol{\alpha})\right|^2d\boldsymbol{R}}.
$$

This is our new probability distribution function  (PDF).
The approximation to the expectation value of the Hamiltonian is now

$$
\overline{E}[\boldsymbol{\alpha}] =
   \frac{\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R};\boldsymbol{\alpha})H(\boldsymbol{R})\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})}
        {\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R};\boldsymbol{\alpha})\Psi_T(\boldsymbol{R};\boldsymbol{\alpha})}.
$$

Define a new quantity

$$
E_L(\boldsymbol{R};\boldsymbol{\alpha})=\frac{1}{\psi_T(\boldsymbol{R};\boldsymbol{\alpha})}H\psi_T(\boldsymbol{R};\boldsymbol{\alpha}),
\label{eq:locale1} \tag{1}
$$

called the local energy, which, together with our trial PDF yields

$$
\overline{E}[\boldsymbol{\alpha}]=\int P(\boldsymbol{R})E_L(\boldsymbol{R};\boldsymbol{\alpha}) d\boldsymbol{R}\approx \frac{1}{N}\sum_{i=1}^NE_L(\boldsymbol{R_i};\boldsymbol{\alpha})
\label{eq:vmc1} \tag{2}
$$

with $N$ being the number of Monte Carlo samples.

The Algorithm for performing a variational Monte Carlo calculations runs thus as this

   * Initialisation: Fix the number of Monte Carlo steps. Choose an initial $\boldsymbol{R}$ and variational parameters $\alpha$ and calculate $\left|\psi_T^{\alpha}(\boldsymbol{R})\right|^2$.

   * Initialise the energy and the variance and start the Monte Carlo calculation.

      * Calculate  a trial position  $\boldsymbol{R}_p=\boldsymbol{R}+r*step$ where $r$ is a random variable $r \in [0,1]$.

      * Metropolis algorithm to accept or reject this move  $w = P(\boldsymbol{R}_p)/P(\boldsymbol{R})$.

      * If the step is accepted, then we set $\boldsymbol{R}=\boldsymbol{R}_p$.

      * Update averages

   * Finish and compute final averages.

Observe that the jumping in space is governed by the variable *step*. This is called brute-force sampling.
Need importance sampling to get more relevant sampling, see lectures below.

### Simple example, the hydrogen atom

The radial Schroedinger equation for the hydrogen atom can be
written as (when we have gotten rid of the first derivative term in the kinetic energy and used $rR(r)=u(r)$)

$$
-\frac{\hbar^2}{2m}\frac{\partial^2 u(r)}{\partial r^2}-
\left(\frac{ke^2}{r}-\frac{\hbar^2l(l+1)}{2mr^2}\right)u(r)=Eu(r).
$$

or with dimensionless variables

$$
-\frac{1}{2}\frac{\partial^2 u(\rho)}{\partial \rho^2}-
\frac{u(\rho)}{\rho}+\frac{l(l+1)}{2\rho^2}u(\rho)-\lambda u(\rho)=0,
\label{eq:hydrodimless1} \tag{3}
$$

with the hamiltonian

$$
H=-\frac{1}{2}\frac{\partial^2 }{\partial \rho^2}-
\frac{1}{\rho}+\frac{l(l+1)}{2\rho^2}.
$$

Use variational parameter $\alpha$ in the trial
wave function

$$
u_T^{\alpha}(\rho)=\alpha\rho e^{-\alpha\rho}.
\label{eq:trialhydrogen} \tag{4}
$$

Inserting this wave function into the expression for the
local energy $E_L$ gives

$$
E_L(\rho)=-\frac{1}{\rho}-
              \frac{\alpha}{2}\left(\alpha-\frac{2}{\rho}\right).
$$

We note that at $\alpha=1$ we obtain the exact
result, and the variance is zero, as it should. The reason is that
we then have the exact wave function, and the action of the hamiltionan
on the wave function

$$
H\psi = \mathrm{constant}\times \psi,
$$

yields just a constant. The integral which defines various
expectation values involving moments of the hamiltonian becomes then

$$
\langle H^n \rangle =
   \frac{\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R})H^n(\boldsymbol{R})\Psi_T(\boldsymbol{R})}
        {\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R})\Psi_T(\boldsymbol{R})}=
\mathrm{constant}\times\frac{\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R})\Psi_T(\boldsymbol{R})}
        {\int d\boldsymbol{R}\Psi^{\ast}_T(\boldsymbol{R})\Psi_T(\boldsymbol{R})}=\mathrm{constant}.
$$

**This gives an important information: the exact wave function leads to zero variance!**
Variation is then performed by minimizing both the energy and the variance.

## The Metropolis algorithm

The Metropolis algorithm , see [the original article](http://scitation.aip.org/content/aip/journal/jcp/21/6/10.1063/1.1699114) was invented by Metropolis et. al
and is often simply called the Metropolis algorithm.
It is a method to sample a normalized probability
distribution by a stochastic process. We define $\mathbf{P}_i^{(n)}$ to
be the probability for finding the system in the state $i$ at step $n$.
The algorithm is then

* Sample a possible new state $j$ with some probability $T_{i\rightarrow j}$.

* Accept the new state $j$ with probability $A_{i \rightarrow j}$ and use it as the next sample. With probability $1-A_{i\rightarrow j}$ the move is rejected and the original state $i$ is used again as a sample.

We wish to derive the required properties of $T$ and $A$ such that
$\mathbf{P}_i^{(n\rightarrow \infty)} \rightarrow p_i$ so that starting
from any distribution, the method converges to the correct distribution.
Note that the description here is for a discrete probability distribution.
Replacing probabilities $p_i$ with expressions like $p(x_i)dx_i$ will
take all of these over to the corresponding continuum expressions.

The dynamical equation for $\mathbf{P}_i^{(n)}$ can be written directly from
the description above. The probability of being in the state $i$ at step $n$
is given by the probability of being in any state $j$ at the previous step,
and making an accepted transition to $i$ added to the probability of
being in the state $i$, making a transition to any state $j$ and
rejecting the move:

$$
\mathbf{P}^{(n)}_i = \sum_j \left [
\mathbf{P}^{(n-1)}_jT_{j\rightarrow i} A_{j\rightarrow i}
+\mathbf{P}^{(n-1)}_iT_{i\rightarrow j}\left ( 1- A_{i\rightarrow j} \right)
\right ] \,.
$$

Since the probability of making some transition must be 1,
$\sum_j T_{i\rightarrow j} = 1$, and the above equation becomes

$$
\mathbf{P}^{(n)}_i = \mathbf{P}^{(n-1)}_i +
 \sum_j \left [
\mathbf{P}^{(n-1)}_jT_{j\rightarrow i} A_{j\rightarrow i}
-\mathbf{P}^{(n-1)}_iT_{i\rightarrow j}A_{i\rightarrow j}
\right ] \,.
$$

For large $n$ we require that $\mathbf{P}^{(n\rightarrow \infty)}_i = p_i$,
the desired probability distribution. Taking this limit, gives the
balance requirement

$$
\sum_j \left [
p_jT_{j\rightarrow i} A_{j\rightarrow i}
-p_iT_{i\rightarrow j}A_{i\rightarrow j}
\right ] = 0 \,.
$$

The balance requirement is very weak. Typically the much stronger detailed
balance requirement is enforced, that is rather than the sum being
set to zero, we set each term separately to zero and use this
to determine the acceptance probabilities. Rearranging, the result is

$$
\frac{ A_{j\rightarrow i}}{A_{i\rightarrow j}}
= \frac{p_iT_{i\rightarrow j}}{ p_jT_{j\rightarrow i}} \,.
$$

The Metropolis choice is to maximize the $A$ values, that is

$$
A_{j \rightarrow i} = \min \left ( 1,
\frac{p_iT_{i\rightarrow j}}{ p_jT_{j\rightarrow i}}\right ).
$$

Other choices are possible, but they all correspond to multilplying
$A_{i\rightarrow j}$ and $A_{j\rightarrow i}$ by the same constant
smaller than unity.\footnote{The penalty function method uses just such
a factor to compensate for $p_i$ that are evaluated stochastically
and are therefore noisy.}

Having chosen the acceptance probabilities, we have guaranteed that
if the  $\mathbf{P}_i^{(n)}$ has equilibrated, that is if it is equal to $p_i$,
it will remain equilibrated. Next we need to find the circumstances for
convergence to equilibrium.

The dynamical equation can be written as

$$
\mathbf{P}^{(n)}_i = \sum_j M_{ij}\mathbf{P}^{(n-1)}_j
$$

with the matrix $M$ given by

$$
M_{ij} = \delta_{ij}\left [ 1 -\sum_k T_{i\rightarrow k} A_{i \rightarrow k}
\right ] + T_{j\rightarrow i} A_{j\rightarrow i} \,.
$$

Summing over $i$ shows that $\sum_i M_{ij} = 1$, and since
$\sum_k T_{i\rightarrow k} = 1$, and $A_{i \rightarrow k} \leq 1$, the
elements of the matrix satisfy $M_{ij} \geq 0$. The matrix $M$ is therefore
a stochastic matrix.

The Metropolis method is simply the power method for computing the
right eigenvector of $M$ with the largest magnitude eigenvalue.
By construction, the correct probability distribution is a right eigenvector
with eigenvalue 1. Therefore, for the Metropolis method to converge
to this result, we must show that $M$ has only one eigenvalue with this
magnitude, and all other eigenvalues are smaller.

### The system: two electrons in a harmonic oscillator trap in two dimensions

The Hamiltonian of the quantum dot is given by

$$
\hat{H} = \hat{H}_0 + \hat{V},
$$

where $\hat{H}_0$ is the many-body HO Hamiltonian, and $\hat{V}$ is the
inter-electron Coulomb interactions. In dimensionless units,

$$
\hat{V}= \sum_{i < j}^N \frac{1}{r_{ij}},
$$

with $r_{ij}=\sqrt{\mathbf{r}_i^2 - \mathbf{r}_j^2}$.

This leads to the  separable Hamiltonian, with the relative motion part given by ($r_{ij}=r$)

$$
\hat{H}_r=-\nabla^2_r + \frac{1}{4}\omega^2r^2+ \frac{1}{r},
$$

plus a standard Harmonic Oscillator problem  for the center-of-mass motion.
This system has analytical solutions in two and three dimensions ([M. Taut 1993 and 1994](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.48.3561)).

We want to perform  a Variational Monte Carlo calculation of the ground state of two electrons in a quantum dot well with different oscillator energies, assuming total spin $S=0$.
Our trial wave function has the following form

$$
\begin{equation}
   \psi_{T}(\boldsymbol{r}_1,\boldsymbol{r}_2) =
   C\exp{\left(-\alpha_1\omega(r_1^2+r_2^2)/2\right)}
   \exp{\left(\frac{r_{12}}{(1+\alpha_2 r_{12})}\right)},
\label{eq:trial} \tag{5}
\end{equation}
$$

where the $\alpha$s represent our variational parameters, two in this case.

Why does the trial function look like this? How did we get there? **This will be our main motivation** for switching to
Machine Learning.

To find an ansatz for the correlated part of the wave function, it is useful to rewrite the two-particle
local energy in terms of the relative and center-of-mass motion.
Let us denote the distance between the two electrons as
$r_{12}$. We omit the center-of-mass motion since we are only interested in the case when
$r_{12} \rightarrow 0$. The contribution from the center-of-mass (CoM) variable $\boldsymbol{R}_{\mathrm{CoM}}$
gives only a finite contribution.
We focus only on the terms that are relevant for $r_{12}$ and for three dimensions. The relevant local energy becomes then

$$
\lim_{r_{12} \rightarrow 0}E_L(R)=
    \frac{1}{{\cal R}_T(r_{12})}\left(2\frac{d^2}{dr_{ij}^2}+\frac{4}{r_{ij}}\frac{d}{dr_{ij}}+
\frac{2}{r_{ij}}-\frac{l(l+1)}{r_{ij}^2}+2E
\right){\cal R}_T(r_{12}) = 0.
$$

Set $l=0$ and we have the so-called **cusp** condition

$$
\frac{d {\cal R}_T(r_{12})}{dr_{12}} = -\frac{1}{2(l+1)}
{\cal R}_T(r_{12})\qquad r_{12}\to 0
$$

The above  results in

$$
{\cal R}_T  \propto \exp{(r_{ij}/2)},
$$

for anti-parallel spins and

$$
{\cal R}_T  \propto \exp{(r_{ij}/4)},
$$

for anti-parallel spins.
This is the so-called cusp condition for the relative motion, resulting in a minimal requirement
for the correlation part of the wave fuction.
For general systems containing more than say two electrons, we have this
condition for each electron pair $ij$.

```python
python code here
```

## Importance sampling

We need to replace the brute force
Metropolis algorithm with a walk in coordinate space biased by the trial wave function.
This approach is based on the Fokker-Planck equation and the Langevin equation for generating a trajectory in coordinate space.  The link between the Fokker-Planck equation and the Langevin equations are explained, only partly, in the slides below.
An excellent reference on topics like Brownian motion, Markov chains, the Fokker-Planck equation and the Langevin equation is the text by  [Van Kampen](http://www.elsevier.com/books/stochastic-processes-in-physics-and-chemistry/van-kampen/978-0-444-52965-7)
Here we will focus first on the implementation part first.

For a diffusion process characterized by a time-dependent probability density $P(x,t)$ in one dimension the Fokker-Planck
equation reads (for one particle /walker)

$$
\frac{\partial P}{\partial t} = D\frac{\partial }{\partial x}\left(\frac{\partial }{\partial x} -F\right)P(x,t),
$$

where $F$ is a drift term and $D$ is the diffusion coefficient.

The new positions in coordinate space are given as the solutions of the Langevin equation using Euler's method, namely,
we go from the Langevin equation

$$
\frac{\partial x(t)}{\partial t} = DF(x(t)) +\eta,
$$

with $\eta$ a random variable,
yielding a new position

$$
y = x+DF(x)\Delta t +\xi\sqrt{\Delta t},
$$

where $\xi$ is gaussian random variable and $\Delta t$ is a chosen time step.
The quantity $D$ is, in atomic units, equal to $1/2$ and comes from the factor $1/2$ in the kinetic energy operator. Note that $\Delta t$ is to be viewed as a parameter. Values of $\Delta t \in [0.001,0.01]$ yield in general rather stable values of the ground state energy.

The process of isotropic diffusion characterized by a time-dependent probability density $P(\mathbf{x},t)$ obeys (as an approximation) the so-called Fokker-Planck equation

$$
\frac{\partial P}{\partial t} = \sum_i D\frac{\partial }{\partial \mathbf{x_i}}\left(\frac{\partial }{\partial \mathbf{x_i}} -\mathbf{F_i}\right)P(\mathbf{x},t),
$$

where $\mathbf{F_i}$ is the $i^{th}$ component of the drift term (drift velocity) caused by an external potential, and $D$ is the diffusion coefficient. The convergence to a stationary probability density can be obtained by setting the left hand side to zero. The resulting equation will be satisfied if and only if all the terms of the sum are equal zero,

$$
\frac{\partial^2 P}{\partial {\mathbf{x_i}^2}} = P\frac{\partial}{\partial {\mathbf{x_i}}}\mathbf{F_i} + \mathbf{F_i}\frac{\partial}{\partial {\mathbf{x_i}}}P.
$$

The drift vector should be of the form $\mathbf{F} = g(\mathbf{x}) \frac{\partial P}{\partial \mathbf{x}}$. Then,

$$
\frac{\partial^2 P}{\partial {\mathbf{x_i}^2}} = P\frac{\partial g}{\partial P}\left( \frac{\partial P}{\partial {\mathbf{x}_i}}  \right)^2 + P g \frac{\partial ^2 P}{\partial {\mathbf{x}_i^2}}  + g \left( \frac{\partial P}{\partial {\mathbf{x}_i}}  \right)^2.
$$

The condition of stationary density means that the left hand side equals zero. In other words, the terms containing first and second derivatives have to cancel each other. It is possible only if $g = \frac{1}{P}$, which yields

$$
\mathbf{F} = 2\frac{1}{\Psi_T}\nabla\Psi_T,
$$

which is known as the so-called *quantum force*. This term is responsible for pushing the walker towards regions of configuration space where the trial wave function is large, increasing the efficiency of the simulation in contrast to the Metropolis algorithm where the walker has the same probability of moving in every direction.

The Fokker-Planck equation yields a (the solution to the equation) transition probability given by the Green's function

$$
G(y,x,\Delta t) = \frac{1}{(4\pi D\Delta t)^{3N/2}} \exp{\left(-(y-x-D\Delta t F(x))^2/4D\Delta t\right)}
$$

which in turn means that our brute force Metropolis algorithm

$$
A(y,x) = \mathrm{min}(1,q(y,x))),
$$

with $q(y,x) = |\Psi_T(y)|^2/|\Psi_T(x)|^2$ is now replaced by the [Metropolis-Hastings algorithm](http://scitation.aip.org/content/aip/journal/jcp/21/6/10.1063/1.1699114) as well as [Hasting's article](http://biomet.oxfordjournals.org/content/57/1/97.abstract),

$$
q(y,x) = \frac{G(x,y,\Delta t)|\Psi_T(y)|^2}{G(y,x,\Delta t)|\Psi_T(x)|^2}
$$

```python
add python code here
```

## Technical aspect, improvements and how to define the cost function

**The above procedure is not the smartest one**. Looping over all variational parameters becomes expensive.
Also, we don't use importance sampling and optimizations of the standard deviation (blocking, bootstrap, jackknife).
Such codes are included in the above Github address.

We can also be smarter and use minimization methods to find the **optimal** variational parameters with fewer Monte Carlo cycles and then
fire up our heavy artillery.

One way to achieve this is to minimize the energy as function of the variational parameters.

To find the derivatives of the local energy expectation value as function of the variational parameters, we can use the chain rule and the hermiticity of the Hamiltonian.

Let us define

$$
\bar{E}_{\alpha_i}=\frac{d\langle  E_L\rangle}{d\alpha_i}.
$$

as the derivative of the energy with respect to the variational parameter $\alpha_i$
We define also the derivative of the trial function (skipping the subindex $T$) as

$$
\bar{\Psi}_{i}=\frac{d\Psi}{d\alpha_i}.
$$

The elements of the gradient of the local energy are then (using the chain rule and the hermiticity of the Hamiltonian)

$$
\bar{E}_{i}= 2\left( \langle \frac{\bar{\Psi}_{i}}{\Psi}E_L\rangle -\langle \frac{\bar{\Psi}_{i}}{\Psi}\rangle\langle E_L \rangle\right).
$$

From a computational point of view it means that you need to compute the expectation values of

$$
\langle \frac{\bar{\Psi}_{i}}{\Psi}E_L\rangle,
$$

and

$$
\langle \frac{\bar{\Psi}_{i}}{\Psi}\rangle\langle E_L\rangle
$$

These integrals are evaluted using MC intergration (with all its possible error sources).
We can then use methods like stochastic gradient or other minimization methods to find the optimal variational parameters (I don't discuss this topic here, but these methods are very important in ML).

We have a model, our likelihood function.

How should we define the cost function?

Suppose the trial function (our model) is the exact wave function. The action of the hamiltionan
on the wave function

$$
H\Psi = \mathrm{constant}\times \Psi,
$$

The integral which defines various
expectation values involving moments of the hamiltonian becomes then

$$
\langle E^n \rangle =   \langle H^n \rangle =
   \frac{\int d\boldsymbol{R}\Psi^{\ast}(\boldsymbol{R})H^n(\boldsymbol{R})\Psi(\boldsymbol{R})}
        {\int d\boldsymbol{R}\Psi^{\ast}(\boldsymbol{R})\Psi(\boldsymbol{R})}=
\mathrm{constant}\times\frac{\int d\boldsymbol{R}\Psi^{\ast}(\boldsymbol{R})\Psi(\boldsymbol{R})}
        {\int d\boldsymbol{R}\Psi^{\ast}(\boldsymbol{R})\Psi(\boldsymbol{R})}=\mathrm{constant}.
$$

**This gives an important information: If I want the variance, the exact wave function leads to zero variance!**
The variance is defined as

$$
\sigma_E = \langle E^2\rangle - \langle E\rangle^2.
$$

Variation is then performed by minimizing both the energy and the variance.

We can then take the derivatives of

$$
\sigma_E = \langle E^2\rangle - \langle E\rangle^2,
$$

with respect to the variational parameters. The derivatives of the variance can then be used to defined the
so-called Hessian matrix, which in turn allows us to use minimization methods like Newton's method or
standard gradient methods.

This leads to however a more complicated expression, with obvious errors when evaluating integrals by Monte Carlo integration. Less used, see however [Filippi and Umrigar](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.94.150201). The expression becomes complicated

$$
\bar{E}_{ij} = 2\left[ \langle (\frac{\bar{\Psi}_{ij}}{\Psi}+\frac{\bar{\Psi}_{j}}{\Psi}\frac{\bar{\Psi}_{i}}{\Psi})(E_L-\langle E\rangle)\rangle -\langle \frac{\bar{\Psi}_{i}}{\Psi}\rangle\bar{E}_j-\langle \frac{\bar{\Psi}_{j}}{\Psi}\rangle\bar{E}_i\right] +\langle \frac{\bar{\Psi}_{i}}{\Psi}E_L{_j}\rangle +\langle \frac{\bar{\Psi}_{j}}{\Psi}E_L{_i}\rangle -\langle \frac{\bar{\Psi}_{i}}{\Psi}\rangle\langle E_L{_j}\rangle \langle \frac{\bar{\Psi}_{j}}{\Psi}\rangle\langle E_L{_i}\rangle.
$$

Evaluating the cost function means having to evaluate the above second derivative of the energy.