Massimo Nocentini

December 1, 2016: `longest_common_subsequence`

November 29, 2016: `matrix_product_ordering`

November 22, 2016: `edit_distance`

September 21, 2016: Monge arrays

Abstract

Some examples of *dynamic programming*.

```python
from functools import lru_cache
import operator
from itertools import count, zip_longest, chain
from collections import OrderedDict
from random import randint
```

```python
def memo_holder(optimizer):
    def f(*args, **kwds):
        return_memo_table = kwds.pop('memo_table', False)
        pair = optimized, memo_table = optimizer(*args, **kwds)
        return pair if return_memo_table else optimized
    return f
```

# DP references

- Dynamic Programming - From Novice to Advanced, by Dumitru topcoder member ([link][dumitru-topcoder])

[dumitru-topcoder]:https://www.topcoder.com/community/data-science/data-science-tutorials/dynamic-programming-from-novice-to-advanced/

# Monge arrays

Have a look at https://en.wikipedia.org/wiki/Monge_array

```python
def parity_numbered_rows(matrix, parity, include_index=False):
    start = 0 if parity == 'even' else 1
    return [(i, r) if include_index else r
            for i in range(start, len(matrix), 2)
            for r in [matrix[i]]]

def argmin(iterable, only_index=True):
    index, minimum = index_min = min(enumerate(iterable), key=operator.itemgetter(1))
    return index if only_index else index_min

def interleaving(one, another):
    for o, a in zip_longest(one, another):
        yield o
        if a: yield a

def is_sorted(iterable, pred=lambda l, g: l <= g):
    _, *rest = iterable
    return all(pred(l, g) for l, g in zip(iterable, rest))

def minima_indexes(matrix):

    if len(matrix) == 1: return [argmin(matrix.pop())]

    recursion = minima_indexes(parity_numbered_rows(matrix, parity='even'))
    even_minima = OrderedDict((i, m) for i, m in zip(count(start=0, step=2), recursion))
    odd_minima = [argmin(odd_r[start:end]) + start
                  for o, odd_r in parity_numbered_rows(matrix, parity='odd', include_index=True)
                  for start in [even_minima[o-1]]
                  for end in [even_minima[o+1]+1 if o+1 in even_minima else None]]

    return list(interleaving(even_minima.values(), odd_minima))

def minima(matrix):
    return [matrix[i][m] for i, m in enumerate(minima_indexes(matrix))]

def is_not_monge(matrix):
    return any(any(matrix[r][m] > matrix[r][i] for i in range(m))
               for r, m in enumerate(minima_indexes(matrix)))

```

The following *is* a Monge array:

```python
matrix = [
    [10, 17, 13, 28, 23],
    [17, 22, 16, 29, 23],
    [24, 28, 22, 34, 24],
    [11, 13, 6, 17, 7],
    [45, 44, 32, 37, 23],
    [36, 33, 19, 21, 6],
    [75, 66, 51, 53, 34],
]

minima(matrix)
```

    [10, 16, 22, 6, 23, 6, 34]

```python
minima_indexes(matrix)
```

    [0, 2, 2, 2, 4, 4, 4]

```python
is_not_monge(matrix)
```

    False

The following *is not* a Monge array:

```python
matrix = [
    [37, 23, 22, 32],
    [21, 6, 7, 10],
    [53, 34, 30, 31],
    [32, 13, 9, 6],
    [43, 21, 15, 8],
]

minima(matrix) # produces a wrong answer!!!
```

    [22, 7, 30, 6, 8]

```python
minima_indexes(matrix)
```

    [2, 2, 2, 3, 3]

```python
is_not_monge(matrix)
```

    True

# `longest_increasing_subsequence`

```python
@memo_holder
def longest_increasing_subsequence(seq):

    L = []

    for i, current in enumerate(seq):
        """opt, arg = max([(l, j) for (l, j) in L[:i] if l[-1] < current],
                       key=lambda p: len(p[0]),
                       default=([], tuple()))
        L.append(opt + [current], (arg, i))"""
        L.append(max(filter(lambda prefix: prefix[-1] < current, L[:i]), key=len, default=[]) + [current])

    return max(L, key=len), L

def lis_rec(seq):

    @lru_cache(maxsize=None)
    def rec(i):
        current = seq[i]
        return max([rec(j) for j in range(i) if seq[j] < current], key=len, default=[]) + [current]

    return max([rec(i) for i, _ in enumerate(seq)], key=len)

```

a simple test case taken from page 157:

```python
seq = [5, 2, 8, 6, 3, 6, 9, 7] # see page 157
```

```python
subseq, memo_table = longest_increasing_subsequence(seq, memo_table=True)
```

```python
subseq
```

    [2, 3, 6, 9]

memoization table shows that `[2,3,6,7]` is another solution:

```python
memo_table
```

    [[5], [2], [5, 8], [5, 6], [2, 3], [2, 3, 6], [2, 3, 6, 9], [2, 3, 6, 7]]

```python
lis_rec(seq)
```

    [2, 3, 6, 9]

The following is an average case where the sequence is generated randomly:

```python
length = int(5e3)
seq = [randint(0, length) for _ in range(length)]
```

```python
%timeit longest_increasing_subsequence(seq)
```

    1 loop, best of 3: 3.73 s per loop

```python
%timeit lis_rec(seq)
```

    1 loop, best of 3: 3.06 s per loop

worst scenario where the sequence is a sorted list, in increasing order:

```python
seq = range(length)
```

```python
%timeit longest_increasing_subsequence(seq)
```

    1 loop, best of 3: 3.94 s per loop

```python
%timeit lis_rec(seq)
```

    1 loop, best of 3: 5.32 s per loop

# `edit_distance`

```python
@memo_holder
def edit_distance(xs, ys,
                  gap_in_xs=lambda y: 1, # cost of putting a gap in `xs` when reading `y`
                  gap_in_ys=lambda x: 1, # cost of putting a gap in `ys` when reading `x`
                  mismatch=lambda x, y: 1,   # cost of mismatch (x, y) in the sense of `==`
                  gap = '▢',
                  mark=lambda s: s.swapcase(),
                  reduce=sum):

    T = {}

    T.update({ (i, 0):(xs[:i], gap * i, i) for i in range(len(xs)+1) })
    T.update({ (0, j):( gap * j,ys[:j], j) for j in range(len(ys)+1) })

    def combine(w, z):
        a, b, c = zip(w, z)
        return ''.join(a), ''.join(b), reduce(c)

    for i, x in enumerate(xs, start=1):
        for j, y in enumerate(ys, start=1):
             T[i, j] = min(combine(T[i-1, j], (x, gap, gap_in_ys(x))),
                           combine(T[i, j-1], (gap, y, gap_in_xs(y))),
                           combine(T[i-1, j-1], (x, y, 0) if x == y else (mark(x), mark(y), mismatch(x, y))),
                           key=lambda t: t[2])

    return T[len(xs), len(ys)], T
```

```python
(xs, ys, cost), memo_table = edit_distance('exponential', 'polynomial', memo_table=True)
```

```python
print('edit with cost {}:\n\n{}\n{}'.format(cost, xs, ys))
```

    edit with cost 6:

    expoNEnT▢ial
    ▢▢poLYnOmial

```python
memo_table
```

    {(0, 0): ('', '', 0),
     (0, 1): ('▢', 'p', 1),
     (0, 2): ('▢▢', 'po', 2),
     (0, 3): ('▢▢▢', 'pol', 3),
     (0, 4): ('▢▢▢▢', 'poly', 4),
     (0, 5): ('▢▢▢▢▢', 'polyn', 5),
     (0, 6): ('▢▢▢▢▢▢', 'polyno', 6),
     (0, 7): ('▢▢▢▢▢▢▢', 'polynom', 7),
     (0, 8): ('▢▢▢▢▢▢▢▢', 'polynomi', 8),
     (0, 9): ('▢▢▢▢▢▢▢▢▢', 'polynomia', 9),
     (0, 10): ('▢▢▢▢▢▢▢▢▢▢', 'polynomial', 10),
     (1, 0): ('e', '▢', 1),
     (1, 1): ('E', 'P', 1),
     (1, 2): ('E▢', 'Po', 2),
     (1, 3): ('E▢▢', 'Pol', 3),
     (1, 4): ('E▢▢▢', 'Poly', 4),
     (1, 5): ('E▢▢▢▢', 'Polyn', 5),
     (1, 6): ('E▢▢▢▢▢', 'Polyno', 6),
     (1, 7): ('E▢▢▢▢▢▢', 'Polynom', 7),
     (1, 8): ('E▢▢▢▢▢▢▢', 'Polynomi', 8),
     (1, 9): ('E▢▢▢▢▢▢▢▢', 'Polynomia', 9),
     (1, 10): ('E▢▢▢▢▢▢▢▢▢', 'Polynomial', 10),
     (2, 0): ('ex', '▢▢', 2),
     (2, 1): ('Ex', 'P▢', 2),
     (2, 2): ('EX', 'PO', 2),
     (2, 3): ('EX▢', 'POl', 3),
     (2, 4): ('EX▢▢', 'POly', 4),
     (2, 5): ('EX▢▢▢', 'POlyn', 5),
     (2, 6): ('EX▢▢▢▢', 'POlyno', 6),
     (2, 7): ('EX▢▢▢▢▢', 'POlynom', 7),
     (2, 8): ('EX▢▢▢▢▢▢', 'POlynomi', 8),
     (2, 9): ('EX▢▢▢▢▢▢▢', 'POlynomia', 9),
     (2, 10): ('EX▢▢▢▢▢▢▢▢', 'POlynomial', 10),
     (3, 0): ('exp', '▢▢▢', 3),
     (3, 1): ('exp', '▢▢p', 2),
     (3, 2): ('EXp', 'PO▢', 3),
     (3, 3): ('EXP', 'POL', 3),
     (3, 4): ('EXP▢', 'POLy', 4),
     (3, 5): ('EXP▢▢', 'POLyn', 5),
     (3, 6): ('EXP▢▢▢', 'POLyno', 6),
     (3, 7): ('EXP▢▢▢▢', 'POLynom', 7),
     (3, 8): ('EXP▢▢▢▢▢', 'POLynomi', 8),
     (3, 9): ('EXP▢▢▢▢▢▢', 'POLynomia', 9),
     (3, 10): ('EXP▢▢▢▢▢▢▢', 'POLynomial', 10),
     (4, 0): ('expo', '▢▢▢▢', 4),
     (4, 1): ('expo', '▢▢p▢', 3),
     (4, 2): ('expo', '▢▢po', 2),
     (4, 3): ('expo▢', '▢▢pol', 3),
     (4, 4): ('expo▢▢', '▢▢poly', 4),
     (4, 5): ('expo▢▢▢', '▢▢polyn', 5),
     (4, 6): ('EXP▢▢o', 'POLyno', 5),
     (4, 7): ('EXP▢▢o▢', 'POLynom', 6),
     (4, 8): ('EXP▢▢o▢▢', 'POLynomi', 7),
     (4, 9): ('EXP▢▢o▢▢▢', 'POLynomia', 8),
     (4, 10): ('EXP▢▢o▢▢▢▢', 'POLynomial', 9),
     (5, 0): ('expon', '▢▢▢▢▢', 5),
     (5, 1): ('expon', '▢▢p▢▢', 4),
     (5, 2): ('expon', '▢▢po▢', 3),
     (5, 3): ('expoN', '▢▢poL', 3),
     (5, 4): ('expoN▢', '▢▢poLy', 4),
     (5, 5): ('expo▢▢n', '▢▢polyn', 4),
     (5, 6): ('expo▢▢n▢', '▢▢polyno', 5),
     (5, 7): ('expo▢▢n▢▢', '▢▢polynom', 6),
     (5, 8): ('expo▢▢n▢▢▢', '▢▢polynomi', 7),
     (5, 9): ('expo▢▢n▢▢▢▢', '▢▢polynomia', 8),
     (5, 10): ('expo▢▢n▢▢▢▢▢', '▢▢polynomial', 9),
     (6, 0): ('expone', '▢▢▢▢▢▢', 6),
     (6, 1): ('expone', '▢▢p▢▢▢', 5),
     (6, 2): ('expone', '▢▢po▢▢', 4),
     (6, 3): ('expoNe', '▢▢poL▢', 4),
     (6, 4): ('expoNE', '▢▢poLY', 4),
     (6, 5): ('expo▢▢ne', '▢▢polyn▢', 5),
     (6, 6): ('expo▢▢nE', '▢▢polynO', 5),
     (6, 7): ('expo▢▢nE▢', '▢▢polynOm', 6),
     (6, 8): ('expo▢▢nE▢▢', '▢▢polynOmi', 7),
     (6, 9): ('expo▢▢nE▢▢▢', '▢▢polynOmia', 8),
     (6, 10): ('expo▢▢nE▢▢▢▢', '▢▢polynOmial', 9),
     (7, 0): ('exponen', '▢▢▢▢▢▢▢', 7),
     (7, 1): ('exponen', '▢▢p▢▢▢▢', 6),
     (7, 2): ('exponen', '▢▢po▢▢▢', 5),
     (7, 3): ('expoNen', '▢▢poL▢▢', 5),
     (7, 4): ('expoNEn', '▢▢poLY▢', 5),
     (7, 5): ('expoNEn', '▢▢poLYn', 4),
     (7, 6): ('expoNEn▢', '▢▢poLYno', 5),
     (7, 7): ('expoNEn▢▢', '▢▢poLYnom', 6),
     (7, 8): ('expoNEn▢▢▢', '▢▢poLYnomi', 7),
     (7, 9): ('expoNEn▢▢▢▢', '▢▢poLYnomia', 8),
     (7, 10): ('expoNEn▢▢▢▢▢', '▢▢poLYnomial', 9),
     (8, 0): ('exponent', '▢▢▢▢▢▢▢▢', 8),
     (8, 1): ('exponent', '▢▢p▢▢▢▢▢', 7),
     (8, 2): ('exponent', '▢▢po▢▢▢▢', 6),
     (8, 3): ('expoNent', '▢▢poL▢▢▢', 6),
     (8, 4): ('expoNEnt', '▢▢poLY▢▢', 6),
     (8, 5): ('expoNEnt', '▢▢poLYn▢', 5),
     (8, 6): ('expoNEnT', '▢▢poLYnO', 5),
     (8, 7): ('expoNEnT▢', '▢▢poLYnOm', 6),
     (8, 8): ('expoNEnT▢▢', '▢▢poLYnOmi', 7),
     (8, 9): ('expoNEnT▢▢▢', '▢▢poLYnOmia', 8),
     (8, 10): ('expoNEnT▢▢▢▢', '▢▢poLYnOmial', 9),
     (9, 0): ('exponenti', '▢▢▢▢▢▢▢▢▢', 9),
     (9, 1): ('exponenti', '▢▢p▢▢▢▢▢▢', 8),
     (9, 2): ('exponenti', '▢▢po▢▢▢▢▢', 7),
     (9, 3): ('expoNenti', '▢▢poL▢▢▢▢', 7),
     (9, 4): ('expoNEnti', '▢▢poLY▢▢▢', 7),
     (9, 5): ('expoNEnti', '▢▢poLYn▢▢', 6),
     (9, 6): ('expoNEnTi', '▢▢poLYnO▢', 6),
     (9, 7): ('expoNEnTI', '▢▢poLYnOM', 6),
     (9, 8): ('expoNEnT▢i', '▢▢poLYnOmi', 6),
     (9, 9): ('expoNEnT▢i▢', '▢▢poLYnOmia', 7),
     (9, 10): ('expoNEnT▢i▢▢', '▢▢poLYnOmial', 8),
     (10, 0): ('exponentia', '▢▢▢▢▢▢▢▢▢▢', 10),
     (10, 1): ('exponentia', '▢▢p▢▢▢▢▢▢▢', 9),
     (10, 2): ('exponentia', '▢▢po▢▢▢▢▢▢', 8),
     (10, 3): ('expoNentia', '▢▢poL▢▢▢▢▢', 8),
     (10, 4): ('expoNEntia', '▢▢poLY▢▢▢▢', 8),
     (10, 5): ('expoNEntia', '▢▢poLYn▢▢▢', 7),
     (10, 6): ('expoNEnTia', '▢▢poLYnO▢▢', 7),
     (10, 7): ('expoNEnTIa', '▢▢poLYnOM▢', 7),
     (10, 8): ('expoNEnT▢ia', '▢▢poLYnOmi▢', 7),
     (10, 9): ('expoNEnT▢ia', '▢▢poLYnOmia', 6),
     (10, 10): ('expoNEnT▢ia▢', '▢▢poLYnOmial', 7),
     (11, 0): ('exponential', '▢▢▢▢▢▢▢▢▢▢▢', 11),
     (11, 1): ('exponential', '▢▢p▢▢▢▢▢▢▢▢', 10),
     (11, 2): ('exponential', '▢▢po▢▢▢▢▢▢▢', 9),
     (11, 3): ('exponential', '▢▢po▢▢▢▢▢▢l', 8),
     (11, 4): ('expoNEntial', '▢▢poLY▢▢▢▢▢', 9),
     (11, 5): ('expoNEntial', '▢▢poLYn▢▢▢▢', 8),
     (11, 6): ('expoNEnTial', '▢▢poLYnO▢▢▢', 8),
     (11, 7): ('expoNEnTIal', '▢▢poLYnOM▢▢', 8),
     (11, 8): ('expoNEnT▢ial', '▢▢poLYnOmi▢▢', 8),
     (11, 9): ('expoNEnT▢ial', '▢▢poLYnOmia▢', 7),
     (11, 10): ('expoNEnT▢ial', '▢▢poLYnOmial', 6)}

```python
(xs, ys, cost), memo_table = edit_distance('exponential', 'polynomial', memo_table=True, mismatch=lambda x,y: 10)
```

```python
print('edit with cost {}:\n\n{}\n{}'.format(cost, xs, ys))
```

    edit with cost 9:

    expo▢▢n▢▢ential
    ▢▢polynom▢▢▢ial

```python
memo_table
```

    {(0, 0): ('', '', 0),
     (0, 1): ('▢', 'p', 1),
     (0, 2): ('▢▢', 'po', 2),
     (0, 3): ('▢▢▢', 'pol', 3),
     (0, 4): ('▢▢▢▢', 'poly', 4),
     (0, 5): ('▢▢▢▢▢', 'polyn', 5),
     (0, 6): ('▢▢▢▢▢▢', 'polyno', 6),
     (0, 7): ('▢▢▢▢▢▢▢', 'polynom', 7),
     (0, 8): ('▢▢▢▢▢▢▢▢', 'polynomi', 8),
     (0, 9): ('▢▢▢▢▢▢▢▢▢', 'polynomia', 9),
     (0, 10): ('▢▢▢▢▢▢▢▢▢▢', 'polynomial', 10),
     (1, 0): ('e', '▢', 1),
     (1, 1): ('▢e', 'p▢', 2),
     (1, 2): ('▢▢e', 'po▢', 3),
     (1, 3): ('▢▢▢e', 'pol▢', 4),
     (1, 4): ('▢▢▢▢e', 'poly▢', 5),
     (1, 5): ('▢▢▢▢▢e', 'polyn▢', 6),
     (1, 6): ('▢▢▢▢▢▢e', 'polyno▢', 7),
     (1, 7): ('▢▢▢▢▢▢▢e', 'polynom▢', 8),
     (1, 8): ('▢▢▢▢▢▢▢▢e', 'polynomi▢', 9),
     (1, 9): ('▢▢▢▢▢▢▢▢▢e', 'polynomia▢', 10),
     (1, 10): ('▢▢▢▢▢▢▢▢▢▢e', 'polynomial▢', 11),
     (2, 0): ('ex', '▢▢', 2),
     (2, 1): ('▢ex', 'p▢▢', 3),
     (2, 2): ('▢▢ex', 'po▢▢', 4),
     (2, 3): ('▢▢▢ex', 'pol▢▢', 5),
     (2, 4): ('▢▢▢▢ex', 'poly▢▢', 6),
     (2, 5): ('▢▢▢▢▢ex', 'polyn▢▢', 7),
     (2, 6): ('▢▢▢▢▢▢ex', 'polyno▢▢', 8),
     (2, 7): ('▢▢▢▢▢▢▢ex', 'polynom▢▢', 9),
     (2, 8): ('▢▢▢▢▢▢▢▢ex', 'polynomi▢▢', 10),
     (2, 9): ('▢▢▢▢▢▢▢▢▢ex', 'polynomia▢▢', 11),
     (2, 10): ('▢▢▢▢▢▢▢▢▢▢ex', 'polynomial▢▢', 12),
     (3, 0): ('exp', '▢▢▢', 3),
     (3, 1): ('exp', '▢▢p', 2),
     (3, 2): ('exp▢', '▢▢po', 3),
     (3, 3): ('exp▢▢', '▢▢pol', 4),
     (3, 4): ('exp▢▢▢', '▢▢poly', 5),
     (3, 5): ('exp▢▢▢▢', '▢▢polyn', 6),
     (3, 6): ('exp▢▢▢▢▢', '▢▢polyno', 7),
     (3, 7): ('exp▢▢▢▢▢▢', '▢▢polynom', 8),
     (3, 8): ('exp▢▢▢▢▢▢▢', '▢▢polynomi', 9),
     (3, 9): ('exp▢▢▢▢▢▢▢▢', '▢▢polynomia', 10),
     (3, 10): ('exp▢▢▢▢▢▢▢▢▢', '▢▢polynomial', 11),
     (4, 0): ('expo', '▢▢▢▢', 4),
     (4, 1): ('expo', '▢▢p▢', 3),
     (4, 2): ('expo', '▢▢po', 2),
     (4, 3): ('expo▢', '▢▢pol', 3),
     (4, 4): ('expo▢▢', '▢▢poly', 4),
     (4, 5): ('expo▢▢▢', '▢▢polyn', 5),
     (4, 6): ('expo▢▢▢▢', '▢▢polyno', 6),
     (4, 7): ('expo▢▢▢▢▢', '▢▢polynom', 7),
     (4, 8): ('expo▢▢▢▢▢▢', '▢▢polynomi', 8),
     (4, 9): ('expo▢▢▢▢▢▢▢', '▢▢polynomia', 9),
     (4, 10): ('expo▢▢▢▢▢▢▢▢', '▢▢polynomial', 10),
     (5, 0): ('expon', '▢▢▢▢▢', 5),
     (5, 1): ('expon', '▢▢p▢▢', 4),
     (5, 2): ('expon', '▢▢po▢', 3),
     (5, 3): ('expo▢n', '▢▢pol▢', 4),
     (5, 4): ('expo▢▢n', '▢▢poly▢', 5),
     (5, 5): ('expo▢▢n', '▢▢polyn', 4),
     (5, 6): ('expo▢▢n▢', '▢▢polyno', 5),
     (5, 7): ('expo▢▢n▢▢', '▢▢polynom', 6),
     (5, 8): ('expo▢▢n▢▢▢', '▢▢polynomi', 7),
     (5, 9): ('expo▢▢n▢▢▢▢', '▢▢polynomia', 8),
     (5, 10): ('expo▢▢n▢▢▢▢▢', '▢▢polynomial', 9),
     (6, 0): ('expone', '▢▢▢▢▢▢', 6),
     (6, 1): ('expone', '▢▢p▢▢▢', 5),
     (6, 2): ('expone', '▢▢po▢▢', 4),
     (6, 3): ('expo▢ne', '▢▢pol▢▢', 5),
     (6, 4): ('expo▢▢ne', '▢▢poly▢▢', 6),
     (6, 5): ('expo▢▢ne', '▢▢polyn▢', 5),
     (6, 6): ('expo▢▢n▢e', '▢▢polyno▢', 6),
     (6, 7): ('expo▢▢n▢▢e', '▢▢polynom▢', 7),
     (6, 8): ('expo▢▢n▢▢▢e', '▢▢polynomi▢', 8),
     (6, 9): ('expo▢▢n▢▢▢▢e', '▢▢polynomia▢', 9),
     (6, 10): ('expo▢▢n▢▢▢▢▢e', '▢▢polynomial▢', 10),
     (7, 0): ('exponen', '▢▢▢▢▢▢▢', 7),
     (7, 1): ('exponen', '▢▢p▢▢▢▢', 6),
     (7, 2): ('exponen', '▢▢po▢▢▢', 5),
     (7, 3): ('expo▢nen', '▢▢pol▢▢▢', 6),
     (7, 4): ('expo▢▢nen', '▢▢poly▢▢▢', 7),
     (7, 5): ('expo▢▢nen', '▢▢polyn▢▢', 6),
     (7, 6): ('expo▢▢n▢en', '▢▢polyno▢▢', 7),
     (7, 7): ('expo▢▢n▢▢en', '▢▢polynom▢▢', 8),
     (7, 8): ('expo▢▢n▢▢▢en', '▢▢polynomi▢▢', 9),
     (7, 9): ('expo▢▢n▢▢▢▢en', '▢▢polynomia▢▢', 10),
     (7, 10): ('expo▢▢n▢▢▢▢▢en', '▢▢polynomial▢▢', 11),
     (8, 0): ('exponent', '▢▢▢▢▢▢▢▢', 8),
     (8, 1): ('exponent', '▢▢p▢▢▢▢▢', 7),
     (8, 2): ('exponent', '▢▢po▢▢▢▢', 6),
     (8, 3): ('expo▢nent', '▢▢pol▢▢▢▢', 7),
     (8, 4): ('expo▢▢nent', '▢▢poly▢▢▢▢', 8),
     (8, 5): ('expo▢▢nent', '▢▢polyn▢▢▢', 7),
     (8, 6): ('expo▢▢n▢ent', '▢▢polyno▢▢▢', 8),
     (8, 7): ('expo▢▢n▢▢ent', '▢▢polynom▢▢▢', 9),
     (8, 8): ('expo▢▢n▢▢▢ent', '▢▢polynomi▢▢▢', 10),
     (8, 9): ('expo▢▢n▢▢▢▢ent', '▢▢polynomia▢▢▢', 11),
     (8, 10): ('expo▢▢n▢▢▢▢▢ent', '▢▢polynomial▢▢▢', 12),
     (9, 0): ('exponenti', '▢▢▢▢▢▢▢▢▢', 9),
     (9, 1): ('exponenti', '▢▢p▢▢▢▢▢▢', 8),
     (9, 2): ('exponenti', '▢▢po▢▢▢▢▢', 7),
     (9, 3): ('expo▢nenti', '▢▢pol▢▢▢▢▢', 8),
     (9, 4): ('expo▢▢nenti', '▢▢poly▢▢▢▢▢', 9),
     (9, 5): ('expo▢▢nenti', '▢▢polyn▢▢▢▢', 8),
     (9, 6): ('expo▢▢n▢enti', '▢▢polyno▢▢▢▢', 9),
     (9, 7): ('expo▢▢n▢▢enti', '▢▢polynom▢▢▢▢', 10),
     (9, 8): ('expo▢▢n▢▢enti', '▢▢polynom▢▢▢i', 9),
     (9, 9): ('expo▢▢n▢▢enti▢', '▢▢polynom▢▢▢ia', 10),
     (9, 10): ('expo▢▢n▢▢enti▢▢', '▢▢polynom▢▢▢ial', 11),
     (10, 0): ('exponentia', '▢▢▢▢▢▢▢▢▢▢', 10),
     (10, 1): ('exponentia', '▢▢p▢▢▢▢▢▢▢', 9),
     (10, 2): ('exponentia', '▢▢po▢▢▢▢▢▢', 8),
     (10, 3): ('expo▢nentia', '▢▢pol▢▢▢▢▢▢', 9),
     (10, 4): ('expo▢▢nentia', '▢▢poly▢▢▢▢▢▢', 10),
     (10, 5): ('expo▢▢nentia', '▢▢polyn▢▢▢▢▢', 9),
     (10, 6): ('expo▢▢n▢entia', '▢▢polyno▢▢▢▢▢', 10),
     (10, 7): ('expo▢▢n▢▢entia', '▢▢polynom▢▢▢▢▢', 11),
     (10, 8): ('expo▢▢n▢▢entia', '▢▢polynom▢▢▢i▢', 10),
     (10, 9): ('expo▢▢n▢▢entia', '▢▢polynom▢▢▢ia', 9),
     (10, 10): ('expo▢▢n▢▢entia▢', '▢▢polynom▢▢▢ial', 10),
     (11, 0): ('exponential', '▢▢▢▢▢▢▢▢▢▢▢', 11),
     (11, 1): ('exponential', '▢▢p▢▢▢▢▢▢▢▢', 10),
     (11, 2): ('exponential', '▢▢po▢▢▢▢▢▢▢', 9),
     (11, 3): ('exponential', '▢▢po▢▢▢▢▢▢l', 8),
     (11, 4): ('exponential▢', '▢▢po▢▢▢▢▢▢ly', 9),
     (11, 5): ('expo▢▢nential', '▢▢polyn▢▢▢▢▢▢', 10),
     (11, 6): ('expo▢▢n▢ential', '▢▢polyno▢▢▢▢▢▢', 11),
     (11, 7): ('expo▢▢n▢▢ential', '▢▢polynom▢▢▢▢▢▢', 12),
     (11, 8): ('expo▢▢n▢▢ential', '▢▢polynom▢▢▢i▢▢', 11),
     (11, 9): ('expo▢▢n▢▢ential', '▢▢polynom▢▢▢ia▢', 10),
     (11, 10): ('expo▢▢n▢▢ential', '▢▢polynom▢▢▢ial', 9)}

# `matrix_product_ordering`

```python
@memo_holder
def matrix_product_ordering(o, w, op):

    n = len(o)
    T = {(i,i):(lambda: o[i], 0) for i in range(n)}

    def combine(i,r,j):
        t_ir, c_ir = T[i, r]
        t_rj, c_rj = T[r+1, j]
        return (lambda: op(t_ir(), t_rj()), c_ir + c_rj + w(i, r+1, j+1)) # w[i]*w[r+1]*w[j+1])

    for d in range(1, n):
        for i in range(n-d):
            j = i + d
            T[i, j] = min([combine(i, r, j) for r in range(i, j)], key=lambda t: t[-1])

    opt, cost = T[0, n-1]
    return (opt(), cost), T

def parens_str_proxy(w, **kwds):
    return matrix_product_ordering(o=['▢']*(len(w)-1),
                                   w=lambda l, c, r: w[l]*w[c]*w[r],
                                   op=lambda a, b: '({} {})'.format(a, b),
                                   **kwds)
```

```python
(opt, cost), memo_table = parens_str_proxy(w={0:100, 1:20, 2:1000, 3:2, 4:50}, memo_table=True)
```

```python
opt, cost
```

    ('((▢ (▢ ▢)) ▢)', 54000)

```python
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
```

    {(0, 0): ('▢', 0),
     (0, 1): ('(▢ ▢)', 2000000),
     (0, 2): ('(▢ (▢ ▢))', 44000),
     (0, 3): ('((▢ (▢ ▢)) ▢)', 54000),
     (1, 1): ('▢', 0),
     (1, 2): ('(▢ ▢)', 40000),
     (1, 3): ('((▢ ▢) ▢)', 42000),
     (2, 2): ('▢', 0),
     (2, 3): ('(▢ ▢)', 100000),
     (3, 3): ('▢', 0)}

```python
(opt, cost), memo_table = parens_str_proxy(w={i:(i+1) for i in range(10)}, memo_table=True)
```

```python
opt, cost
```

    ('((((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 328)

```python
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
```

    {(0, 0): ('▢', 0),
     (0, 1): ('(▢ ▢)', 6),
     (0, 2): ('((▢ ▢) ▢)', 18),
     (0, 3): ('(((▢ ▢) ▢) ▢)', 38),
     (0, 4): ('((((▢ ▢) ▢) ▢) ▢)', 68),
     (0, 5): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 110),
     (0, 6): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 166),
     (0, 7): ('(((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 238),
     (0, 8): ('((((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 328),
     (1, 1): ('▢', 0),
     (1, 2): ('(▢ ▢)', 24),
     (1, 3): ('((▢ ▢) ▢)', 64),
     (1, 4): ('(((▢ ▢) ▢) ▢)', 124),
     (1, 5): ('((((▢ ▢) ▢) ▢) ▢)', 208),
     (1, 6): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 320),
     (1, 7): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 464),
     (1, 8): ('(((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 644),
     (2, 2): ('▢', 0),
     (2, 3): ('(▢ ▢)', 60),
     (2, 4): ('((▢ ▢) ▢)', 150),
     (2, 5): ('(((▢ ▢) ▢) ▢)', 276),
     (2, 6): ('((((▢ ▢) ▢) ▢) ▢)', 444),
     (2, 7): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 660),
     (2, 8): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 930),
     (3, 3): ('▢', 0),
     (3, 4): ('(▢ ▢)', 120),
     (3, 5): ('((▢ ▢) ▢)', 288),
     (3, 6): ('(((▢ ▢) ▢) ▢)', 512),
     (3, 7): ('((((▢ ▢) ▢) ▢) ▢)', 800),
     (3, 8): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 1160),
     (4, 4): ('▢', 0),
     (4, 5): ('(▢ ▢)', 210),
     (4, 6): ('((▢ ▢) ▢)', 490),
     (4, 7): ('(((▢ ▢) ▢) ▢)', 850),
     (4, 8): ('((((▢ ▢) ▢) ▢) ▢)', 1300),
     (5, 5): ('▢', 0),
     (5, 6): ('(▢ ▢)', 336),
     (5, 7): ('((▢ ▢) ▢)', 768),
     (5, 8): ('(((▢ ▢) ▢) ▢)', 1308),
     (6, 6): ('▢', 0),
     (6, 7): ('(▢ ▢)', 504),
     (6, 8): ('((▢ ▢) ▢)', 1134),
     (7, 7): ('▢', 0),
     (7, 8): ('(▢ ▢)', 720),
     (8, 8): ('▢', 0)}

http://oeis.org/A180118

```python
from sympy import fibonacci, Matrix, init_printing

init_printing()
```

```python
(opt, cost), memo_table = parens_str_proxy(w={i:fibonacci(i+1) for i in range(10)}, memo_table=True)
```

```python
opt, cost
```

    ('((((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 3024)

```python
{k:(thunk(), cost) for k,(thunk, cost) in memo_table.items()}
```

    {(0, 0): ('▢', 0),
     (0, 1): ('(▢ ▢)', 2),
     (0, 2): ('((▢ ▢) ▢)', 8),
     (0, 3): ('(((▢ ▢) ▢) ▢)', 23),
     (0, 4): ('((((▢ ▢) ▢) ▢) ▢)', 63),
     (0, 5): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 167),
     (0, 6): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 440),
     (0, 7): ('(((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 1154),
     (0, 8): ('((((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 3024),
     (1, 1): ('▢', 0),
     (1, 2): ('(▢ ▢)', 6),
     (1, 3): ('((▢ ▢) ▢)', 21),
     (1, 4): ('(((▢ ▢) ▢) ▢)', 61),
     (1, 5): ('((((▢ ▢) ▢) ▢) ▢)', 165),
     (1, 6): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 438),
     (1, 7): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 1152),
     (1, 8): ('(((((((▢ ▢) ▢) ▢) ▢) ▢) ▢) ▢)', 3022),
     (2, 2): ('▢', 0),
     (2, 3): ('(▢ ▢)', 30),
     (2, 4): ('((▢ ▢) ▢)', 110),
     (2, 5): ('(((▢ ▢) ▢) ▢)', 318),
     (2, 6): ('((((▢ ▢) ▢) ▢) ▢)', 864),
     (2, 7): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 2292),
     (2, 8): ('((((((▢ ▢) ▢) ▢) ▢) ▢) ▢)', 6032),
     (3, 3): ('▢', 0),
     (3, 4): ('(▢ ▢)', 120),
     (3, 5): ('((▢ ▢) ▢)', 432),
     (3, 6): ('(((▢ ▢) ▢) ▢)', 1251),
     (3, 7): ('((((▢ ▢) ▢) ▢) ▢)', 3393),
     (3, 8): ('(((((▢ ▢) ▢) ▢) ▢) ▢)', 9003),
     (4, 4): ('▢', 0),
     (4, 5): ('(▢ ▢)', 520),
     (4, 6): ('((▢ ▢) ▢)', 1885),
     (4, 7): ('(((▢ ▢) ▢) ▢)', 5455),
     (4, 8): ('((((▢ ▢) ▢) ▢) ▢)', 14805),
     (5, 5): ('▢', 0),
     (5, 6): ('(▢ ▢)', 2184),
     (5, 7): ('((▢ ▢) ▢)', 7896),
     (5, 8): ('(((▢ ▢) ▢) ▢)', 22856),
     (6, 6): ('▢', 0),
     (6, 7): ('(▢ ▢)', 9282),
     (6, 8): ('((▢ ▢) ▢)', 33592),
     (7, 7): ('▢', 0),
     (7, 8): ('(▢ ▢)', 39270),
     (8, 8): ('▢', 0)}

http://oeis.org/A180664

```python
def to_matrix_cost(dim, memo_table):
    n, m = dim
    return Matrix(n, m, lambda n,k: memo_table.get((n, k), (lambda: None, 0))[-1])
```

```python
to_matrix_cost(dim=(9,9), memo_table=memo_table)
```

# `longest_common_subsequence`

```python
@memo_holder
def longest_common_subsequence(A, B, gap_A, gap_B,
                               equal=lambda a, b: 1, shrink_A=lambda a: 0, shrink_B=lambda b: 0,
                               reduce=sum):

    T = {}

    T.update({(i, 0):([gap_A]*i, 0) for i in range(len(A)+1)})
    T.update({(0, j):([gap_B]*j, 0) for j in range(len(B)+1)})

    def combine(w, z):
        alpha, beta = zip(w, z)
        return list(chain.from_iterable(alpha)), reduce(beta)

    for i, a in enumerate(A, start=1):
        for j, b in enumerate(B, start=1):
            T[i, j] = combine(T[i-1, j-1], ([a], equal(a, b))) if a == b else max(
                combine(T[i, j-1], ([gap_B], -shrink_B(b))),
                combine(T[i-1, j], ([gap_A], -shrink_A(a))),
                key=lambda t: t[-1])

    opt, cost = T[len(A), len(B)]
    return (opt, cost), T

def pprint_memo_table(T, joiner, do=str):
    return {k:(joiner.join(map(do, v[0])), v[1]) for k, v in T.items()}
```

```python
(opt, cost), memo_table = longest_common_subsequence(A='ADCAAB',
                                                     B='BAABDCDCAACACBA', gap_A='▢', gap_B='○',
                                                     #shrink_B=lambda b:1,
                                                     memo_table=True)
```

```python
print('BAABDCDCAACACBA')
print(''.join(opt))
```

    BAABDCDCAACACBA
    ○○A○○○DC○A○A○B○

```python
pprint_memo_table(memo_table, joiner='')
```

    {(0, 0): ('', 0),
     (0, 1): ('○', 0),
     (0, 2): ('○○', 0),
     (0, 3): ('○○○', 0),
     (0, 4): ('○○○○', 0),
     (0, 5): ('○○○○○', 0),
     (0, 6): ('○○○○○○', 0),
     (0, 7): ('○○○○○○○', 0),
     (0, 8): ('○○○○○○○○', 0),
     (0, 9): ('○○○○○○○○○', 0),
     (0, 10): ('○○○○○○○○○○', 0),
     (0, 11): ('○○○○○○○○○○○', 0),
     (0, 12): ('○○○○○○○○○○○○', 0),
     (0, 13): ('○○○○○○○○○○○○○', 0),
     (0, 14): ('○○○○○○○○○○○○○○', 0),
     (0, 15): ('○○○○○○○○○○○○○○○', 0),
     (1, 0): ('▢', 0),
     (1, 1): ('▢○', 0),
     (1, 2): ('○A', 1),
     (1, 3): ('○○A', 1),
     (1, 4): ('○○A○', 1),
     (1, 5): ('○○A○○', 1),
     (1, 6): ('○○A○○○', 1),
     (1, 7): ('○○A○○○○', 1),
     (1, 8): ('○○A○○○○○', 1),
     (1, 9): ('○○○○○○○○A', 1),
     (1, 10): ('○○○○○○○○○A', 1),
     (1, 11): ('○○○○○○○○○A○', 1),
     (1, 12): ('○○○○○○○○○○○A', 1),
     (1, 13): ('○○○○○○○○○○○A○', 1),
     (1, 14): ('○○○○○○○○○○○A○○', 1),
     (1, 15): ('○○○○○○○○○○○○○○A', 1),
     (2, 0): ('▢▢', 0),
     (2, 1): ('▢▢○', 0),
     (2, 2): ('○A▢', 1),
     (2, 3): ('○A▢○', 1),
     (2, 4): ('○A▢○○', 1),
     (2, 5): ('○○A○D', 2),
     (2, 6): ('○○A○D○', 2),
     (2, 7): ('○○A○○○D', 2),
     (2, 8): ('○○A○○○D○', 2),
     (2, 9): ('○○A○○○D○○', 2),
     (2, 10): ('○○A○○○D○○○', 2),
     (2, 11): ('○○A○○○D○○○○', 2),
     (2, 12): ('○○A○○○D○○○○○', 2),
     (2, 13): ('○○A○○○D○○○○○○', 2),
     (2, 14): ('○○A○○○D○○○○○○○', 2),
     (2, 15): ('○○A○○○D○○○○○○○○', 2),
     (3, 0): ('▢▢▢', 0),
     (3, 1): ('▢▢▢○', 0),
     (3, 2): ('○A▢▢', 1),
     (3, 3): ('○A▢▢○', 1),
     (3, 4): ('○A▢▢○○', 1),
     (3, 5): ('○○A○D▢', 2),
     (3, 6): ('○○A○DC', 3),
     (3, 7): ('○○A○DC○', 3),
     (3, 8): ('○○A○○○DC', 3),
     (3, 9): ('○○A○○○DC○', 3),
     (3, 10): ('○○A○○○DC○○', 3),
     (3, 11): ('○○A○○○D○○○C', 3),
     (3, 12): ('○○A○○○D○○○C○', 3),
     (3, 13): ('○○A○○○D○○○○○C', 3),
     (3, 14): ('○○A○○○D○○○○○C○', 3),
     (3, 15): ('○○A○○○D○○○○○C○○', 3),
     (4, 0): ('▢▢▢▢', 0),
     (4, 1): ('▢▢▢▢○', 0),
     (4, 2): ('▢▢▢○A', 1),
     (4, 3): ('○A▢▢A', 2),
     (4, 4): ('○A▢▢A○', 2),
     (4, 5): ('○A▢▢A○○', 2),
     (4, 6): ('○○A○DC▢', 3),
     (4, 7): ('○○A○DC▢○', 3),
     (4, 8): ('○○A○DC▢○○', 3),
     (4, 9): ('○○A○○○DCA', 4),
     (4, 10): ('○○A○○○DC○A', 4),
     (4, 11): ('○○A○○○DC○A○', 4),
     (4, 12): ('○○A○○○D○○○CA', 4),
     (4, 13): ('○○A○○○D○○○CA○', 4),
     (4, 14): ('○○A○○○D○○○CA○○', 4),
     (4, 15): ('○○A○○○D○○○○○C○A', 4),
     (5, 0): ('▢▢▢▢▢', 0),
     (5, 1): ('▢▢▢▢▢○', 0),
     (5, 2): ('▢▢▢▢○A', 1),
     (5, 3): ('▢▢▢○AA', 2),
     (5, 4): ('▢▢▢○AA○', 2),
     (5, 5): ('▢▢▢○AA○○', 2),
     (5, 6): ('○○A○DC▢▢', 3),
     (5, 7): ('○○A○DC▢▢○', 3),
     (5, 8): ('○○A○DC▢▢○○', 3),
     (5, 9): ('○○A○DC▢○○A', 4),
     (5, 10): ('○○A○○○DCAA', 5),
     (5, 11): ('○○A○○○DCAA○', 5),
     (5, 12): ('○○A○○○DC○A○A', 5),
     (5, 13): ('○○A○○○DC○A○A○', 5),
     (5, 14): ('○○A○○○DC○A○A○○', 5),
     (5, 15): ('○○A○○○D○○○CA○○A', 5),
     (6, 0): ('▢▢▢▢▢▢', 0),
     (6, 1): ('▢▢▢▢▢B', 1),
     (6, 2): ('▢▢▢▢▢B○', 1),
     (6, 3): ('▢▢▢○AA▢', 2),
     (6, 4): ('▢▢▢○AAB', 3),
     (6, 5): ('▢▢▢○AAB○', 3),
     (6, 6): ('▢▢▢○AAB○○', 3),
     (6, 7): ('▢▢▢○AAB○○○', 3),
     (6, 8): ('▢▢▢○AAB○○○○', 3),
     (6, 9): ('○○A○DC▢○○A▢', 4),
     (6, 10): ('○○A○○○DCAA▢', 5),
     (6, 11): ('○○A○○○DCAA▢○', 5),
     (6, 12): ('○○A○○○DCAA▢○○', 5),
     (6, 13): ('○○A○○○DCAA▢○○○', 5),
     (6, 14): ('○○A○○○DC○A○A○B', 6),
     (6, 15): ('○○A○○○DC○A○A○B○', 6)}

```python
(opt, cost), memo_table = longest_common_subsequence(A=[0,1,1,2,3,5,8,13,21,34,55],
                                                     B=[1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440,],
                                                     gap_A='▢', gap_B='○',
                                                     #shrink_B=lambda b:1,
                                                     memo_table=True)
```

```python
print(','.join(map(str, opt)))
```

    ▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○,○,○

```python
pprint_memo_table(memo_table, joiner=',')
```

    {(0, 0): ('', 0),
     (0, 1): ('○', 0),
     (0, 2): ('○,○', 0),
     (0, 3): ('○,○,○', 0),
     (0, 4): ('○,○,○,○', 0),
     (0, 5): ('○,○,○,○,○', 0),
     (0, 6): ('○,○,○,○,○,○', 0),
     (0, 7): ('○,○,○,○,○,○,○', 0),
     (0, 8): ('○,○,○,○,○,○,○,○', 0),
     (0, 9): ('○,○,○,○,○,○,○,○,○', 0),
     (0, 10): ('○,○,○,○,○,○,○,○,○,○', 0),
     (0, 11): ('○,○,○,○,○,○,○,○,○,○,○', 0),
     (0, 12): ('○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (0, 13): ('○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (0, 14): ('○,○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (0, 15): ('○,○,○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 0): ('▢', 0),
     (1, 1): ('▢,○', 0),
     (1, 2): ('▢,○,○', 0),
     (1, 3): ('▢,○,○,○', 0),
     (1, 4): ('▢,○,○,○,○', 0),
     (1, 5): ('▢,○,○,○,○,○', 0),
     (1, 6): ('▢,○,○,○,○,○,○', 0),
     (1, 7): ('▢,○,○,○,○,○,○,○', 0),
     (1, 8): ('▢,○,○,○,○,○,○,○,○', 0),
     (1, 9): ('▢,○,○,○,○,○,○,○,○,○', 0),
     (1, 10): ('▢,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 11): ('▢,○,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 12): ('▢,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 13): ('▢,○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 14): ('▢,○,○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (1, 15): ('▢,○,○,○,○,○,○,○,○,○,○,○,○,○,○,○', 0),
     (2, 0): ('▢,▢', 0),
     (2, 1): ('▢,1', 1),
     (2, 2): ('▢,○,1', 1),
     (2, 3): ('▢,○,1,○', 1),
     (2, 4): ('▢,○,1,○,○', 1),
     (2, 5): ('▢,○,1,○,○,○', 1),
     (2, 6): ('▢,○,1,○,○,○,○', 1),
     (2, 7): ('▢,○,1,○,○,○,○,○', 1),
     (2, 8): ('▢,○,1,○,○,○,○,○,○', 1),
     (2, 9): ('▢,○,1,○,○,○,○,○,○,○', 1),
     (2, 10): ('▢,○,1,○,○,○,○,○,○,○,○', 1),
     (2, 11): ('▢,○,1,○,○,○,○,○,○,○,○,○', 1),
     (2, 12): ('▢,○,1,○,○,○,○,○,○,○,○,○,○', 1),
     (2, 13): ('▢,○,1,○,○,○,○,○,○,○,○,○,○,○', 1),
     (2, 14): ('▢,○,1,○,○,○,○,○,○,○,○,○,○,○,○', 1),
     (2, 15): ('▢,○,1,○,○,○,○,○,○,○,○,○,○,○,○,○', 1),
     (3, 0): ('▢,▢,▢', 0),
     (3, 1): ('▢,▢,1', 1),
     (3, 2): ('▢,1,1', 2),
     (3, 3): ('▢,1,1,○', 2),
     (3, 4): ('▢,1,1,○,○', 2),
     (3, 5): ('▢,1,1,○,○,○', 2),
     (3, 6): ('▢,1,1,○,○,○,○', 2),
     (3, 7): ('▢,1,1,○,○,○,○,○', 2),
     (3, 8): ('▢,1,1,○,○,○,○,○,○', 2),
     (3, 9): ('▢,1,1,○,○,○,○,○,○,○', 2),
     (3, 10): ('▢,1,1,○,○,○,○,○,○,○,○', 2),
     (3, 11): ('▢,1,1,○,○,○,○,○,○,○,○,○', 2),
     (3, 12): ('▢,1,1,○,○,○,○,○,○,○,○,○,○', 2),
     (3, 13): ('▢,1,1,○,○,○,○,○,○,○,○,○,○,○', 2),
     (3, 14): ('▢,1,1,○,○,○,○,○,○,○,○,○,○,○,○', 2),
     (3, 15): ('▢,1,1,○,○,○,○,○,○,○,○,○,○,○,○,○', 2),
     (4, 0): ('▢,▢,▢,▢', 0),
     (4, 1): ('▢,▢,1,▢', 1),
     (4, 2): ('▢,1,1,▢', 2),
     (4, 3): ('▢,1,1,2', 3),
     (4, 4): ('▢,1,1,2,○', 3),
     (4, 5): ('▢,1,1,2,○,○', 3),
     (4, 6): ('▢,1,1,2,○,○,○', 3),
     (4, 7): ('▢,1,1,2,○,○,○,○', 3),
     (4, 8): ('▢,1,1,2,○,○,○,○,○', 3),
     (4, 9): ('▢,1,1,2,○,○,○,○,○,○', 3),
     (4, 10): ('▢,1,1,2,○,○,○,○,○,○,○', 3),
     (4, 11): ('▢,1,1,2,○,○,○,○,○,○,○,○', 3),
     (4, 12): ('▢,1,1,2,○,○,○,○,○,○,○,○,○', 3),
     (4, 13): ('▢,1,1,2,○,○,○,○,○,○,○,○,○,○', 3),
     (4, 14): ('▢,1,1,2,○,○,○,○,○,○,○,○,○,○,○', 3),
     (4, 15): ('▢,1,1,2,○,○,○,○,○,○,○,○,○,○,○,○', 3),
     (5, 0): ('▢,▢,▢,▢,▢', 0),
     (5, 1): ('▢,▢,1,▢,▢', 1),
     (5, 2): ('▢,1,1,▢,▢', 2),
     (5, 3): ('▢,1,1,2,▢', 3),
     (5, 4): ('▢,1,1,2,▢,○', 3),
     (5, 5): ('▢,1,1,2,▢,○,○', 3),
     (5, 6): ('▢,1,1,2,▢,○,○,○', 3),
     (5, 7): ('▢,1,1,2,▢,○,○,○,○', 3),
     (5, 8): ('▢,1,1,2,▢,○,○,○,○,○', 3),
     (5, 9): ('▢,1,1,2,▢,○,○,○,○,○,○', 3),
     (5, 10): ('▢,1,1,2,▢,○,○,○,○,○,○,○', 3),
     (5, 11): ('▢,1,1,2,▢,○,○,○,○,○,○,○,○', 3),
     (5, 12): ('▢,1,1,2,▢,○,○,○,○,○,○,○,○,○', 3),
     (5, 13): ('▢,1,1,2,▢,○,○,○,○,○,○,○,○,○,○', 3),
     (5, 14): ('▢,1,1,2,▢,○,○,○,○,○,○,○,○,○,○,○', 3),
     (5, 15): ('▢,1,1,2,▢,○,○,○,○,○,○,○,○,○,○,○,○', 3),
     (6, 0): ('▢,▢,▢,▢,▢,▢', 0),
     (6, 1): ('▢,▢,1,▢,▢,▢', 1),
     (6, 2): ('▢,1,1,▢,▢,▢', 2),
     (6, 3): ('▢,1,1,2,▢,▢', 3),
     (6, 4): ('▢,1,1,2,▢,5', 4),
     (6, 5): ('▢,1,1,2,▢,5,○', 4),
     (6, 6): ('▢,1,1,2,▢,5,○,○', 4),
     (6, 7): ('▢,1,1,2,▢,5,○,○,○', 4),
     (6, 8): ('▢,1,1,2,▢,5,○,○,○,○', 4),
     (6, 9): ('▢,1,1,2,▢,5,○,○,○,○,○', 4),
     (6, 10): ('▢,1,1,2,▢,5,○,○,○,○,○,○', 4),
     (6, 11): ('▢,1,1,2,▢,5,○,○,○,○,○,○,○', 4),
     (6, 12): ('▢,1,1,2,▢,5,○,○,○,○,○,○,○,○', 4),
     (6, 13): ('▢,1,1,2,▢,5,○,○,○,○,○,○,○,○,○', 4),
     (6, 14): ('▢,1,1,2,▢,5,○,○,○,○,○,○,○,○,○,○', 4),
     (6, 15): ('▢,1,1,2,▢,5,○,○,○,○,○,○,○,○,○,○,○', 4),
     (7, 0): ('▢,▢,▢,▢,▢,▢,▢', 0),
     (7, 1): ('▢,▢,1,▢,▢,▢,▢', 1),
     (7, 2): ('▢,1,1,▢,▢,▢,▢', 2),
     (7, 3): ('▢,1,1,2,▢,▢,▢', 3),
     (7, 4): ('▢,1,1,2,▢,5,▢', 4),
     (7, 5): ('▢,1,1,2,▢,5,▢,○', 4),
     (7, 6): ('▢,1,1,2,▢,5,▢,○,○', 4),
     (7, 7): ('▢,1,1,2,▢,5,▢,○,○,○', 4),
     (7, 8): ('▢,1,1,2,▢,5,▢,○,○,○,○', 4),
     (7, 9): ('▢,1,1,2,▢,5,▢,○,○,○,○,○', 4),
     (7, 10): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○', 4),
     (7, 11): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○,○', 4),
     (7, 12): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○,○,○', 4),
     (7, 13): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○,○,○,○', 4),
     (7, 14): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○,○,○,○,○', 4),
     (7, 15): ('▢,1,1,2,▢,5,▢,○,○,○,○,○,○,○,○,○,○,○', 4),
     (8, 0): ('▢,▢,▢,▢,▢,▢,▢,▢', 0),
     (8, 1): ('▢,▢,1,▢,▢,▢,▢,▢', 1),
     (8, 2): ('▢,1,1,▢,▢,▢,▢,▢', 2),
     (8, 3): ('▢,1,1,2,▢,▢,▢,▢', 3),
     (8, 4): ('▢,1,1,2,▢,5,▢,▢', 4),
     (8, 5): ('▢,1,1,2,▢,5,▢,▢,○', 4),
     (8, 6): ('▢,1,1,2,▢,5,▢,▢,○,○', 4),
     (8, 7): ('▢,1,1,2,▢,5,▢,▢,○,○,○', 4),
     (8, 8): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○', 4),
     (8, 9): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○', 4),
     (8, 10): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○', 4),
     (8, 11): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○,○', 4),
     (8, 12): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○,○,○', 4),
     (8, 13): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○,○,○,○', 4),
     (8, 14): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○,○,○,○,○', 4),
     (8, 15): ('▢,1,1,2,▢,5,▢,▢,○,○,○,○,○,○,○,○,○,○,○', 4),
     (9, 0): ('▢,▢,▢,▢,▢,▢,▢,▢,▢', 0),
     (9, 1): ('▢,▢,1,▢,▢,▢,▢,▢,▢', 1),
     (9, 2): ('▢,1,1,▢,▢,▢,▢,▢,▢', 2),
     (9, 3): ('▢,1,1,2,▢,▢,▢,▢,▢', 3),
     (9, 4): ('▢,1,1,2,▢,5,▢,▢,▢', 4),
     (9, 5): ('▢,1,1,2,▢,5,▢,▢,▢,○', 4),
     (9, 6): ('▢,1,1,2,▢,5,▢,▢,▢,○,○', 4),
     (9, 7): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○', 4),
     (9, 8): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○', 4),
     (9, 9): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○', 4),
     (9, 10): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○', 4),
     (9, 11): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○,○', 4),
     (9, 12): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○,○,○', 4),
     (9, 13): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○,○,○,○', 4),
     (9, 14): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○,○,○,○,○', 4),
     (9, 15): ('▢,1,1,2,▢,5,▢,▢,▢,○,○,○,○,○,○,○,○,○,○,○', 4),
     (10, 0): ('▢,▢,▢,▢,▢,▢,▢,▢,▢,▢', 0),
     (10, 1): ('▢,▢,1,▢,▢,▢,▢,▢,▢,▢', 1),
     (10, 2): ('▢,1,1,▢,▢,▢,▢,▢,▢,▢', 2),
     (10, 3): ('▢,1,1,2,▢,▢,▢,▢,▢,▢', 3),
     (10, 4): ('▢,1,1,2,▢,5,▢,▢,▢,▢', 4),
     (10, 5): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○', 4),
     (10, 6): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○', 4),
     (10, 7): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○', 4),
     (10, 8): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○', 4),
     (10, 9): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○', 4),
     (10, 10): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○', 4),
     (10, 11): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○,○', 4),
     (10, 12): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○,○,○', 4),
     (10, 13): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○', 4),
     (10, 14): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○,○', 4),
     (10, 15): ('▢,1,1,2,▢,5,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○,○,○', 4),
     (11, 0): ('▢,▢,▢,▢,▢,▢,▢,▢,▢,▢,▢', 0),
     (11, 1): ('▢,▢,1,▢,▢,▢,▢,▢,▢,▢,▢', 1),
     (11, 2): ('▢,1,1,▢,▢,▢,▢,▢,▢,▢,▢', 2),
     (11, 3): ('▢,1,1,2,▢,▢,▢,▢,▢,▢,▢', 3),
     (11, 4): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢', 4),
     (11, 5): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○', 4),
     (11, 6): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○', 4),
     (11, 7): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○', 4),
     (11, 8): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○', 4),
     (11, 9): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○', 4),
     (11, 10): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○', 4),
     (11, 11): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○', 4),
     (11, 12): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○,○', 4),
     (11, 13): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○', 4),
     (11, 14): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○,○', 4),
     (11, 15): ('▢,1,1,2,▢,5,▢,▢,▢,▢,▢,○,○,○,○,○,○,○,○,○,○,○', 4)}

---

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


# Three-stage Least Squares (3SLS)

This example demonstrates how a system of simultaneous equations can be jointly estimated using three-stage least squares (3SLS).  The simultaneous equations model the wage and number of hours worked.  The two equations are

$$
\begin{eqnarray}
hours & = & \beta_0 + \beta_1 \ln(wage) + \beta_2 educ + \beta_3 age + \beta_4 kidslt6 + \beta_5 nwifeinc + \epsilon^h_i
\\
\ln(wage) & = & \gamma_0 + \gamma_1 hours + \gamma_2 educ + \gamma_3 educ^2 + \gamma_4 exper + \epsilon^w_i
\end{eqnarray}
$$

Each equation has a single exogenous variables.  The instruments for the endogenous variables are the regressors that appear in one equation but not the other.

## Data

The data set is the MORZ data set from Wooldridge (2002).

```python
from linearmodels.datasets import mroz

data = mroz.load()
```

Here the relevant variables are selected and missing observations are dropped to avoid warnings.

```python
data = data[
    ["hours", "educ", "age", "kidslt6", "nwifeinc", "lwage", "exper", "expersq"]
]
data = data.dropna()
```

The main models are imported:

* `IV2SLS` - single equation 2-stage least squares
* `IV3SLS` - system estimation using instrumental variables
* `SUR` - system estimation without endogenous variables

```python
from linearmodels import IV2SLS, IV3SLS, SUR, IVSystemGMM
```

## Formulas

These examples use the formula interface.  This is usually simpler when models have exogenous regressors, endogenous regressors and instruments.  The syntax is the same as in the 2SLS models.

```python
hours = "hours ~ educ + age + kidslt6 + nwifeinc + [lwage ~ exper + expersq]"

hours_mod = IV2SLS.from_formula(hours, data)
hours_res = hours_mod.fit(cov_type="unadjusted")
print(hours_res)
```

The $\ln$ wage model can be similarly specified and estimated

```python
lwage = "lwage ~ educ + exper + expersq + [hours ~ age + kidslt6 + nwifeinc]"

lwage_mod = IV2SLS.from_formula(lwage, data)
lwage_res = lwage_mod.fit(cov_type="unadjusted")
print(lwage_res)
```

A system can be specified using a dictionary for formulas.  The dictionary keys are used as equation labels. Aside from this simple change, the syntax is identical.

Here the model is estimated using `method="ols"` which will just simultaneously estimate the two equations but will produce estimates that are identical to separate equations.

```python
equations = dict(hours=hours, lwage=lwage)
system_2sls = IV3SLS.from_formula(equations, data)
system_2sls_res = system_2sls.fit(method="ols", cov_type="unadjusted")
print(system_2sls_res)
```

Using `method="gls"` will use GLS estimates which can be more efficient than the usual estimates. Here only the first equation changes.  This is due to the structure of the problem.

```python
equations = dict(hours=hours, lwage=lwage)
system_3sls = IV3SLS.from_formula(equations, data)
system_3sls_res = system_3sls.fit(method="gls", cov_type="unadjusted")
print(system_3sls_res)
```

## Direct Model Specification

The model can be directly specified using a dictionary of dictionaries where the inner dictionaries contain the 4 components of the model:

* dependent - The dependent variable
* exog - Exogenous regressors
* endog - Endogenous regressors
* instruments - Instrumental variables

The estimates are the same.  This interface is more useful for programmatically generating and estimating models.

```python
hours = {
    "dependent": data[["hours"]],
    "exog": data[["educ", "age", "kidslt6", "nwifeinc"]],
    "endog": data[["lwage"]],
    "instruments": data[["exper", "expersq"]],
}

lwage = {
    "dependent": data[["lwage"]],
    "exog": data[["educ", "exper", "expersq"]],
    "endog": data[["hours"]],
    "instruments": data[["age", "kidslt6", "nwifeinc"]],
}

equations = dict(hours=hours, lwage=lwage)
system_3sls = IV3SLS(equations)
system_3sls_res = system_3sls.fit(cov_type="unadjusted")
print(system_3sls_res)
```

## System GMM Estimation

System GMM is an alternative to 3SLS estimation. It is the natural extension to GMM estimation of IV models.  It makes weaker assumptions about instruments than 3SLS does. In particular, instruments are assumed exogenous on an equation-by-equation basis rather than the 3SLS assumption that all instruments are exogenous in all equations.

The system GMM estimator is similar to the 3SLS estimator except that it requires making a choice about the moment weighting estimator.  Valid options for the weighting estimator are `"unadjusted"` or `"homoskedastic"` which assumes that residuals are conditionally homoskedastic or `"robust"` or `"heteroskedastic"` which allows for conditional heteroskedasticity.

The System GMM estimator also supports iterative application where it is possible to iterate until convergence.

Here the examples make use of the same data as in the 3SLS example and only use the formula interface. The default uses 2-step (efficient) GMM.

```python
equations = dict(
    hours="hours ~ educ + age + kidslt6 + nwifeinc + [lwage ~ exper + expersq]",
    lwage="lwage ~ educ + exper + expersq + [hours ~ age + kidslt6 + nwifeinc]",
)
system_gmm = IVSystemGMM.from_formula(equations, data, weight_type="unadjusted")
system_gmm_res = system_gmm.fit(cov_type="unadjusted")
print(system_gmm_res)
```

Robust weighting can be used by setting the `weight_type`.  The number of iterations can be set using `iter_limit`. Overall the parameters do not meaningfully change.

```python
system_gmm = IVSystemGMM.from_formula(equations, data, weight_type="robust")
system_gmm_res = system_gmm.fit(cov_type="robust", iter_limit=100)
print("Number of iterations: " + str(system_gmm_res.iterations))
print(system_gmm_res)
```

## Simultaneous Equations

Consider the simple simultaneous equation model

$$
\begin{align}
y_1 = y_2 + x_1 + \epsilon_1 \\
y_2 = -y_1 + x_2 + \epsilon_1 \\
\end{align}
$$

Substituting each equation in to the other, the reduced form of this model is
$$
\begin{align}
y_1 = \frac{1}{2}\left(x_1 + x_2 + \epsilon_1 + \epsilon_2\right) \\
y_1 = \frac{1}{2}\left(x_2 - x_1 + \epsilon_2 - \epsilon_1\right) \\
\end{align}
$$

Data from this model is simple to simulate.

```python
import numpy as np
import pandas as pd

rs = np.random.default_rng(20220224)
e = rs.standard_normal((50000, 2))
x = rs.standard_normal((50000, 2))
y2 = x[:, 1] / 2 - x[:, 0] / 2 + e[:, 1] / 2 - e[:, 0] / 2
y1 = x[:, 1] / 2 + x[:, 0] / 2 + e[:, 1] / 2 + e[:, 0] / 2
df = pd.DataFrame(np.column_stack([y1, y2, x]), columns=["y1", "y2", "x1", "x2"])
in_sample = df.iloc[:-10000]
oos = df.iloc[-10000:]
mod = IV3SLS.from_formula(
    dict(y1="y1 ~  x1 + [y2 ~ x2]", y2="y2 ~ x2 + [y1 ~ x1]"), data=df
)
res = mod.fit()
print(res)
```

Out-of-sample predictions can be made using the `predict` method along with a `DataFrame` containing the values to use for the predictions. The predictions make use of the out-of-sample values stored above.

```python
res.predict(data=oos, dataframe=True)
```

These predictions can be compared to making the predicted values using the estimated parameters.

```python
y1_pred = res.params.y1_x1 * oos.x1 + res.params.y1_y2 * oos.y2
y2_pred = res.params.y2_x2 * oos.x2 + res.params.y2_y1 * oos.y1
pred_df = pd.DataFrame({"y1": y1_pred, "y2": y2_pred})
pred_df
```


### Example 1b: Linear convection in 2D, revisited

We will now revisit the fhe first example of this tutorial with an example that is better suited to the numerical scheme used in Devito. As a reminder, the governing equation is:

$$\frac{\partial u}{\partial t}+c\frac{\partial u}{\partial x} + c\frac{\partial u}{\partial y} = 0$$

We then discretized this using forward differences in time and backward differences in space:

$$u_{i,j}^{n+1} = u_{i,j}^n-c \frac{\Delta t}{\Delta x}(u_{i,j}^n-u_{i-1,j}^n)-c \frac{\Delta t}{\Delta y}(u_{i,j}^n-u_{i,j-1}^n)$$

In the previous example, the system was initialised with a hat function. As easy as this example seems, it actually highlights a few limitations of finite differences and related methods:
 - The governing equation above contains spatial derivatives ($\frac{\partial u}{\partial x}$ and $\frac{\partial u}{\partial y}$). The hat, with its sharp corners, is discontinuous and therefore non-smooth, meaning that the derivatives do not exist at the corners of the hat. This means that the governing equation has no solution in the strict sense for this problem. Mathematically, this problem can be overcome by introducing weak solutions, which still exist in the presence of discontinuities, as long as the problem is smooth almost everywhere. The Finite Volume (FV), Finite Element (FEM) and related schemes are based on this weak form.
 - The finite differences method only works well if finite differences are a good approximation of the derivatives. With the chosen discretisation above, this requires that $\frac{u_{i,j}^n-u_{i-1,j}^n}{\Delta x} \approx \frac{\partial u}{\partial x}$ and $\frac{u_{i,j}^n-u_{i,j-1}^n}{\Delta y} \approx \frac{\partial u}{\partial y}$. This is the case for systems with a smooth solution if $\Delta x$ and $\Delta y$ are sufficiently small. But if the solution is non-smooth, as in this example, then we can't expect much regardless of the grid size.
 - First-order methods, such as the backward differences that we have used in this exampe, are known to create artificial diffusion. Higher-order schemes, such as central differences, avoid this problem. However, in the presence of discontinuities these methods introduce so-called spurious oscillations. These oscillations may even build up (grow infinitely) and cause the computation to diverge.
 - Discontinuities can appear by themselves for some equations (such as the Burgers equation that we discuss next), even if the intial condition is smooth. In CFD, discontinuities appear for example as shocks in the simulation of transonic flow. For this reason, numerical schemes that behave well in the presence of discontinuities have been a research subject for a long time. A thorough discussion is beyond the scope of this tutorial, but can be found in [R. LeVeque (1992): Numerical Methods for Conservation Laws, 2nd ed., Birkhäuser Verlag, pp. 8-13].

In the remainder of this example, we will reproduce the results from the previous example, only this time with a smooth initial condition. This lets us observe Devito in a setting for which it is better equipped.

```python
from examples.cfd import plot_field, init_hat, init_smooth
import numpy as np
%matplotlib inline

# Some variable declarations
nx = 81
ny = 81
nt = 100
c = 1.
dx = 2. / (nx - 1)
dy = 2. / (ny - 1)
sigma = .2
dt = sigma * dx
```

Let us now initialise the field with an infinitely smooth bump, as given by [J.A. Krakos (2012): Unsteady Adjoint Analysis for Output Sensitivity
and Mesh Adaptation, PhD thesis, p. 68] as $$
f(r)=
\begin{cases}
\frac{1}{A}e^{-1/(r-r^2)} &\text{ for } 0 < r < 1,\\
0 &\text{ else.}
\end{cases}
$$
We use this with $A=100$, and define the initial condition in two dimensions as $$u^0(x,y)=1+f\left(\frac{2}{3}x\right)*f\left(\frac{2}{3}y\right).$$

```python
#NBVAL_IGNORE_OUTPUT

# Create field and assign initial conditions
u = np.empty((nx, ny))
init_smooth(field=u, dx=dx, dy=dy)

# Plot initial condition
plot_field(u, zmax=4)
```

Solving this will again move the bump.

```python
# Repeat initialisation, so we can re-run the cell
init_smooth(field=u, dx=dx, dy=dy)

for n in range(nt + 1):
    # Copy previous result into a new buffer
    un = u.copy()

    # Update the new result with a 3-point stencil
    u[1:, 1:] = (un[1:, 1:] - (c * dt / dx * (un[1:, 1:] - un[1:, :-1])) -
                              (c * dt / dy * (un[1:, 1:] - un[:-1, 1:])))

    # Apply boundary conditions
    u[0, :] = 1.
    u[-1, :] = 1.
    u[:, 0] = 1.
    u[:, -1] = 1.
```

```python
#NBVAL_IGNORE_OUTPUT

# A small sanity check for auto-testing
assert (u[45:55, 45:55] > 1.8).all()
u_ref = u.copy()

plot_field(u, zmax=4.)
```

Hooray, the wave moved! It looks like the solver works much better for this example: The wave has not noticeably changed its shape.

#### Devito implementation
Again, we can re-create this via a Devito operator. Let's fill the initial buffer with smooth data and look at it:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Grid, TimeFunction, INTERIOR

grid = Grid(shape=(nx, ny), extent=(2., 2.))
u = TimeFunction(name='u', grid=grid)
init_smooth(field=u.data[0], dx=dx, dy=dy)

plot_field(u.data[0])
```

We again create the discretised equation as shown below. Note that the equation is still the same, only the initial condition has changed.

```python
from devito import Eq

eq = Eq(u.dt + c*u.dxl + c*u.dyl)

print(eq)
```

    Eq(1.0*u(t, x, y)/h_y - 1.0*u(t, x, y - h_y)/h_y + 1.0*u(t, x, y)/h_x - 1.0*u(t, x - h_x, y)/h_x - u(t, x, y)/dt + u(t + dt, x, y)/dt, 0)

SymPy can re-organise this equation just like in the previous example.

```python
from sympy import solve

stencil = solve(eq, u.forward, rational=False)[0]

print(stencil)
```

    (1.0*dt*h_x*(-u(t, x, y) + u(t, x, y - h_y)) + 1.0*dt*h_y*(-u(t, x, y) + u(t, x - h_x, y)) + h_x*h_y*u(t, x, y))/(h_x*h_y)

We can now use this stencil expression to create an operator to apply to our data object:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Operator

# Reset our initial condition in both buffers.
# This is required to avoid 0s propagating into
# our solution, which has a background value of 1.
init_smooth(field=u.data[0], dx=dx, dy=dy)
init_smooth(field=u.data[1], dx=dx, dy=dy)

# Apply boundary conditions
u.data[:, 0, :] = 1.
u.data[:, -1, :] = 1.
u.data[:, :, 0] = 1.
u.data[:, :, -1] = 1.

# Create an operator that updates the forward stencil
# point to the interior region only.
op = Operator(Eq(u.forward, stencil, region=INTERIOR))

# Apply the operator for a number of timesteps
op(time=nt, dt=dt)

plot_field(u.data[0, :, :])

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

Again, this looks just like the result from NumPy. Since this example is just like the one before, the low-level treatment of boundaries is also unchanged.

```python
#NBVAL_IGNORE_OUTPUT

# Reset our data field and ICs in both buffers
init_smooth(field=u.data[0], dx=dx, dy=dy)
init_smooth(field=u.data[1], dx=dx, dy=dy)

# For defining BCs, we generally to explicitly set rows/columns
# in our field using an expression. We can use Devito's "indexed"
# notation to do this:
x, y = grid.dimensions
t = grid.stepping_dim
bc_left = Eq(u.indexed[t + 1, 0, y], 1.)
bc_right = Eq(u.indexed[t + 1, nx-1, y], 1.)
bc_top = Eq(u.indexed[t + 1, x, ny-1], 1.)
bc_bottom = Eq(u.indexed[t + 1, x, 0], 1.)

# Now combine the BC expressions with the stencil to form operator.
expressions = [Eq(u.forward, stencil, region=INTERIOR)]
expressions += [bc_left, bc_right, bc_top, bc_bottom]
op = Operator(expressions=expressions, dle=None, dse=None)  # <-- Turn off performance optimisations
op(time=nt, dt=dt)

plot_field(u.data[0, :, :])

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

The C code of the Kernel is also still the same.

```python
print(op.ccode)
```

    #define _POSIX_C_SOURCE 200809L
    #include "stdlib.h"
    #include "math.h"
    #include "sys/time.h"

    struct Profiler
    {
      double section0;
      double section1;
      double section2;
    } ;

    int Kernel(const float dt, const float h_x, const float h_y, float *restrict u_vec, const int time_M, const int time_m, void *_timers, const int x_M, const int x_m, const int x_size, const int y_M, const int y_m, const int y_size)
    {
      float (*restrict u)[x_size + 1 + 1][y_size + 1 + 1] __attribute__((aligned(64))) = (float (*)[x_size + 1 + 1][y_size + 1 + 1]) u_vec;
      struct Profiler *timers = (struct Profiler*) _timers;
      for (int time = time_m, t0 = (time)%(2), t1 = (time + 1)%(2); time <= time_M; time += 1, t0 = (time)%(2), t1 = (time + 1)%(2))
      {
        struct timeval start_section0, end_section0;
        gettimeofday(&start_section0, NULL);
        for (int xi = x_m + 1; xi <= x_M - 1; xi += 1)
        {
          for (int yi = y_m + 1; yi <= y_M - 1; yi += 1)
          {
            u[t1][xi + 1][yi + 1] = (1.0F*dt*h_x*(u[t0][xi + 1][yi] - u[t0][xi + 1][yi + 1]) + 1.0F*dt*h_y*(u[t0][xi][yi + 1] - u[t0][xi + 1][yi + 1]) + h_x*h_y*u[t0][xi + 1][yi + 1])/(h_x*h_y);
          }
        }
        gettimeofday(&end_section0, NULL);
        timers->section0 += (double)(end_section0.tv_sec-start_section0.tv_sec)+(double)(end_section0.tv_usec-start_section0.tv_usec)/1000000;
        struct timeval start_section1, end_section1;
        gettimeofday(&start_section1, NULL);
        for (int y = y_m; y <= y_M; y += 1)
        {
          u[t1][1][y + 1] = 1.00000000000000F;
          u[t1][81][y + 1] = 1.00000000000000F;
        }
        gettimeofday(&end_section1, NULL);
        timers->section1 += (double)(end_section1.tv_sec-start_section1.tv_sec)+(double)(end_section1.tv_usec-start_section1.tv_usec)/1000000;
        struct timeval start_section2, end_section2;
        gettimeofday(&start_section2, NULL);
        for (int x = x_m; x <= x_M; x += 1)
        {
          u[t1][x + 1][81] = 1.00000000000000F;
          u[t1][x + 1][1] = 1.00000000000000F;
        }
        gettimeofday(&end_section2, NULL);
        timers->section2 += (double)(end_section2.tv_sec-start_section2.tv_sec)+(double)(end_section2.tv_usec-start_section2.tv_usec)/1000000;
      }
      return 0;
    }

```python

```


# Compute the Structure Factor and Boson Entropy from the Pair Correlation Function After a Quench

```python
import numpy as np
import matplotlib.pyplot as plt
from numpy import pi as π

%matplotlib inline
%config InlineBackend.figure_format = 'svg'

from collections import defaultdict
import pickle

import include.colors as colortools
with plt.style.context('./include/sans.mplstyle'):
    fig_size = plt.rcParams['figure.figsize']
```

### Load useful utilities from disk

```python
import quenchutils as quench
```

## Load ED data from disk and compute the pair correlation function

Using the notation in Appendix A4 in Giuliani and Vignale "Quantum Theory of the Electron Liquid" on Page 682 Eq. (A4.2) we define:

\begin{equation}
g_2(i-j) = \frac{\langle \hat{n}_i \hat{n}_j \rangle}{\rho_0^2} - \frac{\delta_{i,j}}{\rho_0}
\end{equation}

where $ \rho_0$  is the density.

```python
g2,g2_eq = {},{}

Vi = 0.0
Vf = [-0.5,-0.25,-0.15,0.15,0.25,0.765]
Vp = [-0.15,0.15,-0.25,0.25,-0.5,0.765]
Δt = 0.1
ti = 0.0
ρ = 0.5  # 1/2 filling in units where the lattice spacing is unity
kF = π*ρ # Fermi wavevector

N = np.arange(4,14)
cn = 1
bc = 'PBC'
colors = colortools.get_linear_colors('Spectral',len(Vf),reverse=False)

# Get Equilbrium Results First
ti,tf = 0.0,0.0
for cVf in Vf:
    Vi = cVf
    for i,cN in enumerate(N):
        data_dir = '../data/equilibrium/'
        try:
            p_data = np.loadtxt(quench.get_ED_fname(cN,Vi,cVf,Δt,ti,tf,cn,data_dir,fmt='g2'))
            key = quench.lookup(cN,Vi,cVf,Δt,cn,bc=bc)
            g2_eq[key] = p_data[1:]
            g2_eq[key] /= ρ**2
            g2_eq[key][0] -= 1/ρ
        except:
            print("Can't load: {:s}".format(quench.get_ED_fname(cN,Vi,cVf,Δt,ti,tf,cn,data_dir,fmt='g2')))

# Load the quench results from disk
Vi = 0.0
ti,tf = 0.0,100.0
for cVf in Vf:
    for i,cN in enumerate(N):
        data_dir = f'../data/quench/ED/{bc:s}/'
        try:
            p_data = np.loadtxt(quench.get_ED_fname(cN,Vi,cVf,Δt,ti,tf,cn,data_dir,fmt='g2'))
            key = quench.lookup(cN,Vi,cVf,Δt,cn,bc=bc)
            g2[key] = p_data
            g2[key][:,1:] /= ρ**2
            g2[key][:,1] = g2[key][:,1] - 1/ρ
        except:
            print("Can't load: {:s}".format(quench.get_ED_fname(cN,Vi,cVf,Δt,ti,tf,cn,data_dir,fmt='g2')))
```

## The Structure Factor

In order to obtain the Fourier components of the density fluctuation operator we express things in terms of the static structure factor $s(q)$ defined by:

\begin{equation}
s(q;t) = 1 + f\sum_{x=0}^{L/2-1} \left[g_2(x;t)-1 \right] \mathrm{e}^{-i q x}
\end{equation}

where $f = \rho_0 a_0$ is the filling fraction with $a_0$ the lattice spacing (set to unity here).

Let us investigate it's time dependence re-scaled by the ground state LL prediction $S_{LL}(q) = K \frac{|q|}{2k_{\rm F}}$ where we evaluate the Luttinger paramger $K$ via the Bethe ansatz

\begin{equation}
K = \frac{\pi}{2\cos^{-1}(-V/2J)}
\end{equation}

```python
S_eq = {}
q_eq = {}
n_eq = {}

for iV,cVf in enumerate(Vf):
    for cN in N:
        key_eq = quench.lookup(cN,cVf,cVf,Δt,cn)

        x = np.arange(2*cN)
        Δx = x[1]-x[0]
        q_eq[key_eq] = 2*π*np.fft.rfftfreq(x.size,Δx)
        q_eq[key_eq][0] = 1E-16

        # Equilibrium Structure factor
        S_eq[key_eq] = (1.0 + ρ*np.fft.rfft(g2_eq[key_eq]-1)).real
        S_eq[key_eq][0] = 1E-16
```

```python
S̄ = defaultdict(list)
q = {}

for iV,cVf in enumerate(Vf):
    K = quench.KV(cVf)

    for cN in N:
        key = quench.lookup(cN,Vi,cVf,Δt,cn)
        key_eq = quench.lookup(cN,cVf,cVf,Δt,cn)

        if key in g2:

            x = np.arange(2*cN)
            Δx = x[1]-x[0]
            q[key] = 2*π*np.fft.rfftfreq(x.size,Δx)
            q[key][0] = 1E-16

            SLL = K*np.abs(q[key])/(2.0*kF)
            SLL = S_eq[key_eq]

            len_t = len(g2[key][:,0])
            for it in range(len_t):
                S̄[key].append((1.0 + ρ*np.fft.rfft(g2[key][it,1:]-1.0).real)/SLL)

            S̄[key] = np.array(S̄[key])
```

### Examining the Structure Factor at Short Times $t \cdot J = 1/2$

```python
ncolors = colortools.get_linear_colors('Spectral',20,reverse=True)
fig, axes = plt.subplots(1,2,sharex=True, sharey=False, squeeze=True, figsize=(2*fig_size[0],fig_size[1]))
fig.subplots_adjust(wspace=0.3)

q_th = np.linspace(0,2,1000)
for iV,cVf in enumerate(Vp):

    for cN in N[-1:]:
        key = quench.lookup(cN,Vi,cVf,Δt,cn,bc='PBC')
        key_eq = quench.lookup(cN,cVf,cVf,Δt,cn,bc='PBC')

        props = {'mec':ncolors[iV],'mew':0.7,'ms':3,
                 'mfc':colortools.get_alpha_hex(ncolors[iV],0.5), 'color':ncolors[iV], 'lw':0.7}

        if key_eq in q_eq:
            axes[0].plot(q_eq[key_eq]/kF,S_eq[key_eq].real, marker='o', label=f'$V = {cVf:+.2f}$', ls='None', **props)

            ## The LL Prediction
            K = quench.KV(cVf)
            axes[0].plot(q_th,K*q_th/(2.0), lw=0.7,color=ncolors[iV], linestyle='-', zorder=-1)

        if key in S̄:
            idx = np.where(np.abs(g2[key][:,0]-1/2)<Δt)[0][0]
            axes[1].plot(q[key][1:]/kF,S̄[key][idx,1:], marker='o', label=f'$V_f = {cVf:+.2f}$', linestyle='--',**props)

axes[0].text(0.99,0.02,f'$N = {cN}$',transform=axes[0].transAxes, fontsize=8, ha='right')
axes[0].set_xlabel(r'$q/k_{\rm F}$')
axes[0].set_ylabel(r'$s_{\rm eq}(q)$')
axes[0].legend(loc=(-0.04,0.35),frameon=False, fontsize=8, handletextpad=0.1)
axes[0].set_xlim(0,1)
axes[0].set_ylim(0,0.5)

axes[1].set_ylabel(r'$\overline{s}(q;t= 1/2J)$')
axes[1].set_xlabel(r'$q/k_{\rm F}$')

#plt.savefig('Output/Sq_vs_q_N_13.pdf')
```

    Text(0.5, 0, '$q/k_{\\rm F}$')

### Comparison of Pre and Post Quench Structure Factor

```python
ncolors = colortools.get_linear_colors('Spectral',20,reverse=True)
Vp = [-0.15,0.15,-0.25,0.25,-0.5,0.765]
blue = "#4b90a5"
orange = "#f67a49"

plt.style.reload_library()
with plt.style.context('./include/sans.mplstyle'):
    fig, axes = plt.subplots(1,1,)

    q_th = np.linspace(0,2,1000)
    iV = 5
    cVf = 0.765
    cN = 13
    key = quench.lookup(cN,Vi,cVf,Δt,cn,bc='PBC')
    key_eq = quench.lookup(cN,cVf,cVf,Δt,cn,bc='PBC')

    if key in S̄:
        idx = np.where(np.abs(g2[key][:,0]-1/2)<Δt)[0][0]
        idx = 4
        axes.plot(q[key]/kF,S̄[key][idx,:]*S_eq[key_eq], marker='o', label=r'post-quench  $t \cdot J = 1/2$', linestyle='--',
                  mfc=orange+'80',mec=orange, mew=0.7, ms=5, color=orange, lw=0.7)

    if key_eq in q_eq:
        axes.plot(q_eq[key_eq]/kF,S_eq[key_eq].real, marker='o', label='equilibrium', ls='None', mec=blue,
                  mfc=blue+'80', ms=5, mew=0.7)

        ## The LL Prediction
        K = quench.KV(cVf)
        axes.plot(q_th,K*q_th/(2.0), lw=0.7,color=blue, linestyle='-', zorder=-1, label=r'$ {K|q|}\, /\,  {2k_{\rm F}} $')

    #lab = f'$N = {cN}$' + '\n' + f'$V = {cVf:.3f}J$' + '\n' f'$K = {K:.3f}$'
    lab = f'$N = {cN}$      ' + '\n' + f'$V = {cVf:.3f}J$' + '\n' f'$K = {K:.3f}$ '

    axes.text(0.99,0.02,lab,transform=axes.transAxes, fontsize=8, ha='right')
    axes.set_xlabel(r'Wavevector  $q/k_{\rm F}$')
    axes.set_ylabel(r'Structure Factor')
    axes.legend(loc='upper left',frameon=False, fontsize=8, handletextpad=0.5, handlelength=1)
    axes.set_xlim(0,0.48)
    axes.set_ylim(0,0.23)

    plt.savefig(f'../figures/S_vs_q_N_{cN}.pdf')
    plt.savefig(f'../figures/S_vs_q_n_{cN}.svg')
```

## Extracting the boson momentum distribution at low-$q$

We want to investigate fitting the time dependence of the low-q structure factor to the Luttinger liquid prediction:

\begin{equation}
\bar{s}(q; t) \equiv \frac{s(q; t)}{K |q|/(2k_{\rm F})}  =    2 n(q) + 1 + \cos(2 \omega_q t) \sinh(2 \beta)
\end{equation}

and we will perform a 3 paramter fit to $n_{\rm LL}$, $C$ and $\omega_q$ via

\begin{equation}
\bar{s}(q; t) = 1 + 2 n_{\rm LL} + C \cos(2 \omega_q t)\; .
\end{equation}

```python
from lmfit import minimize, Parameters

def get_fit(x,y,nLL,C,ω):
    fit_params = Parameters()
    fit_params.add('nLL', value=nLL, min=0.0, max=1.0, vary=True)
    fit_params.add('C', value=C, min=-1.0, max=1.0, vary=True)
    fit_params.add('c1',value=0.0, vary=False)
    fit_params.add('omega', value=ω, min=0.0, vary=True)

    def f(params,x_):
        p = {}
        [p.update({pname:params[pname].value}) for pname in ['nLL','C','c1','omega']]
        return 1.0 + 2*p['nLL'] + p['c1']*x_ + p['C']*np.cos(2*p['omega']*x_)

    def fss_residual(params, x_, y_):
        return f(params,x_) - y_

    fit = minimize(fss_residual, fit_params, args=(x,y))

    return lambda x: f(fit.params,x),fit

def S̄LL(params,x_):
    p = {}
    [p.update({pname:params[pname].value}) for pname in ['nLL','C','c1','omega']]
    return 1.0 + 2*p['nLL'] + p['c1']*x_ + p['C']*np.cos(2*p['omega']*x_)
```

```python
Sfit,ΔSfit = {},{}
```

```python
with plt.style.context('./include/sans.mplstyle'):
    fig_size = plt.rcParams['figure.figsize']
    dfig_size = (1.5*fig_size[0],1.5*fig_size[1])
    ncolors = colortools.get_linear_colors('Spectral',2*len(N),reverse=True)

    fig, axes = plt.subplots(2,3,sharex=True, sharey=True, squeeze=True)
    axes = axes.flatten()
    fig.subplots_adjust(hspace=0.05, wspace=0.05)

    cN = N[-2]
    q_index = 1

    Vp = [-0.15,0.15,-0.25,0.25,-0.5,0.765]
    for iV,cVf in enumerate(Vp):

        key = quench.lookup(cN,Vi,cVf,Δt,cn)
        K = quench.KV(cVf)
        if key in S̄:

            # get the time dependent n(q,t) data and perform a fit
            data = S̄[key][:,q_index]
            t = g2[key][:,0]*quench.tscalefactor(cVf)

            # find the main oscillation frequency
            #S̄_qω = np.fft.rfft(data).real
            #ω = 2*π*np.fft.rfftfreq(t.size,t[1]-t[0])
            #ω0 = ω[np.argmax(np.abs(S̄_qω[1:]))+1]
            #ω0 = 2.0*q[key][q_index]

            # estimate parameters in the fit
            nLL0 = quench.γeq(quench.KV(cVf))**2/2
            C0 = quench.γ(quench.KV(cVf)) # 0.5*(1/K-K)
            ω0 = q[key][q_index]

            stop_fit = np.where(np.abs(g2[key][:,0]*quench.tscalefactor(cVf)-cN/2)<Δt*quench.tscalefactor(cVf))[0][0]
            fit_n,fit = get_fit(t[:stop_fit],data[:stop_fit],nLL0,C0,ω0)

            Sfit[key] = fit #.params['nLL'].value
            ΔSfit[key] = fit # .params['nLL'].stderr

            #fit.params.pretty_print()

            props = {'mec':ncolors[iV],'mew':0.25, 'ls':'None','ms':2, 'mfc':'None'}
            axes[iV].plot(t,data, 'o', **props, label='ED data')
            axes[iV].plot(t[:stop_fit],S̄LL(Sfit[key].params,t[:stop_fit]),'-', color=ncolors[iV], linewidth=1, label=r'fit')
            axes[iV].plot(t[stop_fit:],S̄LL(Sfit[key].params,t[stop_fit:]),'-', color=colortools.get_alpha_hex(ncolors[iV],0.8),
                          linewidth=0.5, label=r'extrapolated')

            #axes[iV].axhline(y=(1+2*nLL0), color=ncolors[iV], linewidth=1, ls=':', zorder=-10, label=r'$(K+1/K-2)/4$')
            #axes[iV].axhline(y=(1+2*fit.params['nLL'].value), color=ncolors[iV], linewidth=1, ls='--', zorder=-1, label=r'$n_{\rm LL}$')

            if not iV % 3:
                cq = q[key][q_index]/kF
                axes[iV].set_ylabel(r'$\bar{{s}}(q/k_{{\rm F}}={:3.2f};t)$'.format(cq))
            axes[iV].set_xlim(0,20)
            axes[iV].text(0.99,0.02,'$V = {:5.3f}J$'.format(cVf),transform=axes[iV].transAxes, fontsize=8, ha='right')

    axes[2].text(0.99,0.87,f'$N = {cN}$',transform=axes[2].transAxes, fontsize=8, ha='right')
    axes[0].legend(loc=(0.0,0.48), fontsize=8, handlelength=1, labelspacing=0.2, handletextpad=0.5)
    for iax in range(-3,0):
        axes[iax].set_xlabel(r'Time  $t \cdot v$')

    #axes[-1].set_ylim(0.79,1.29)
    axes[-1].set_xlim(0,18)

    plt.savefig(f'../figures/nqt_vs_t_with_fit_N_{cN}.svg')
    plt.savefig(f'../figures/nqt_vs_t_with_fit_N_{cN}.pdf')
```

```python
grey = '#9fa2b2'
with plt.style.context('./include/sans.mplstyle'):

    fit_K = np.linspace(0.75,1.25,100)
    cN = 12

    props = {'mec':colors[-1],'mew':0.7, 'ecolor':colors[-1], 'elinewidth':0.7, 'ls':'None','ms':4,
                 'mfc':colortools.get_alpha_hex(colors[-1],0.5)+'80'}
    props['mfc'] = grey + '80'
    props['ecolor']=grey
    props['mec'] = grey
    fig, axes = plt.subplots(1,1)

    for iV,cVf in enumerate(Vf):
        key = quench.lookup(cN,Vi,cVf,Δt,cn)
        K = quench.KV(cVf)
        n0 = 0.25*(fit_K+1/fit_K-2)
        if iV == 0:
            cq = q[key][1]/kF
            lab=r'$\langle n_{q} \rangle$ extracted from ' + r'$s(q/k_{{\rm F}}={:.2f};t)$'.format(cq)
        else:
            lab=''
        axes.errorbar(K,Sfit[key].params['nLL'].value,yerr=Sfit[key].params['nLL'].stderr, **props, label=lab, marker='o')

    axes.text(0.99,0.02,f'$N = {cN}$',transform=axes.transAxes, ha='right',fontsize=8)

    axes.plot(fit_K,n0, linewidth=0.7, label=r'$\frac{1}{4}(K+1/K-2)$', color=grey)
    axes.set_xlabel('Luttinger Parameter  $K$')
    axes.set_ylabel('Bosonic Density\n Occupation Number  ' +  r'$\langle n_q \rangle$')
    axes.set_xlim(0.745,1.255)
    axes.legend(handlelength=1)

    plt.savefig(f'../figures/nq_vs_K_with_LL_N_{cN}.svg')
    plt.savefig(f'../figures/nq_vs_K_with_LL_N_{cN}.pdf')
```

## Paper Figure 5: Extrating $n(q)$ from $S(q;t)$
---

```python
plt.style.reload_library()
with plt.style.context('./include/sans.mplstyle'):

    fig, axes = plt.subplots(1,2,sharex=False, sharey=False, squeeze=True)
    fig.subplots_adjust(wspace=0.525)
    cVf = 0.15
    cN = 12
    q_index = 1

    key = quench.lookup(cN,Vi,cVf,Δt,cn)
    K = quench.KV(cVf)

    # S(q;t) for a single value of V

    # get the time dependent S(q,t) data
    data = S̄[key][:,q_index]
    t = g2[key][:,0]*quench.tscalefactor(cVf)

    props = {'mec':colors[-1],'mew':0.5, 'ls':'None','ms':2, 'mfc':'None'}
    flabel = r'fit: $1 + 2n_{\rm LL} + \sinh(2\beta)\cos(2\omega_q t)$'
    SLL = K*np.abs(q[key])/(2.0*kF)
    axes[0].plot(t,data*SLL[q_index], 'o', **props, label=f'V = {cVf:5.3f}')
    axes[0].plot(t,S̄LL(Sfit[key].params,t)*SLL[q_index],'-', color=colors[-1], linewidth=0.5, label=r'fit', zorder=-10)
    axes[0].set_xlabel(r'$vt/a_0$')
    axes[0].set_ylabel(r'$S(q/k_{{\rm F}}={:.2f};t)$'.format(q[key][q_index]/kF))
    axes[0].text(0.68,0.01,f'N = {cN}',transform=axes[0].transAxes, fontsize=8, ha='left')

    axes[0].set_xlim(0,20)
    axes[0].legend(frameon=False, loc=(-0.02,0.9), handletextpad=0.5, handlelength=1.0, ncol=2, columnspacing=1.0)

    # n_LL extracted from the fit vs. K

    fit_K = np.linspace(0.75,1.25,100)

    props = {'mec':colors[-2],'mew':0.5, 'ecolor':colors[-2], 'elinewidth':0.5, 'ls':'None','ms':3,
                 'mfc':colortools.get_alpha_hex(colors[-2],0.5)}

    for iV,cVf in enumerate(Vf):
        key = quench.lookup(cN,Vi,cVf,Δt,cn)
        K = quench.KV(cVf)
        nLL = 0.25*(fit_K+1/fit_K-2)
        if iV == 0:
            cq = q[key][q_index]/kF
            lab=f'computed from $\bar{{S}}(q/k_{{\rm F}}={cq:.2f};t)$'
            lab = 'from fit'
        else:
            lab=''
        axes[1].errorbar(K,Sfit[key].params['nLL'].value,yerr=Sfit[key].params['nLL'].stderr, **props, label=lab, marker='o')

    axes[1].plot(fit_K,nLL,linewidth=0.5, label=r'$\frac{1}{4}(K+K^{-1}-2)$', color=colors[-2])

    axes[1].legend(frameon=False, loc=(0.1,0.5), handletextpad=0.5, handlelength=1.0, ncol=1, columnspacing=1.0)

    axes[1].set_xlabel(r'$K$')
    axes[1].set_ylabel(r'$n_{\rm LL}$')
    axes[1].set_xlim(0.77,1.23)
    axes[1].set_ylim(0,0.017)
    plt.savefig(f'../figures/SqnLL_N{cN}.svg')
```

### Save fit data for structure factor to disk

```python
plt.style.reload_library()
with plt.style.context('./include/sans.mplstyle'):

    fig, axes = plt.subplots(1,1,sharex=False, sharey=False, squeeze=True)
    cVf = 0.15
    cN = 12
    q_index = 1

    key = quench.lookup(cN,Vi,cVf,Δt,cn)
    K = quench.KV(cVf)

    # S(q;t) for a single value of V

    # get the time dependent S(q,t) data
    data = S̄[key][:,q_index]
    t = g2[key][:,0]*quench.tscalefactor(cVf)

    props = {'mec':colors[-1],'mew':0.5, 'ls':'None','ms':2, 'mfc':'None'}
    flabel = r'fit: $1 + 2n_{\rm LL} + \sinh(2\beta)\cos(2\omega_q t)$'
    SLL = K*np.abs(q[key])/(2.0*kF)
    axes.plot(t,data*SLL[q_index], 'o', **props, label=f'V = {cVf:5.3f}')
    axes.plot(t,S̄LL(Sfit[key].params,t)*SLL[q_index],'-', color=colors[-1], linewidth=0.5, label=r'fit', zorder=-10)
    axes.set_xlabel(r'$vt/a_0$')
    axes.set_ylabel(r'$S(q/k_{{\rm F}}={:.2f};t)$'.format(q[key][q_index]/kF))
    axes.text(0.68,0.01,f'N = {cN}',transform=axes.transAxes, fontsize=8, ha='left')

    axes.set_xlim(0,20)
    axes.legend(frameon=False, loc=(-0.02,0.9), handletextpad=0.5, handlelength=1.0, ncol=2, columnspacing=1.0)

    # Store everything so we can plot it in another notebook
    structure = {'x1':t,'y1':data*SLL[q_index],'x2':t,'y2':S̄LL(Sfit[key].params,t)*SLL[q_index],'q':q, 'kF':kF}

    with open('../figures/Sq_boson_fit.pickle', 'wb') as pfile:
        pickle.dump(structure, pfile, pickle.HIGHEST_PROTOCOL)

    #plt.savefig(f'../figures/SqnLL_N{cN}.pdf')
```

```python

```