# The Jupyter notebook

[IPython](https://ipython.org) provides a **kernel** for [Jupyter](https://jupyter.org).
Jupyter is the name for this notebook interface,
and the document format.

Notebooks can contain [Markdown](https://help.github.com/articles/markdown-basics/) like this cell here,
as well as mathematics rendered with [mathjax](https://mathjax.org):

$$
\frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
{1+\frac{e^{-8\pi}} {1+\ldots} } } }
$$

```python
!head -n 32 "Intro to IPython.ipynb"
```

    {
     "cells": [
      {
       "cell_type": "markdown",
       "metadata": {},
       "source": [
        "# The Jupyter notebook\n",
        "\n",
        "[IPython](https://ipython.org) provides a **kernel** for [Jupyter](https://jupyter.org).\n",
        "Jupyter is the name for this notebook interface,\n",
        "and the document format.\n",
        "\n",
        "\n",
        "\n",
        "Notebooks can contain [Markdown](https://help.github.com/articles/markdown-basics/) like this cell here,\n",
        "as well as mathematics rendered with [mathjax](https://mathjax.org):\n",
        "\n",
        "$$\n",
        "\\frac{1}{\\Bigl(\\sqrt{\\phi \\sqrt{5}}-\\phi\\Bigr) e^{\\frac25 \\pi}} =\n",
        "1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}}\n",
        "{1+\\frac{e^{-8\\pi}} {1+\\ldots} } } } \n",
        "$$"
       ]
      },
      {
       "cell_type": "code",
       "execution_count": null,
       "metadata": {},
       "outputs": [],
       "source": [
        "!head -n 32 \"Intro to IPython.ipynb\""
       ]

[nbviewer](http://nbviewer.org) is a service that renders notebooks to HTML,
for sharing and reading notebooks on the Internet.

[This notebook](http://nbviewer.ipython.org/81c2a94563d102d93895) on nbviewer.

You can also convert notebooks to HTML and other formats locally with `jupyter nbconvert`.

When executing code in IPython, all valid Python syntax works as-is, but IPython provides a number of features designed to make the interactive experience more fluid and efficient.

## First things first: running code, getting help

In the notebook, to run a cell of code, hit `Shift-Enter`. This executes the cell and puts the cursor in the next cell below, or makes a new one if you are at the end.  Alternately, you can use:

- `Alt-Enter` to force the creation of a new cell unconditionally (useful when inserting new content in the middle of an existing notebook).
- `Control-Enter` executes the cell and keeps the cursor in the same cell, useful for quick experimentation of snippets that you don't need to keep permanently.

```python
print("Hi")
```

    Hi

```python
import time

for i in range(10):
    print(i, end=' ')
    time.sleep(1)
```

    0 1 2 3 4 5 6 7 8 9

```python
i
```

    9

Getting help:

```python
?
```

    IPython -- An enhanced Interactive Python
    =========================================

    IPython offers a fully compatible replacement for the standard Python
    interpreter, with convenient shell features, special commands, command
    history mechanism and output results caching.

    At your system command line, type 'ipython -h' to see the command line
    options available. This document only describes interactive features.

    GETTING HELP
    ------------

    Within IPython you have various way to access help:

      ?         -> Introduction and overview of IPython's features (this screen).
      object?   -> Details about 'object'.
      object??  -> More detailed, verbose information about 'object'.
      %quickref -> Quick reference of all IPython specific syntax and magics.
      help      -> Access Python's own help system.

    If you are in terminal IPython you can quit this screen by pressing `q`.

    MAIN FEATURES
    -------------

    * Access to the standard Python help with object docstrings and the Python
      manuals. Simply type 'help' (no quotes) to invoke it.

    * Magic commands: type %magic for information on the magic subsystem.

    * System command aliases, via the %alias command or the configuration file(s).

    * Dynamic object information:

      Typing ?word or word? prints detailed information about an object. Certain
      long strings (code, etc.) get snipped in the center for brevity.

      Typing ??word or word?? gives access to the full information without
      snipping long strings. Strings that are longer than the screen are printed
      through the less pager.

      The ?/?? system gives access to the full source code for any object (if
      available), shows function prototypes and other useful information.

      If you just want to see an object's docstring, type '%pdoc object' (without
      quotes, and without % if you have automagic on).

    * Tab completion in the local namespace:

      At any time, hitting tab will complete any available python commands or
      variable names, and show you a list of the possible completions if there's
      no unambiguous one. It will also complete filenames in the current directory.

    * Search previous command history in multiple ways:

      - Start typing, and then use arrow keys up/down or (Ctrl-p/Ctrl-n) to search
        through the history items that match what you've typed so far.

      - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
        your history for lines that match what you've typed so far, completing as
        much as it can.

      - %hist: search history by index.

    * Persistent command history across sessions.

    * Logging of input with the ability to save and restore a working session.

    * System shell with !. Typing !ls will run 'ls' in the current directory.

    * The reload command does a 'deep' reload of a module: changes made to the
      module since you imported will actually be available without having to exit.

    * Verbose and colored exception traceback printouts. See the magic xmode and
      xcolor functions for details (just type %magic).

    * Input caching system:

      IPython offers numbered prompts (In/Out) with input and output caching. All
      input is saved and can be retrieved as variables (besides the usual arrow
      key recall).

      The following GLOBAL variables always exist (so don't overwrite them!):
      _i: stores previous input.
      _ii: next previous.
      _iii: next-next previous.
      _ih : a list of all input _ih[n] is the input from line n.

      Additionally, global variables named _i<n> are dynamically created (<n>
      being the prompt counter), such that _i<n> == _ih[<n>]

      For example, what you typed at prompt 14 is available as _i14 and _ih[14].

      You can create macros which contain multiple input lines from this history,
      for later re-execution, with the %macro function.

      The history function %hist allows you to see any part of your input history
      by printing a range of the _i variables. Note that inputs which contain
      magic functions (%) appear in the history with a prepended comment. This is
      because they aren't really valid Python code, so you can't exec them.

    * Output caching system:

      For output that is returned from actions, a system similar to the input
      cache exists but using _ instead of _i. Only actions that produce a result
      (NOT assignments, for example) are cached. If you are familiar with
      Mathematica, IPython's _ variables behave exactly like Mathematica's %
      variables.

      The following GLOBAL variables always exist (so don't overwrite them!):
      _ (one underscore): previous output.
      __ (two underscores): next previous.
      ___ (three underscores): next-next previous.

      Global variables named _<n> are dynamically created (<n> being the prompt
      counter), such that the result of output <n> is always available as _<n>.

      Finally, a global dictionary named _oh exists with entries for all lines
      which generated output.

    * Directory history:

      Your history of visited directories is kept in the global list _dh, and the
      magic %cd command can be used to go to any entry in that list.

    * Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython)

      1. Auto-parentheses

         Callable objects (i.e. functions, methods, etc) can be invoked like
         this (notice the commas between the arguments)::

             In [1]: callable_ob arg1, arg2, arg3

         and the input will be translated to this::

             callable_ob(arg1, arg2, arg3)

         This feature is off by default (in rare cases it can produce
         undesirable side-effects), but you can activate it at the command-line
         by starting IPython with `--autocall 1`, set it permanently in your
         configuration file, or turn on at runtime with `%autocall 1`.

         You can force auto-parentheses by using '/' as the first character
         of a line.  For example::

              In [1]: /globals             # becomes 'globals()'

         Note that the '/' MUST be the first character on the line!  This
         won't work::

              In [2]: print /globals    # syntax error

         In most cases the automatic algorithm should work, so you should
         rarely need to explicitly invoke /. One notable exception is if you
         are trying to call a function with a list of tuples as arguments (the
         parenthesis will confuse IPython)::

              In [1]: zip (1,2,3),(4,5,6)  # won't work

         but this will work::

              In [2]: /zip (1,2,3),(4,5,6)
              ------> zip ((1,2,3),(4,5,6))
              Out[2]= [(1, 4), (2, 5), (3, 6)]

         IPython tells you that it has altered your command line by
         displaying the new command line preceded by -->.  e.g.::

              In [18]: callable list
              -------> callable (list)

      2. Auto-Quoting

         You can force auto-quoting of a function's arguments by using ',' as
         the first character of a line.  For example::

              In [1]: ,my_function /home/me   # becomes my_function("/home/me")

         If you use ';' instead, the whole argument is quoted as a single
         string (while ',' splits on whitespace)::

              In [2]: ,my_function a b c   # becomes my_function("a","b","c")
              In [3]: ;my_function a b c   # becomes my_function("a b c")

         Note that the ',' MUST be the first character on the line!  This
         won't work::

              In [4]: x = ,my_function /home/me    # syntax error

Typing `object_name?` will print all sorts of details about any object, including docstrings, function definition lines (for call arguments) and constructor details for classes.

```python
import collections
collections.namedtuple?
```

    Signature: collections.namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)
    Docstring:
    Returns a new subclass of tuple with named fields.

    >>> Point = namedtuple('Point', ['x', 'y'])
    >>> Point.__doc__                   # docstring for the new class
    'Point(x, y)'
    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
    >>> p[0] + p[1]                     # indexable like a plain tuple
    33
    >>> x, y = p                        # unpack like a regular tuple
    >>> x, y
    (11, 22)
    >>> p.x + p.y                       # fields also accessible by name
    33
    >>> d = p._asdict()                 # convert to a dictionary
    >>> d['x']
    11
    >>> Point(**d)                      # convert from a dictionary
    Point(x=11, y=22)
    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
    Point(x=100, y=22)
    File:      ~/conda/lib/python3.6/collections/__init__.py
    Type:      function

```python
collections.Counter??
```

    Init signature: collections.Counter(*args, **kwds)
    Source:
    class Counter(dict):
        '''Dict subclass for counting hashable items.  Sometimes called a bag
        or multiset.  Elements are stored as dictionary keys and their counts
        are stored as dictionary values.

        >>> c = Counter('abcdeabcdabcaba')  # count elements from a string

        >>> c.most_common(3)                # three most common elements
        [('a', 5), ('b', 4), ('c', 3)]
        >>> sorted(c)                       # list all unique elements
        ['a', 'b', 'c', 'd', 'e']
        >>> ''.join(sorted(c.elements()))   # list elements with repetitions
        'aaaaabbbbcccdde'
        >>> sum(c.values())                 # total of all counts
        15

        >>> c['a']                          # count of letter 'a'
        5
        >>> for elem in 'shazam':           # update counts from an iterable
        ...     c[elem] += 1                # by adding 1 to each element's count
        >>> c['a']                          # now there are seven 'a'
        7
        >>> del c['b']                      # remove all 'b'
        >>> c['b']                          # now there are zero 'b'
        0

        >>> d = Counter('simsalabim')       # make another counter
        >>> c.update(d)                     # add in the second counter
        >>> c['a']                          # now there are nine 'a'
        9

        >>> c.clear()                       # empty the counter
        >>> c
        Counter()

        Note:  If a count is set to zero or reduced to zero, it will remain
        in the counter until the entry is deleted or the counter is cleared:

        >>> c = Counter('aaabbc')
        >>> c['b'] -= 2                     # reduce the count of 'b' by two
        >>> c.most_common()                 # 'b' is still in, but its count is zero
        [('a', 3), ('c', 1), ('b', 0)]

        '''
        # References:
        #   http://en.wikipedia.org/wiki/Multiset
        #   http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html
        #   http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm
        #   http://code.activestate.com/recipes/259174/
        #   Knuth, TAOCP Vol. II section 4.6.3

        def __init__(*args, **kwds):
            '''Create a new, empty Counter object.  And if given, count elements
            from an input iterable.  Or, initialize the count from another mapping
            of elements to their counts.

            >>> c = Counter()                           # a new, empty counter
            >>> c = Counter('gallahad')                 # a new counter from an iterable
            >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
            >>> c = Counter(a=4, b=2)                   # a new counter from keyword args

            '''
            if not args:
                raise TypeError("descriptor '__init__' of 'Counter' object "
                                "needs an argument")
            self, *args = args
            if len(args) > 1:
                raise TypeError('expected at most 1 arguments, got %d' % len(args))
            super(Counter, self).__init__()
            self.update(*args, **kwds)

        def __missing__(self, key):
            'The count of elements not in the Counter is zero.'
            # Needed so that self[missing_item] does not raise KeyError
            return 0

        def most_common(self, n=None):
            '''List the n most common elements and their counts from the most
            common to the least.  If n is None, then list all element counts.

            >>> Counter('abcdeabcdabcaba').most_common(3)
            [('a', 5), ('b', 4), ('c', 3)]

            '''
            # Emulate Bag.sortedByCount from Smalltalk
            if n is None:
                return sorted(self.items(), key=_itemgetter(1), reverse=True)
            return _heapq.nlargest(n, self.items(), key=_itemgetter(1))

        def elements(self):
            '''Iterator over elements repeating each as many times as its count.

            >>> c = Counter('ABCABC')
            >>> sorted(c.elements())
            ['A', 'A', 'B', 'B', 'C', 'C']

            # Knuth's example for prime factors of 1836:  2**2 * 3**3 * 17**1
            >>> prime_factors = Counter({2: 2, 3: 3, 17: 1})
            >>> product = 1
            >>> for factor in prime_factors.elements():     # loop over factors
            ...     product *= factor                       # and multiply them
            >>> product
            1836

            Note, if an element's count has been set to zero or is a negative
            number, elements() will ignore it.

            '''
            # Emulate Bag.do from Smalltalk and Multiset.begin from C++.
            return _chain.from_iterable(_starmap(_repeat, self.items()))

        # Override dict methods where necessary

        @classmethod
        def fromkeys(cls, iterable, v=None):
            # There is no equivalent method for counters because setting v=1
            # means that no element can have a count greater than one.
            raise NotImplementedError(
                'Counter.fromkeys() is undefined.  Use Counter(iterable) instead.')

        def update(*args, **kwds):
            '''Like dict.update() but add counts instead of replacing them.

            Source can be an iterable, a dictionary, or another Counter instance.

            >>> c = Counter('which')
            >>> c.update('witch')           # add elements from another iterable
            >>> d = Counter('watch')
            >>> c.update(d)                 # add elements from another counter
            >>> c['h']                      # four 'h' in which, witch, and watch
            4

            '''
            # The regular dict.update() operation makes no sense here because the
            # replace behavior results in the some of original untouched counts
            # being mixed-in with all of the other counts for a mismash that
            # doesn't have a straight-forward interpretation in most counting
            # contexts.  Instead, we implement straight-addition.  Both the inputs
            # and outputs are allowed to contain zero and negative counts.

            if not args:
                raise TypeError("descriptor 'update' of 'Counter' object "
                                "needs an argument")
            self, *args = args
            if len(args) > 1:
                raise TypeError('expected at most 1 arguments, got %d' % len(args))
            iterable = args[0] if args else None
            if iterable is not None:
                if isinstance(iterable, Mapping):
                    if self:
                        self_get = self.get
                        for elem, count in iterable.items():
                            self[elem] = count + self_get(elem, 0)
                    else:
                        super(Counter, self).update(iterable) # fast path when counter is empty
                else:
                    _count_elements(self, iterable)
            if kwds:
                self.update(kwds)

        def subtract(*args, **kwds):
            '''Like dict.update() but subtracts counts instead of replacing them.
            Counts can be reduced below zero.  Both the inputs and outputs are
            allowed to contain zero and negative counts.

            Source can be an iterable, a dictionary, or another Counter instance.

            >>> c = Counter('which')
            >>> c.subtract('witch')             # subtract elements from another iterable
            >>> c.subtract(Counter('watch'))    # subtract elements from another counter
            >>> c['h']                          # 2 in which, minus 1 in witch, minus 1 in watch
            0
            >>> c['w']                          # 1 in which, minus 1 in witch, minus 1 in watch
            -1

            '''
            if not args:
                raise TypeError("descriptor 'subtract' of 'Counter' object "
                                "needs an argument")
            self, *args = args
            if len(args) > 1:
                raise TypeError('expected at most 1 arguments, got %d' % len(args))
            iterable = args[0] if args else None
            if iterable is not None:
                self_get = self.get
                if isinstance(iterable, Mapping):
                    for elem, count in iterable.items():
                        self[elem] = self_get(elem, 0) - count
                else:
                    for elem in iterable:
                        self[elem] = self_get(elem, 0) - 1
            if kwds:
                self.subtract(kwds)

        def copy(self):
            'Return a shallow copy.'
            return self.__class__(self)

        def __reduce__(self):
            return self.__class__, (dict(self),)

        def __delitem__(self, elem):
            'Like dict.__delitem__() but does not raise KeyError for missing values.'
            if elem in self:
                super().__delitem__(elem)

        def __repr__(self):
            if not self:
                return '%s()' % self.__class__.__name__
            try:
                items = ', '.join(map('%r: %r'.__mod__, self.most_common()))
                return '%s({%s})' % (self.__class__.__name__, items)
            except TypeError:
                # handle case where values are not orderable
                return '{0}({1!r})'.format(self.__class__.__name__, dict(self))

        # Multiset-style mathematical operations discussed in:
        #       Knuth TAOCP Volume II section 4.6.3 exercise 19
        #       and at http://en.wikipedia.org/wiki/Multiset
        #
        # Outputs guaranteed to only include positive counts.
        #
        # To strip negative and zero counts, add-in an empty counter:
        #       c += Counter()

        def __add__(self, other):
            '''Add counts from two counters.

            >>> Counter('abbb') + Counter('bcc')
            Counter({'b': 4, 'c': 2, 'a': 1})

            '''
            if not isinstance(other, Counter):
                return NotImplemented
            result = Counter()
            for elem, count in self.items():
                newcount = count + other[elem]
                if newcount > 0:
                    result[elem] = newcount
            for elem, count in other.items():
                if elem not in self and count > 0:
                    result[elem] = count
            return result

        def __sub__(self, other):
            ''' Subtract count, but keep only results with positive counts.

            >>> Counter('abbbc') - Counter('bccd')
            Counter({'b': 2, 'a': 1})

            '''
            if not isinstance(other, Counter):
                return NotImplemented
            result = Counter()
            for elem, count in self.items():
                newcount = count - other[elem]
                if newcount > 0:
                    result[elem] = newcount
            for elem, count in other.items():
                if elem not in self and count < 0:
                    result[elem] = 0 - count
            return result

        def __or__(self, other):
            '''Union is the maximum of value in either of the input counters.

            >>> Counter('abbb') | Counter('bcc')
            Counter({'b': 3, 'c': 2, 'a': 1})

            '''
            if not isinstance(other, Counter):
                return NotImplemented
            result = Counter()
            for elem, count in self.items():
                other_count = other[elem]
                newcount = other_count if count < other_count else count
                if newcount > 0:
                    result[elem] = newcount
            for elem, count in other.items():
                if elem not in self and count > 0:
                    result[elem] = count
            return result

        def __and__(self, other):
            ''' Intersection is the minimum of corresponding counts.

            >>> Counter('abbb') & Counter('bcc')
            Counter({'b': 1})

            '''
            if not isinstance(other, Counter):
                return NotImplemented
            result = Counter()
            for elem, count in self.items():
                other_count = other[elem]
                newcount = count if count < other_count else other_count
                if newcount > 0:
                    result[elem] = newcount
            return result

        def __pos__(self):
            'Adds an empty counter, effectively stripping negative and zero counts'
            result = Counter()
            for elem, count in self.items():
                if count > 0:
                    result[elem] = count
            return result

        def __neg__(self):
            '''Subtracts from an empty counter.  Strips positive and zero counts,
            and flips the sign on negative counts.

            '''
            result = Counter()
            for elem, count in self.items():
                if count < 0:
                    result[elem] = 0 - count
            return result

        def _keep_positive(self):
            '''Internal method to strip elements with a negative or zero count'''
            nonpositive = [elem for elem, count in self.items() if not count > 0]
            for elem in nonpositive:
                del self[elem]
            return self

        def __iadd__(self, other):
            '''Inplace add from another counter, keeping only positive counts.

            >>> c = Counter('abbb')
            >>> c += Counter('bcc')
            >>> c
            Counter({'b': 4, 'c': 2, 'a': 1})

            '''
            for elem, count in other.items():
                self[elem] += count
            return self._keep_positive()

        def __isub__(self, other):
            '''Inplace subtract counter, but keep only results with positive counts.

            >>> c = Counter('abbbc')
            >>> c -= Counter('bccd')
            >>> c
            Counter({'b': 2, 'a': 1})

            '''
            for elem, count in other.items():
                self[elem] -= count
            return self._keep_positive()

        def __ior__(self, other):
            '''Inplace union is the maximum of value from either counter.

            >>> c = Counter('abbb')
            >>> c |= Counter('bcc')
            >>> c
            Counter({'b': 3, 'c': 2, 'a': 1})

            '''
            for elem, other_count in other.items():
                count = self[elem]
                if other_count > count:
                    self[elem] = other_count
            return self._keep_positive()

        def __iand__(self, other):
            '''Inplace intersection is the minimum of corresponding counts.

            >>> c = Counter('abbb')
            >>> c &= Counter('bcc')
            >>> c
            Counter({'b': 1})

            '''
            for elem, count in self.items():
                other_count = other[elem]
                if other_count < count:
                    self[elem] = other_count
            return self._keep_positive()
    File:           ~/conda/lib/python3.6/collections/__init__.py
    Type:           type

```python
c = collections.Counter('abcdeabcdabcaba')
```

```python
c.most_common?
```

    Signature: c.most_common(n=None)
    Docstring:
    List the n most common elements and their counts from the most
    common to the least.  If n is None, then list all element counts.

    >>> Counter('abcdeabcdabcaba').most_common(3)
    [('a', 5), ('b', 4), ('c', 3)]
    File:      ~/conda/lib/python3.6/collections/__init__.py
    Type:      method

```python
c.most_common(2)
```

    [('a', 5), ('b', 4)]

with '\*', you can do a wildcard search:

```python
*int*?
```

    FloatingPointError
    int
    print

```python
import numpy as np
np.*array?
```

    np.array
    np.asanyarray
    np.asarray
    np.ascontiguousarray
    np.asfarray
    np.asfortranarray
    np.chararray
    np.ndarray
    np.numarray
    np.recarray

An IPython quick reference card:

```python
%quickref
```

    IPython -- An enhanced Interactive Python - Quick Reference Card
    ================================================================

    obj?, obj??      : Get help, or more help for object (also works as
                       ?obj, ??obj).
    ?foo.*abc*       : List names in 'foo' containing 'abc' in them.
    %magic           : Information about IPython's 'magic' % functions.

    Magic functions are prefixed by % or %%, and typically take their arguments
    without parentheses, quotes or even commas for convenience.  Line magics take a
    single % and cell magics are prefixed with two %%.

    Example magic function calls:

    %alias d ls -F   : 'd' is now an alias for 'ls -F'
    alias d ls -F    : Works if 'alias' not a python name
    alist = %alias   : Get list of aliases to 'alist'
    cd /usr/share    : Obvious. cd -<tab> to choose from visited dirs.
    %cd??            : See help AND source for magic %cd
    %timeit x=10     : time the 'x=10' statement with high precision.
    %%timeit x=2**100
    x**100           : time 'x**100' with a setup of 'x=2**100'; setup code is not
                       counted.  This is an example of a cell magic.

    System commands:

    !cp a.txt b/     : System command escape, calls os.system()
    cp a.txt b/      : after %rehashx, most system commands work without !
    cp ${f}.txt $bar : Variable expansion in magics and system commands
    files = !ls /usr : Capture system command output
    files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'

    History:

    _i, _ii, _iii    : Previous, next previous, next next previous input
    _i4, _ih[2:5]    : Input history line 4, lines 2-4
    exec _i81        : Execute input history line #81 again
    %rep 81          : Edit input history line #81
    _, __, ___       : previous, next previous, next next previous output
    _dh              : Directory history
    _oh              : Output history
    %hist            : Command history of current session.
    %hist -g foo     : Search command history of (almost) all sessions for 'foo'.
    %hist -g         : Command history of (almost) all sessions.
    %hist 1/2-8      : Command history containing lines 2-8 of session 1.
    %hist 1/ ~2/     : Command history of session 1 and 2 sessions before current.
    %hist ~8/1-~6/5  : Command history from line 1 of 8 sessions ago to
                       line 5 of 6 sessions ago.
    %edit 0/         : Open editor to execute code with history of current session.

    Autocall:

    f 1,2            : f(1,2)  # Off by default, enable with %autocall magic.
    /f 1,2           : f(1,2) (forced autoparen)
    ,f 1 2           : f("1","2")
    ;f 1 2           : f("1 2")

    Remember: TAB completion works in many contexts, not just file names
    or python names.

    The following magic functions are currently available:

    %alias:
        Define an alias for a system command.
    %alias_magic:
        ::
    %autocall:
        Make functions callable without having to type parentheses.
    %automagic:
        Make magic functions callable without having to type the initial %.
    %autosave:
        Set the autosave interval in the notebook (in seconds).
    %bookmark:
        Manage IPython's bookmark system.
    %cat:
        Alias for `!cat`
    %cd:
        Change the current working directory.
    %clear:
        Clear the terminal.
    %colors:
        Switch color scheme for prompts, info system and exception handlers.
    %config:
        configure IPython
    %connect_info:
        Print information for connecting other clients to this kernel
    %cp:
        Alias for `!cp`
    %debug:
        ::
    %dhist:
        Print your history of visited directories.
    %dirs:
        Return the current directory stack.
    %doctest_mode:
        Toggle doctest mode on and off.
    %ed:
        Alias for `%edit`.
    %edit:
        Bring up an editor and execute the resulting code.
    %env:
        Get, set, or list environment variables.
    %gui:
        Enable or disable IPython GUI event loop integration.
    %hist:
        Alias for `%history`.
    %history:
        ::
    %killbgscripts:
        Kill all BG processes started by %%script and its family.
    %ldir:
        Alias for `!ls -F -G -l %l | grep /$`
    %less:
        Show a file through the pager.
    %lf:
        Alias for `!ls -F -l -G %l | grep ^-`
    %lk:
        Alias for `!ls -F -l -G %l | grep ^l`
    %ll:
        Alias for `!ls -F -l -G`
    %load:
        Load code into the current frontend.
    %load_ext:
        Load an IPython extension by its module name.
    %loadpy:
        Alias of `%load`
    %logoff:
        Temporarily stop logging.
    %logon:
        Restart logging.
    %logstart:
        Start logging anywhere in a session.
    %logstate:
        Print the status of the logging system.
    %logstop:
        Fully stop logging and close log file.
    %ls:
        Alias for `!ls -F -G`
    %lsmagic:
        List currently available magic functions.
    %lx:
        Alias for `!ls -F -l -G %l | grep ^-..x`
    %macro:
        Define a macro for future re-execution. It accepts ranges of history,
    %magic:
        Print information about the magic function system.
    %man:
        Find the man page for the given command and display in pager.
    %matplotlib:
        ::
    %mkdir:
        Alias for `!mkdir`
    %more:
        Show a file through the pager.
    %mv:
        Alias for `!mv`
    %namespace:
        Load one or more predefined namespace
    %notebook:
        ::
    %page:
        Pretty print the object and display it through a pager.
    %pastebin:
        Upload code to Github's Gist paste bin, returning the URL.
    %pdb:
        Control the automatic calling of the pdb interactive debugger.
    %pdef:
        Print the call signature for any callable object.
    %pdoc:
        Print the docstring for an object.
    %pfile:
        Print (or run through pager) the file where an object is defined.
    %pinfo:
        Provide detailed information about an object.
    %pinfo2:
        Provide extra detailed information about an object.
    %pip:

    %popd:
        Change to directory popped off the top of the stack.
    %pprint:
        Toggle pretty printing on/off.
    %precision:
        Set floating point precision for pretty printing.
    %prun:
        Run a statement through the python code profiler.
    %psearch:
        Search for object in namespaces by wildcard.
    %psource:
        Print (or run through pager) the source code for an object.
    %pushd:
        Place the current dir on stack and change directory.
    %pwd:
        Return the current working directory path.
    %pycat:
        Show a syntax-highlighted file through a pager.
    %pylab:
        ::
    %qtconsole:
        Open a qtconsole connected to this kernel.
    %quickref:
        Show a quick reference sheet
    %recall:
        Repeat a command, or get command to input line for editing.
    %rehashx:
        Update the alias table with all executable files in $PATH.
    %reload_ext:
        Reload an IPython extension by its module name.
    %rep:
        Alias for `%recall`.
    %rerun:
        Re-run previous input
    %reset:
        Resets the namespace by removing all names defined by the user, if
    %reset_selective:
        Resets the namespace by removing names defined by the user.
    %rm:
        Alias for `!rm`
    %rmdir:
        Alias for `!rmdir`
    %run:
        Run the named file inside IPython as a program.
    %save:
        Save a set of lines or a macro to a given filename.
    %sc:
        Shell capture - run shell command and capture output (DEPRECATED use !).
    %set_env:
        Set environment variables.  Assumptions are that either "val" is a
    %store:
        Lightweight persistence for python variables.
    %sx:
        Shell execute - run shell command and capture output (!! is short-hand).
    %system:
        Shell execute - run shell command and capture output (!! is short-hand).
    %tb:
        Print the last traceback with the currently active exception mode.
    %tic:
        Start a timer
    %time:
        Time execution of a Python statement or expression.
    %timeit:
        Time execution of a Python statement or expression
    %toc:
        Stop and print the timer started by the last call to %tic
    %unalias:
        Remove an alias
    %unload_ext:
        Unload an IPython extension by its module name.
    %who:
        Print all interactive variables, with some minimal formatting.
    %who_ls:
        Return a sorted list of all interactive variables.
    %whos:
        Like %who, but gives some extra information about each variable.
    %xdel:
        Delete a variable, trying to clear it from anywhere that
    %xmode:
        Switch modes for the exception handlers.
    %%!:
        Shell execute - run shell command and capture output (!! is short-hand).
    %%HTML:
        Alias for `%%html`.
    %%SVG:
        Alias for `%%svg`.
    %%bash:
        %%bash script magic
    %%capture:
        ::
    %%debug:
        ::
    %%file:
        Alias for `%%writefile`.
    %%html:
        ::
    %%javascript:
        Run the cell block of Javascript code
    %%js:
        Run the cell block of Javascript code
    %%latex:
        Render the cell as a block of latex
    %%markdown:
        Render the cell as Markdown text block
    %%perl:
        %%perl script magic
    %%prun:
        Run a statement through the python code profiler.
    %%pypy:
        %%pypy script magic
    %%python:
        %%python script magic
    %%python2:
        %%python2 script magic
    %%python3:
        %%python3 script magic
    %%ruby:
        %%ruby script magic
    %%script:
        ::
    %%sh:
        %%sh script magic
    %%svg:
        Render the cell as an SVG literal
    %%sx:
        Shell execute - run shell command and capture output (!! is short-hand).
    %%system:
        Shell execute - run shell command and capture output (!! is short-hand).
    %%time:
        Time execution of a Python statement or expression.
    %%timeit:
        Time execution of a Python statement or expression
    %%writefile:
        ::

## Tab completion

Tab completion, especially for attributes, is a convenient way to explore the structure of any object you’re dealing with. Simply type `object_name.<TAB>` to view the object’s attributes. Besides Python objects and keywords, tab completion also works on file and directory names.

```python
np.array_equal
```

## The interactive workflow: input, output, history

```python
2+10
```

    12

```python
_+10
```

    62

You can suppress the storage and rendering of output if you append `;` to the last cell (this comes in handy when plotting with matplotlib, for example):

```python
10+20;
```

```python
_
```

The output is stored in `_N` and `Out[N]` variables:

```python
Out[21]
```

```python
_15 == Out[15]
```

`%history` lets you view and search your history

```python
%history -n 1-5
```

       1: !head -n 32 "Intro to IPython.ipynb"
       2: print("Hi")
       3:
    import time

    for i in range(10):
        print(i, end=' ')
        time.sleep(1)
       4: i
       5: ?

```python
%history?
```

    Docstring:
    ::

      %history [-n] [-o] [-p] [-t] [-f FILENAME] [-g [PATTERN [PATTERN ...]]]
                   [-l [LIMIT]] [-u]
                   [range [range ...]]

    Print input history (_i<n> variables), with most recent last.

    By default, input history is printed without line numbers so it can be
    directly pasted into an editor. Use -n to show them.

    By default, all input history from the current session is displayed.
    Ranges of history can be indicated using the syntax:

    ``4``
        Line 4, current session
    ``4-6``
        Lines 4-6, current session
    ``243/1-5``
        Lines 1-5, session 243
    ``~2/7``
        Line 7, session 2 before current
    ``~8/1-~6/5``
        From the first line of 8 sessions ago, to the fifth line of 6
        sessions ago.

    Multiple ranges can be entered, separated by spaces

    The same syntax is used by %macro, %save, %edit, %rerun

    Examples
    --------
    ::

      In [6]: %history -n 4-6
      4:a = 12
      5:print a**2
      6:%history -n 4-6

    positional arguments:
      range

    optional arguments:
      -n                    print line numbers for each input. This feature is
                            only available if numbered prompts are in use.
      -o                    also print outputs for each input.
      -p                    print classic '>>>' python prompts before each input.
                            This is useful for making documentation, and in
                            conjunction with -o, for producing doctest-ready
                            output.
      -t                    print the 'translated' history, as IPython understands
                            it. IPython filters your input and converts it all
                            into valid Python source before executing it (things
                            like magics or aliases are turned into function calls,
                            for example). With this option, you'll see the native
                            history instead of the user-entered version: '%cd /'
                            will be seen as 'get_ipython().run_line_magic("cd",
                            "/")' instead of '%cd /'.
      -f FILENAME           FILENAME: instead of printing the output to the
                            screen, redirect it to the given file. The file is
                            always overwritten, though *when it can*, IPython asks
                            for confirmation first. In particular, running the
                            command 'history -f FILENAME' from the IPython
                            Notebook interface will replace FILENAME even if it
                            already exists *without* confirmation.
      -g <[PATTERN [PATTERN ...]]>
                            treat the arg as a glob pattern to search for in
                            (full) history. This includes the saved history
                            (almost all commands ever written). The pattern may
                            contain '?' to match one unknown character and '*' to
                            match any number of unknown characters. Use '%hist -g'
                            to show full saved history (may be very long).
      -l <[LIMIT]>          get the last n lines from all sessions. Specify n as a
                            single arg, or the default is the last 10 lines.
      -u                    when searching history using `-g`, show only unique
                            history.
    File:      ~/dev/ip/ipython/IPython/core/magics/history.py

## Accessing the underlying operating system

```python
import os
print(os.getcwd())
```

    /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter

```python
!pwd
```

    /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter

```python
!ls -la
```

    total 3808
    drwxr-xr-x   7 minrk  staff      224 May 25 14:19 .
    drwxr-xr-x  11 minrk  staff      352 May 25 13:27 ..
    drwxr-xr-x   5 minrk  staff      160 May 25 13:58 .ipynb_checkpoints
    -rw-r--r--   1 minrk  staff   104035 May 25 14:19 Intro to IPython.ipynb
    -rw-r--r--   1 minrk  staff   500312 May 25 14:09 Lorenz Differential Equations.ipynb
    -rw-r--r--   1 minrk  staff  1311739 May 25 14:00 Profiling and Optimizing with IPython.ipynb
    -rw-r--r--@  1 minrk  staff    22868 Sep 23  2015 jupyter-logo.png

```python
ls
```

    Intro to IPython.ipynb
    Lorenz Differential Equations.ipynb
    Profiling and Optimizing with IPython.ipynb
    jupyter-logo.png

```python
files = !ls
print("My current directory's files:")
print(files)
```

    My current directory's files:
    ['Intro to IPython.ipynb', 'Lorenz Differential Equations.ipynb', 'Profiling and Optimizing with IPython.ipynb', 'jupyter-logo.png']

```python
for f in files:
    print(f * 2)
```

    Intro to IPython.ipynbIntro to IPython.ipynb
    Lorenz Differential Equations.ipynbLorenz Differential Equations.ipynb
    Profiling and Optimizing with IPython.ipynbProfiling and Optimizing with IPython.ipynb
    jupyter-logo.pngjupyter-logo.png

```python
!echo $files
```

    [Intro to IPython.ipynb, Lorenz Differential Equations.ipynb, Profiling and Optimizing with IPython.ipynb, jupyter-logo.png]

```python
!echo {files[0].upper()}
```

    INTRO TO IPYTHON.IPYNB

Note that all this is available even in multiline blocks:

```python
import os
for i,f in enumerate(files):
    if f.endswith('ipynb'):
        !echo {"%02d" % i} - "{os.path.splitext(f)[0]}"
    else:
        print('--')
```

    00 - Intro to IPython
    01 - Lorenz Differential Equations
    02 - Profiling and Optimizing with IPython
    --

```python
%history -t 34
```

## Beyond Python: magic functions

The IPyhton 'magic' functions are a set of commands, invoked by prepending one or two `%` signs to their name, that live in a namespace separate from your normal Python variables and provide a more command-like interface.  They take flags with `--` and arguments without quotes, parentheses or commas. The motivation behind this system is two-fold:

- To provide an orthogonal namespace for controlling IPython itself and exposing other system-oriented functionality.

- To expose a calling mode that requires minimal verbosity and typing while working interactively.  Thus the inspiration taken from the classic Unix shell style for commands.

Line vs cell magics:

```python
%timeit list(range(1000))
```

    13 µs ± 26.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

```python
%%timeit
list(range(10))
list(range(100))
```

    1.54 µs ± 45.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Line magics can be used even inside code blocks:

```python
for i in range(1, 5):
    size = i*100
    print('size:', size, end=' ')
    %timeit list(range(size))
```

    size: 100 1.05 µs ± 30.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    size: 200 1.5 µs ± 41.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    size: 300 2.52 µs ± 128 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    size: 400 4.13 µs ± 138 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

```python
%timeit time.sleep(0.1)
```

    104 ms ± 770 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

```python
%timeit?
```

    Docstring:
    Time execution of a Python statement or expression

    Usage, in line mode:
      %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
    or in cell mode:
      %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
      code
      code...

    Time execution of a Python statement or expression using the timeit
    module.  This function can be used both as a line and cell magic:

    - In line mode you can time a single-line statement (though multiple
      ones can be chained with using semicolons).

    - In cell mode, the statement in the first line is used as setup code
      (executed but not timed) and the body of the cell is timed.  The cell
      body has access to any variables created in the setup code.

    Options:
    -n<N>: execute the given statement <N> times in a loop. If this value
    is not given, a fitting value is chosen.

    -r<R>: repeat the loop iteration <R> times and take the best result.
    Default: 3

    -t: use time.time to measure the time, which is the default on Unix.
    This function measures wall time.

    -c: use time.clock to measure the time, which is the default on
    Windows and measures wall time. On Unix, resource.getrusage is used
    instead and returns the CPU user time.

    -p<P>: use a precision of <P> digits to display the timing result.
    Default: 3

    -q: Quiet, do not print result.

    -o: return a TimeitResult that can be stored in a variable to inspect
        the result in more details.

    Examples
    --------
    ::

      In [1]: %timeit pass
      8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

      In [2]: u = None

      In [3]: %timeit u is None
      29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

      In [4]: %timeit -r 4 u == None

      In [5]: import time

      In [6]: %timeit -n1 time.sleep(2)

    The times reported by %timeit will be slightly higher than those
    reported by the timeit.py script when variables are accessed. This is
    due to the fact that %timeit executes the statement in the namespace
    of the shell, compared with timeit.py, which uses a single setup
    statement to import function or create variables. Generally, the bias
    does not matter as long as results from timeit.py are not mixed with
    those from %timeit.
    File:      ~/dev/ip/ipython/IPython/core/magics/execution.py

Magics can do anything they want with their input, so it doesn't have to be valid Python:

```bash
%%bash
echo "My shell is:" $SHELL
echo "My disk usage is:"
df -h
```

    My shell is: /usr/local/bin/bash
    My disk usage is:
    Filesystem      Size   Used  Avail Capacity iused               ifree %iused  Mounted on
    /dev/disk1s1   466Gi  353Gi  108Gi    77% 3738151 9223372036851037656    0%   /
    devfs          194Ki  194Ki    0Bi   100%     670                   0  100%   /dev
    /dev/disk1s4   466Gi  4.0Gi  108Gi     4%       4 9223372036854775803    0%   /private/var/vm
    map -hosts       0Bi    0Bi    0Bi   100%       0                   0  100%   /net
    map auto_home    0Bi    0Bi    0Bi   100%       0                   0  100%   /home

```ruby
%%ruby
puts "hello"
```

    hello

Another interesting cell magic: create any file you want locally from the notebook:

```python
%%writefile test.txt
This is a test file!
It can contain anything I want...

And more...
```

    Writing test.txt

```python
!cat test.txt
```

    This is a test file!
    It can contain anything I want...

    And more...

Let's see what other magics are currently defined in the system:

```python
%lsmagic
```

    Available line magics:
    %alias  %alias_magic  %autocall  %automagic  %autosave  %bookmark  %cat  %cd  %clear  %colors  %config  %connect_info  %cp  %debug  %dhist  %dirs  %doctest_mode  %ed  %edit  %env  %gui  %hist  %history  %killbgscripts  %ldir  %less  %lf  %lk  %ll  %load  %load_ext  %loadpy  %logoff  %logon  %logstart  %logstate  %logstop  %ls  %lsmagic  %lx  %macro  %magic  %man  %matplotlib  %mkdir  %more  %mv  %namespace  %notebook  %page  %pastebin  %pdb  %pdef  %pdoc  %pfile  %pinfo  %pinfo2  %popd  %pprint  %precision  %prun  %psearch  %psource  %pushd  %pwd  %pycat  %pylab  %qtconsole  %quickref  %recall  %rehashx  %reload_ext  %rep  %rerun  %reset  %reset_selective  %rm  %rmdir  %run  %save  %sc  %set_env  %store  %sx  %system  %tb  %tic  %time  %timeit  %toc  %unalias  %unload_ext  %who  %who_ls  %whos  %xdel  %xmode

    Available cell magics:
    %%!  %%HTML  %%SVG  %%bash  %%capture  %%debug  %%file  %%html  %%javascript  %%js  %%latex  %%markdown  %%perl  %%prun  %%pypy  %%python  %%python2  %%python3  %%ruby  %%script  %%sh  %%svg  %%sx  %%system  %%time  %%timeit  %%writefile

    Automagic is ON, % prefix IS NOT needed for line magics.

```python
%%capture?
```

    Docstring:
    ::

      %capture [--no-stderr] [--no-stdout] [--no-display] [output]

    run the cell, capturing stdout, stderr, and IPython's rich display() calls.

    positional arguments:
      output        The name of the variable in which to store output. This is a
                    utils.io.CapturedIO object with stdout/err attributes for the
                    text of the captured output. CapturedOutput also has a show()
                    method for displaying the output, and __call__ as well, so you
                    can use that to quickly display the output. If unspecified,
                    captured output is discarded.

    optional arguments:
      --no-stderr   Don't capture stderr.
      --no-stdout   Don't capture stdout.
      --no-display  Don't capture IPython's rich display.
    File:      ~/dev/ip/ipython/IPython/core/magics/execution.py

```python
import math
math.pi
```

    3.141592653589793

```python
%precision 2
math.pi
```

    3.14

```python
%precision?
```

## Running normal Python code: execution and errors

Not only can you input normal Python code, you can even paste straight from a Python or IPython shell session:

```python
>>> # Fibonacci series:
... # the sum of two elements defines the next
... a, b = 0, 1
>>> while b < 10:
...     print(b)
...     a, b = b, a+b

```

```python
import sys
for i in range(32):
    print(i, end=' ')
    time.sleep(0.1)

```

    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

And when your code produces errors, you can control how they are displayed with the `%xmode` magic:

```python
%%writefile mod.py

def f(x):
    return 1.0/(x-1)

def g(y):
    return f(y+1)
```

    Writing mod.py

Now let's call the function `g` with an argument that would produce an error:

```python
import mod
mod.g(0)
```

```python
%xmode verbose
```

    Exception reporting mode: Verbose

```python
mod.g(0)
```

## Raw Input in the notebook

Since 1.0 the IPython notebook web application support `raw_input` which for example allow us to invoke the `%debug` magic in the notebook:

```python
%debug
```

    > /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter/mod.py(3)f()
          1
          2 def f(x):
    ----> 3     return 1.0/(x-1)
          4
          5 def g(y):

    1
    > /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter/mod.py(6)g()
          2 def f(x):
          3     return 1.0/(x-1)
          4
          5 def g(y):
    ----> 6     return f(y+1)

    > /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter/mod.py(3)f()
          1
          2 def f(x):
    ----> 3     return 1.0/(x-1)
          4
          5 def g(y):

    > /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter/mod.py(6)g()
          2 def f(x):
          3     return 1.0/(x-1)
          4
          5 def g(y):
    ----> 6     return f(y+1)

    > <ipython-input-51-9fa96bd6b3b6>(1)<module>()
    ----> 1 mod.g(0)

    > /Users/minrk/dev/simula/tools-meetup/2018-05-25-jupyter/mod.py(6)g()
          2 def f(x):
          3     return 1.0/(x-1)
          4
          5 def g(y):
    ----> 6     return f(y+1)

    <function f at 0x10b423400>
    0
    --KeyboardInterrupt--
    --KeyboardInterrupt--

Don't foget to exit your debugging session. Raw input can of course be use to ask for user input:

```python
colour = input('What is your favourite colour? ')
print('colour is:', colour)
```

    colour is: x

## Running code in other languages with special `%%` magics

```perl
%%perl
@months = ("July", "August", "September");
print $months[0];
```

```ruby
%%ruby
name = "world"
puts "Hello #{name.capitalize}!"
```

## Plotting in the notebook

This magic configures matplotlib to render its figures inline:

```python
%matplotlib inline
```

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

```python
x = np.linspace(0, 2*np.pi, 300)
y = np.sin(x**2)
plt.plot(x, y)
plt.title("A little chirp")
fig = plt.gcf()  # let's keep the figure object around for later...
```

### Widgets

```python
from ipywidgets import interact

@interact
def show_args(num=5, text='hello', check=True):
    print(locals())
```

    interactive(children=(IntSlider(value=5, description='num', max=15, min=-5), Text(value='hello', description='…

```python
%timeit time.sleep(0.1)
```

```python
%history -t 72
```

```python
import sympy
from sympy import Symbol, Eq, factor
x = Symbol('x')
sympy.init_printing(use_latex='mathjax')
x
```

$$x$$

```python
@interact(n=(1,21))
def factorit(n):
    return Eq(x**n-1, factor(x**n-1))
```

    interactive(children=(IntSlider(value=11, description='n', max=21, min=1), Output()), _dom_classes=('widget-in…

```python

```


[SCEC BP3-QD](https://strike.scec.org/cvws/seas/download/SEAS_BP3.pdf) document is here.

# [DRAFT] Quasidynamic thrust fault earthquake cycles (plane strain)

## Summary

* Most of the code here follows almost exactly from [the previous section on strike-slip/antiplane earthquake cycles](part6_qd).
* Since the fault motion is in the same plane as the fault normal vectors, we are no longer operating in an antiplane approximation. Instead, we use plane strain elasticity, a different 2D reduction of full 3D elasticity.
* One key difference is the vector nature of the displacement and the tensor nature of the stress. We must always make sure we are dealing with tractions on the correct surface.
* We construct a mesh, build our discrete boundary integral operators, step through time and then compare against other benchmark participants' results.

Does this section need detailed explanation or is it best left as lonely code? Most of the explanation would be redundant with the antiplane QD document.

```python
from tectosaur2.nb_config import setup

setup()
```

```python
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
from tectosaur2 import gauss_rule, refine_surfaces, integrate_term, panelize_symbolic_surface
from tectosaur2.elastic2d import elastic_t, elastic_h
from tectosaur2.rate_state import MaterialProps, qd_equation, solve_friction, aging_law
```

```python
surf_half_L = 1000000
fault_length = 40000
max_panel_length = 400
n_fault = 400
mu = shear_modulus = 3.2e10
nu = 0.25

quad_rule = gauss_rule(6)
sp_t = sp.var("t")

angle_rad = sp.pi / 6
sp_x = (sp_t + 1) / 2 * sp.cos(angle_rad) * fault_length
sp_y = -(sp_t + 1) / 2 * sp.sin(angle_rad) * fault_length
fault = panelize_symbolic_surface(
    sp_t, sp_x, sp_y,
    quad_rule,
    n_panels=n_fault
)

free = refine_surfaces(
    [
        (sp_t, -sp_t * surf_half_L, 0 * sp_t)  # free surface
    ],
    quad_rule,
    control_points = [
        # nearfield surface panels and fault panels will be limited to 200m
        # at 200m per panel, we have ~40m per solution node because the panels
        # have 5 nodes each
        (0, 0, 1.5 * fault_length, max_panel_length),
        (0, 0, 0.2 * fault_length, 1.5 * fault_length / (n_fault)),
        # farfield panels will be limited to 200000 m per panel at most
        (0, 0, surf_half_L, 50000),
    ]
)
print(
    f"The free surface mesh has {free.n_panels} panels with a total of {free.n_pts} points."
)
print(
    f"The fault mesh has {fault.n_panels} panels with a total of {fault.n_pts} points."
)
```

    The free surface mesh has 646 panels with a total of 3876 points.
    The fault mesh has 400 panels with a total of 2400 points.

```python
plt.plot(free.pts[:,0]/1000, free.pts[:,1]/1000, 'k-o')
plt.plot(fault.pts[:,0]/1000, fault.pts[:,1]/1000, 'r-o')
plt.xlabel(r'$x ~ \mathrm{(km)}$')
plt.ylabel(r'$y ~ \mathrm{(km)}$')
plt.axis('scaled')
plt.xlim([-100, 100])
plt.ylim([-80, 20])
plt.show()
```

And, to start off the integration, we'll construct the operators necessary for solving for free surface displacement from fault slip.

```python
singularities = np.array(
    [
        [-surf_half_L, 0],
        [surf_half_L, 0],
        [0, 0],
        [float(sp_x.subs(sp_t,1)), float(sp_y.subs(sp_t,1))],
    ]
)
```

```python
(free_disp_to_free_disp, fault_slip_to_free_disp), report = integrate_term(
    elastic_t(nu), free.pts, free, fault, singularities=singularities, safety_mode=True, return_report=True
)
```

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:204: UserWarning: Some integrals failed to converge during adaptive integration. This an indication of a problem in either the integration or the problem formulation.
      warnings.warn(

```python
fault_slip_to_free_disp = fault_slip_to_free_disp.reshape((-1, 2 * fault.n_pts))
free_disp_to_free_disp = free_disp_to_free_disp.reshape((-1, 2 * free.n_pts))
free_disp_solve_mat = (
    np.eye(free_disp_to_free_disp.shape[0]) + free_disp_to_free_disp
)
```

```python
from tectosaur2.elastic2d import ElasticH

(free_disp_to_fault_stress, fault_slip_to_fault_stress), report = integrate_term(
    ElasticH(nu, d_cutoff=8.0),
    # elastic_h(nu),
    fault.pts,
    free,
    fault,
    tol=1e-12,
    safety_mode=True,
    singularities=singularities,
    return_report=True,
)
fault_slip_to_fault_stress *= shear_modulus
free_disp_to_fault_stress *= shear_modulus
```

**We're not achieving the tolerance we asked for!!**
Hypersingular integrals can be tricky but I think this is solvable.

```python

report['integration_error'].max()
```

    1.4504879515499894e-07

```python
A = -fault_slip_to_fault_stress.reshape((-1, 2 * fault.n_pts))
B = -free_disp_to_fault_stress.reshape((-1, 2 * free.n_pts))
C = fault_slip_to_free_disp
Dinv = np.linalg.inv(free_disp_solve_mat)
total_fault_slip_to_fault_stress = A - B.dot(Dinv.dot(C))

nx = fault.normals[:, 0]
ny = fault.normals[:, 1]
normal_mult = np.transpose(np.array([[nx, 0 * nx, ny], [0 * nx, ny, nx]]), (2, 0, 1))

total_fault_slip_to_fault_traction = np.sum(
    total_fault_slip_to_fault_stress.reshape((-1, 3, fault.n_pts, 2))[:, None, :, :, :]
    * normal_mult[:, :, :, None, None],
    axis=2,
).reshape((-1, 2 * fault.n_pts))
```

## Rate and state friction

```python
siay = 31556952  # seconds in a year
density = 2670  # rock density (kg/m^3)
cs = np.sqrt(shear_modulus / density)  # Shear wave speed (m/s)
Vp = 1e-9  # Rate of plate motion
sigma_n0 = 50e6  # Normal stress (Pa)

# parameters describing "a", the coefficient of the direct velocity strengthening effect
a0 = 0.01
amax = 0.025
H = 15000
h = 3000
fx = fault.pts[:, 0]
fy = fault.pts[:, 1]
fd = -np.sqrt(fx ** 2 + fy ** 2)
a = np.where(
    fd > -H, a0, np.where(fd > -(H + h), a0 + (amax - a0) * (fd + H) / -h, amax)
)

mp = MaterialProps(a=a, b=0.015, Dc=0.008, f0=0.6, V0=1e-6, eta=shear_modulus / (2 * cs))
```

```python
plt.figure(figsize=(3, 5))
plt.plot(mp.a, fd/1000, label='a')
plt.plot(np.full(fy.shape[0], mp.b), fd/1000, label='b')
plt.xlim([0, 0.03])
plt.ylabel('depth')
plt.legend()
plt.show()
```

```python
mesh_L = np.max(np.abs(np.diff(fd)))
Lb = shear_modulus * mp.Dc / (sigma_n0 * mp.b)
hstar = (np.pi * shear_modulus * mp.Dc) / (sigma_n0 * (mp.b - mp.a))
mesh_L, Lb, np.min(hstar[hstar > 0])
```

    (23.861918608330598, 341.3333333333333, 3216.990877275949)

## Quasidynamic earthquake cycle derivatives

```python
from scipy.optimize import fsolve
import copy

init_state_scalar = fsolve(lambda S: aging_law(mp, Vp, S), 0.7)[0]
mp_amax = copy.copy(mp)
mp_amax.a=amax
tau_amax = -qd_equation(mp_amax, sigma_n0, 0, Vp, init_state_scalar)
init_state = np.log((2*mp.V0/Vp)*np.sinh((tau_amax - mp.eta*Vp) / (mp.a*sigma_n0))) * mp.a

init_tau = np.full(fault.n_pts, tau_amax)
init_sigma = np.full(fault.n_pts, sigma_n0)
init_slip_deficit = np.zeros(fault.n_pts)
init_conditions = np.concatenate((init_slip_deficit, init_state))
```

```python
class SystemState:

    V_old = np.full(fault.n_pts, Vp)
    state = None

    def calc(self, t, y, verbose=False):
        # Separate the slip_deficit and state sub components of the
        # time integration state.
        slip_deficit = y[: init_slip_deficit.shape[0]]
        state = y[init_slip_deficit.shape[0] :]

        # If the state values are bad, then the adaptive integrator probably
        # took a bad step.
        if np.any((state < 0) | (state > 2.0)):
            print("bad state")
            return False

        # The big three lines solving for quasistatic shear stress, slip rate
        # and state evolution
        sd_vector = np.stack((slip_deficit * -ny, slip_deficit * nx), axis=1).ravel()
        traction = total_fault_slip_to_fault_traction.dot(sd_vector).reshape((-1, 2))
        delta_sigma_qs = np.sum(traction * np.stack((nx, ny), axis=1), axis=1)
        delta_tau_qs = -np.sum(traction * np.stack((-ny, nx), axis=1), axis=1)
        tau_qs = init_tau + delta_tau_qs
        sigma_qs = init_sigma + delta_sigma_qs

        V = solve_friction(mp, sigma_qs, tau_qs, self.V_old, state)
        if not V[2]:
            print("convergence failed")
            return False

        V=V[0]
        if not np.all(np.isfinite(V)):
            print("infinite V")
            return False
        dstatedt = aging_law(mp, V, state)
        self.V_old = V

        slip_deficit_rate = Vp - V
        out = (
            slip_deficit,
            state,
            delta_sigma_qs,
            sigma_qs,
            delta_tau_qs,
            tau_qs,
            V,
            slip_deficit_rate,
            dstatedt,
        )
        self.data = out
        return self.data
```

```python
def plot_system_state(t, SS, xlim=None):
    """This is just a helper function that creates some rough plots of the
    current state to help with debugging"""
    (
        slip_deficit,
        state,
        delta_sigma_qs,
        sigma_qs,
        delta_tau_qs,
        tau_qs,
        V,
        slip_deficit_rate,
        dstatedt,
    ) = SS

    slip = Vp * t - slip_deficit

    fd = -np.linalg.norm(fault.pts, axis=1)

    plt.figure(figsize=(15, 9))
    plt.suptitle(f"t={t/siay}")
    plt.subplot(3, 3, 1)
    plt.title("slip")
    plt.plot(fd, slip)
    plt.xlim(xlim)

    plt.subplot(3, 3, 2)
    plt.title("slip deficit")
    plt.plot(fd, slip_deficit)
    plt.xlim(xlim)

    # plt.subplot(3, 3, 2)
    # plt.title("slip deficit rate")
    # plt.plot(fd, slip_deficit_rate)
    # plt.xlim(xlim)

    # plt.subplot(3, 3, 2)
    # plt.title("strength")
    # plt.plot(fd, tau_qs/sigma_qs)
    # plt.xlim(xlim)

    plt.subplot(3, 3, 3)
    # plt.title("log V")
    # plt.plot(fd, np.log10(V))
    plt.title("V")
    plt.plot(fd, V)
    plt.xlim(xlim)

    plt.subplot(3, 3, 4)
    plt.title(r"$\sigma_{qs}$")
    plt.plot(fd, sigma_qs)
    plt.xlim(xlim)

    plt.subplot(3, 3, 5)
    plt.title(r"$\tau_{qs}$")
    plt.plot(fd, tau_qs, 'k-o')
    plt.xlim(xlim)

    plt.subplot(3, 3, 6)
    plt.title("state")
    plt.plot(fd, state)
    plt.xlim(xlim)

    plt.subplot(3, 3, 7)
    plt.title(r"$\Delta\sigma_{qs}$")
    plt.plot(fd, delta_sigma_qs)
    plt.hlines([0], [fd[-1]], [fd[0]])
    plt.xlim(xlim)

    plt.subplot(3, 3, 8)
    plt.title(r"$\Delta\tau_{qs}$")
    plt.plot(fd, delta_tau_qs)
    plt.hlines([0], [fd[-1]], [fd[0]])
    plt.xlim(xlim)

    plt.subplot(3, 3, 9)
    plt.title("dstatedt")
    plt.plot(fd, dstatedt)
    plt.xlim(xlim)
    plt.tight_layout()

    plt.show()
```

```python
def calc_derivatives(state, t, y):
    """
    This helper function calculates the system state and then extracts the
    relevant derivatives that the integrator needs. It also intentionally
    returns infinite derivatives when the `y` vector provided by the integrator
    is invalid.
    """
    if not np.all(np.isfinite(y)):
        return np.inf * y
    state_vecs = state.calc(t, y)
    if not state_vecs:
        return np.inf * y
    derivatives = np.concatenate((state_vecs[-2], state_vecs[-1]))
    return derivatives
```

## Integrating through time

```python
%%time
from scipy.integrate import RK23, RK45

# We use a 5th order adaptive Runge Kutta method and pass the derivative function to it
# the relative tolerance will be 1e-11 to make sure that even
state = SystemState()
derivs = lambda t, y: calc_derivatives(state, t, y)
integrator = RK45
atol = Vp * 1e-6
rtol = 1e-11
rk = integrator(derivs, 0, init_conditions, 1e50, atol=atol, rtol=rtol)

# Set the initial time step to one day.
rk.h_abs = 60 * 60 * 24

# Integrate for 1000 years.
max_T = 1000 * siay

n_steps = 500000
t_history = [0]
y_history = [init_conditions.copy()]
for i in range(n_steps):
    # Take a time step and store the result
    if rk.step() != None:
        raise Exception("TIME STEPPING FAILED")
    t_history.append(rk.t)
    y_history.append(rk.y.copy())

    # Print the time every 5000 steps
    if i % 5000 == 0:
        print(f"step={i}, time={rk.t / siay} yrs, step={(rk.t - t_history[-2]) / siay}")

    if rk.t > max_T:
        break

y_history = np.array(y_history)
t_history = np.array(t_history)
```

    step=0, time=1.4616397558414107e-05 yrs, step=1.4616397558414107e-05
    step=5000, time=133.40079223999558 yrs, step=0.025598497379265003
    step=10000, time=176.14015294812103 yrs, step=5.998816102602071e-11
    step=15000, time=176.14015339898557 yrs, step=8.238172642666623e-11
    step=20000, time=176.14296040288025 yrs, step=2.1979783596630634e-05
    step=25000, time=263.1375647457368 yrs, step=0.0006967333900754272
    step=30000, time=263.2373556060858 yrs, step=5.5787478717397596e-11
    step=35000, time=263.23735589847996 yrs, step=1.6116718526537453e-10
    step=40000, time=314.8023513177958 yrs, step=0.030260419123390083
    step=45000, time=327.9773806823734 yrs, step=7.065608084576142e-11
    step=50000, time=327.97738099773073 yrs, step=3.294060227625318e-11
    step=55000, time=356.3693505901927 yrs, step=0.02798904784696968
    step=60000, time=414.56802731019104 yrs, step=4.732567262808485e-11
    step=65000, time=414.56802756651166 yrs, step=4.1885940142098087e-11
    step=70000, time=414.9294840754991 yrs, step=0.002112030610137971
    step=75000, time=479.28663934235965 yrs, step=5.784248876765926e-11
    step=80000, time=479.28663967884637 yrs, step=7.796949896581029e-11
    step=85000, time=479.2877011529307 yrs, step=8.501305135281465e-06
    step=90000, time=565.6498981880231 yrs, step=0.001480316487609428
    step=95000, time=565.8770021991605 yrs, step=5.4639090748133724e-11
    step=100000, time=565.877002481205 yrs, step=1.531586902165607e-10
    step=105000, time=614.5975778365752 yrs, step=0.029474945371490282
    step=110000, time=630.595404186933 yrs, step=6.22547162285152e-11
    step=115000, time=630.5954045063037 yrs, step=3.6990180904709996e-11
    step=120000, time=656.0166350083252 yrs, step=0.02921588740701827
    step=125000, time=717.1857705022477 yrs, step=4.0979318061100294e-11
    step=130000, time=717.1857707585524 yrs, step=5.234231480960598e-11
    step=135000, time=717.3785763542687 yrs, step=0.0010220541964367556
    step=140000, time=781.904169712801 yrs, step=6.805709754690108e-11
    step=145000, time=781.904170048407 yrs, step=7.603537185968166e-11
    step=150000, time=781.9045694275084 yrs, step=3.6081749206038533e-06
    step=155000, time=867.9930534924584 yrs, step=0.0034391542903144255
    step=160000, time=868.4945363826215 yrs, step=5.258408069787206e-11
    step=165000, time=868.4945366547215 yrs, step=1.5182897783109724e-10
    step=170000, time=914.161314434437 yrs, step=0.030127968410185897
    step=175000, time=933.2129355866244 yrs, step=7.228800059155745e-11
    step=180000, time=933.2129359097105 yrs, step=3.505605379858137e-11
    step=185000, time=955.611410869588 yrs, step=0.025085701504597362
    CPU times: user 5h 6min 33s, sys: 27min 1s, total: 5h 33min 35s
    Wall time: 1h 27min 50s

## Plotting the results

Now that we've solved for 1000 years of fault slip evolution, let's plot some of the results. I'll start with a super simple plot of the maximum log slip rate over time.

```python
derivs_history = np.diff(y_history, axis=0) / np.diff(t_history)[:, None]
max_vel = np.max(np.abs(derivs_history), axis=1)
plt.plot(t_history[1:] / siay, np.log10(max_vel))
plt.xlabel('$t ~~ \mathrm{(yrs)}$')
plt.ylabel('$\log_{10}(V)$')
plt.show()
```

And next, we'll make the classic plot showing the spatial distribution of slip over time:
- the blue lines show interseismic slip evolution and are plotted every fifteen years
- the red lines show evolution during rupture every three seconds.

```python
plt.figure(figsize=(10, 4))
last_plt_t = -1000
last_plt_slip = init_slip_deficit
event_times = []
for i in range(len(y_history) - 1):
    y = y_history[i]
    t = t_history[i]
    slip_deficit = y[: init_slip_deficit.shape[0]]
    should_plot = False

    # Plot a red line every three second if the slip rate is over 0.1 mm/s.
    if (
        max_vel[i] >= 0.0001 and t - last_plt_t > 3
    ):
        if len(event_times) == 0 or t - event_times[-1] > siay:
            event_times.append(t)
        should_plot = True
        color = "r"

    # Plot a blue line every fifteen years during the interseismic period
    if t - last_plt_t > 15 * siay:
        should_plot = True
        color = "b"

    if should_plot:
        # Convert from slip deficit to slip:
        slip = -slip_deficit + Vp * t
        plt.plot(slip, fd / 1000.0, color + "-", linewidth=0.5)
        last_plt_t = t
        last_plt_slip = slip
plt.xlim([0, np.max(last_plt_slip)])
plt.ylim([-40, 0])
plt.ylabel(r"$\textrm{z (km)}$")
plt.xlabel(r"$\textrm{slip (m)}$")
plt.tight_layout()
plt.savefig("halfspace.png", dpi=300)
plt.show()
```

And a plot of recurrence interval:

```python
plt.title("Recurrence interval")
plt.plot(np.diff(event_times) / siay, "k-*")
plt.xticks(np.arange(0, 10, 1))
plt.yticks(np.arange(75, 80, 0.5))
plt.xlabel("Event number")
plt.ylabel("Time between events (yr)")
plt.show()
```

## Comparison against SCEC SEAS results

```python
ozawa_data = np.loadtxt("ozawa7500.txt")
ozawa_slip_rate = 10 ** ozawa_data[:, 2]
ozawa_stress = ozawa_data[:, 3]
```

```python
t_start_idx = np.argmax(max_vel > 1e-4)
t_end_idx = np.argmax(max_vel[t_start_idx:] < 1e-6)
n_steps = t_end_idx - t_start_idx
t_chunk = t_history[t_start_idx : t_end_idx]
```

```python
shear_chunk = []
slip_rate_chunk = []
for i in range(n_steps):
    system_state = SystemState().calc(t_history[t_start_idx + i], y_history[t_start_idx + i])
    slip_deficit, state, delta_sigma_qs, sigma_qs, delta_tau_qs, tau_qs, V, slip_deficit_rate, dstatedt = system_state
    shear_chunk.append((tau_qs - mp.eta * V))
    slip_rate_chunk.append(V)
shear_chunk = np.array(shear_chunk)
slip_rate_chunk = np.array(slip_rate_chunk)
```

```python
fault_idx = np.argmax((-7450 > fd) & (fd > -7550))
VAvg = np.mean(slip_rate_chunk[:, fault_idx:(fault_idx+2)], axis=1)
SAvg = np.mean(shear_chunk[:, fault_idx:(fault_idx+2)], axis=1)
```

```python
fault_idx
```

    447

```python
t_align = t_chunk[np.argmax(VAvg > 0.2)]
ozawa_t_align = np.argmax(ozawa_slip_rate > 0.2)

for lims in [(-1, 1), (-15, 30)]:
    plt.figure(figsize=(12, 8))
    plt.subplot(2, 1, 1)
    plt.plot(t_chunk - t_align, SAvg / 1e6, "k-o", markersize=0.5, linewidth=0.5, label='here')
    plt.plot(
        ozawa_data[:, 0] - ozawa_data[ozawa_t_align, 0],
        ozawa_stress,
        "b-*",
        markersize=0.5,
        linewidth=0.5,
        label='ozawa'
    )
    plt.legend()
    plt.xlim(lims)
    plt.xlabel("Time (s)")
    plt.ylabel("Shear Stress (MPa)")
    # plt.show()

    plt.subplot(2, 1, 2)
    plt.plot(t_chunk - t_align, VAvg, "k-o", markersize=0.5, linewidth=0.5, label='here')
    plt.plot(
        ozawa_data[:, 0] - ozawa_data[ozawa_t_align, 0],
        ozawa_slip_rate[:],
        "b-*",
        markersize=0.5,
        linewidth=0.5,
        label='ozawa'
    )
    plt.legend()
    plt.xlim(lims)
    plt.xlabel("Time (s)")
    plt.ylabel("Slip rate (m/s)")
    plt.tight_layout()
    plt.show()
```


## Simulating  Active Brownian motion
In this notebook, we simulate the trajectory of a single, non-interacting active Brownian particle. Unlike passive Brownian particles (e.g. pollen grains in water) that do diffusive random walks, active Brownian particles (ABPs) propel themselves with directed motion in addition to diffusing randomly.

The APB model can be used to accurately desribe the motion of certain types of cells, those which locomote actively using focal adhesions. The distribution of focal adhesions within a cell may vary continuously, tending to reorient the cell with respect to it's past momentum and giving rise to different speeds with which the cell moves. It is this angular reorientation which can be modelled as a diffusive process.

Durotaxis is the preference for cells to move preferentially towards a stiffer substrate. In this project we implement durotaxis by incorporating the dependence of a cell's rotational diffusion properties on the stiffness of the medium upon which a cell is crawling.

*It should be noted that although all of the simulations in this project display the trajectories or data associated with numerous cells, intercellular interactions are not taken into account here. This is a simple single cell model.*

```python
import argparse

parser = argparse.ArgumentParser()

parser.add_argument('L', default = 3, help = 'width of stiffness transition region', type = float)

parser.add_argument('Dr', default = 5, help = 'baseline rotational diffusion constant', type = float)

parser.add_argument('ks', default = 1, help = 'stiffness on soft side of system', type = float)

parser.add_argument('kh', default = 50, help = 'stiffness on hard side of system', type = float)

parser.add_argument('vel', default = 1, help = 'inherent cellular velocity', type = float)

parser.add_argument('nw', default = 1000, help = '# of cells used to collect statistical data', type = int)

parser.add_argument('bb', default = 20, help = 'size of system', type = int)

parser.add_argument('ns', default = 1000, help = '# of steps each cell takes', type = int)

args = parser.parse_args()

L = args.L

Drot0 = args.Dr

ks = args.ks

kh = args.kh

vel = args.vel

num_walks = args.nw

Bbox = args.bb

num_steps = args.ns
```

```python
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from numpy.random import random as rand
from scipy import stats

import os.path
from os import path
```

### 2D Confined ABP model with durotaxis: an overview.
Cells perform a ballistic step (proportional to $dt$) and random walk in the space of $\theta$-values.

Furthermore we implement walled boundary conditions, treating a wall as a reflecting surface. If a walker goes beyond the confining boundary of the box, it's position is altered so as to keep it in the box. Note that when this reflection of a walker by a boundary occurs, the velocity of the walker is not infuenced by the reflection.

We also incorporate 1D substrate stiffness into the model. Specifically the stiffness will depend on the location of a walker within the confining box. To the left (-x) the substrate is more soft, to the right (+x) it is more stiff, and in the center is a transition region. The rotational diffusion constant of each walker is then made to depend on the stiffness of the substrate upon which it is walking, so that a walker in the stiffer region will be taking steps more ballistically, while a walker in the softer region will be taking steps more diffusively.

To extract meaningful data, we collect statistics from an ensemble of identically prepared systems, i.e. individual cells which are placed in identical environments, whose ballistic and diffusive components of motion are drawn from the same distributions.

# The Durotaxis Index (DI)

## A measure of directional preference

In the following cell we track the durotaxis index  for a given ensemble of systems at every *ten* times steps (denoted a big step in the output plot below).

The durotaxis index is calculated as follows:

\begin{equation}
    D_{i}(t) = \frac{N_{right} - N_{left}} {N_{right} + N_{left}}
\end{equation}

Where $N_{right}$ and $N_{left}$ are the number of cells (in the ensemble) which have undergone net displacement in the right and left directions respectively.

The index at a given point in time tells us the proportion of cells in an ensemble which have net displacement to the right (left) for $D_{i}$ positive (negative). Since the stiffness gradients in these systems are set up to be increasing as we move to the right, a positive index simply tells us how strongly cellular motion is influenced by this gradient. An index of zero would correspond to all cells having reached a region in the system where the substrate stiffness is constant (i.e. either the far left or far right) and so the cell would again be performing regular active brownian motion.

```python
#  ABP model

#ABP model parameters: ballistic velocity, time step, rotational diffusion constant, translational diffusion constant
dt = 0.01; Dtrans = 0.001;

Bframe = Bbox/10

Di = np.empty((num_walks,int(num_steps/10)))

for j in range(num_walks):

    # initialize arrays that store x,y and theta values, as well as initial particle position and angle

    xvec=np.zeros(0); yvec=np.zeros(0); thetavec = np.zeros(0)
    x=0.0; y = 0.0; theta = (2*np.pi)*rand(1)
    xvec = np.append(xvec,x); yvec = np.append(yvec,y); thetavec = np.append(thetavec, theta)

    dj = np.empty(int(num_steps/10))

    for i in range(num_steps):

        #determine substrate stiffness based on position
        if x > -L and x < L:
            k = ks + ((kh-ks)/(2*L))*(x + L)
        elif x > L:
            k = kh
        elif x < -L:
            k = ks

        #calculate rotational diffusion constant based on substrate stiffness
        Drot = Drot0/k

        # calculate diffusive/random steps. For the x- and y-,we generate
        #a random number between -1 & 1 to ensure that the walker can step in both directions(up/down and left/right).
        dx = np.sqrt(2*Dtrans*dt)*2*(rand(1)-0.5);
        dy= np.sqrt(2*Dtrans*dt)*2*(rand(1)-0.5);
        dtheta = np.sqrt(2*Drot*dt)*(2*np.pi)*(rand(1) - 0.5);
        # update coordinates (including ballistic step)
        x += vel*dt*np.cos(theta) + dx
        y += vel*dt*np.sin(theta) + dy
        # implement walled boundary conditions
        if x > Bbox/2:
            x -= 2*(x - Bbox/2)
        elif x < -Bbox/2:
            x -= 2*(x + Bbox/2)
        elif x < Bbox/2 and x > -Bbox/2:
            x += 0

        if y > Bbox/2:
            y -= 2*(y - Bbox/2)
        elif y < -Bbox/2:
            y -= 2*(y + Bbox/2)
        elif y < Bbox/2 and y > -Bbox/2:
            y += 0

        # store successive positions in arrays
        xvec = np.append(xvec,x); yvec = np.append(yvec,y)
        # update the angle and store in array
        theta += dtheta
        thetavec = np.append(thetavec, theta)

    for k in range(int(num_steps/10)):

        if (xvec[10*(k+1)] - xvec[10*k]) > 0:

            dj[k] =  1

        elif (xvec[10*(k+1)] - xvec[10*k]) < 0:

            dj[k] = -1

    Di[j] = dj
```

The following cell plots the DI data collected from executing the confined ABP durotaxis model.

```python
bigsteps = np.linspace(1,int(num_steps/10),int(num_steps/10))

plt.plot(bigsteps, Di.sum(0)/num_walks, 'b-')
plt.title('Durotaxis Index')
plt.xlabel('Big Step');

plt.savefig('Durotaxis index.png')
```

```python

```


# Homework 4  - PID tuning
The plant:
\begin{equation}
G(s) = \frac{e^{-Ts}}{s+ 2},
\end{equation}
where $T=0.2$.

## The ultimate sensitivity method
1. Determine the critical gain and period of oscillations analytically
2. Determine by simulation
3. Implement controller and perform a step-response

## Sample the model
1. Implement sampled controller

```python

```

```python

```

```python

```

```python

```


```python
import numpy as np
import pandas as pd
import linearsolve as ls
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Class 13: Introduction to Real Business Cycle Modeling

Real business cycle (RBC) models are extensions of the stochastic Solow model. RBC models replace the ad hoc assumption of a constant saving rate in the Solow model with the solution to an intertemporal utility maximization problem that gives rise to a variable saving rate. RBC models also often feature some sort of household labor-leisure tradeoff that produces endogenous labor varation.

In this notebook, we'll consider a baseline RBC model that does not have labor. We'll use the model to compute impulse responses to a one percent shock to TFP.

## The Baseline RBC Model without Labor

The equilibrium conditions for the RBC model without labor are:

\begin{align}
\frac{1}{C_t} & = \beta E_t \left[\frac{\alpha A_{t+1}K_{t+1}^{\alpha-1} +1-\delta }{C_{t+1}}\right]\\
K_{t+1} & = I_t + (1-\delta) K_t\\
Y_t & = A_t K_t^{\alpha}\\
Y_t & = C_t + I_t\\
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1}
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$.

The objective is use `linearsolve` to simulate impulse responses to a TFP shock using the following parameter values for the simulation:

| $\rho$ | $\sigma$ | $\beta$ | $\alpha$ | $\delta $ | $T$ |
|--------|----------|---------|----------|-----------|-----|
| 0.75   | 0.006    | 0.99    | 0.35     |  0.025    | 26  |

## Model Preparation

Before proceding, let's recast the model in the form required for `linearsolve`. Write the model with all variables moved to the lefthand side of the equations and dropping the expecations operator $E_t$ and the exogenous shock $\epsilon_{t+1}$:

\begin{align}
0 & = \beta\left[\frac{\alpha A_{t+1}K_{t+1}^{\alpha-1} +1-\delta }{C_{t+1}}\right] - \frac{1}{C_t}\\
0 & = A_t K_t^{\alpha} - Y_t\\
0 & = I_t + (1-\delta) K_t - K_{t+1}\\
0 & = C_t + I_t - Y_t\\
0 & = \rho \log A_t - \log A_{t+1}
\end{align}

Remember, capital and TFP are called *state variables* because they're $t+1$ values are predetermined. Output, consumption, and investment are called a *costate* or *control* variables. Note that the model as 5 equations in 5 endogenous variables.

## Initialization, Approximation, and Solution

The next several cells initialize the model in `linearsolve` and then approximate and solve it.

```python
# Create a variable called 'parameters' that stores the model parameter values in a Pandas Series

# Print the model's parameters

```

```python
# Create a variable called 'sigma' that stores the value of sigma

```

```python
# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable.

```

```python
# Define a function that evaluates the equilibrium conditions of the model solved for zero. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Parameters. PROVIDED
    p = parameters

    # Current variables. PROVIDED
    cur = variables_current

    # Forward variables. PROVIDED
    fwd = variables_forward

    # Euler equation

    # Production function

    # Capital evolution

    # Market clearing

    # Exogenous tfp

    # Stack equilibrium conditions into a numpy array

```

Next, initialize the model using `ls.model` which takes the following required arguments:

* `equations`
* `n_states`
* `var_names`
* `shock_names`
* `parameters`

```python
# Initialize the model into a variable named 'rbc_model'

```

```python
# Compute the steady state numerically using .compute_ss() method of rbc_model

# Print the computed steady state

```

```python
# Find the log-linear approximation around the non-stochastic steady state and solve using .approximate_and_solve() method of rbc_model

```

### Impulse Responses

Compute a 26 period impulse responses of the model's variables to a 0.01 unit shock to TFP in period 5.

```python
# Compute impulse responses

# Print the first 10 rows of the computed impulse responses to the TFP shock

```

Construct a $2\times2$ grid of plots of simulated TFP, output, consumption, and investment. Be sure to multiply simulated values by 100 so that vertical axis units are in "percent deviation from steady state."

```python
# Create figure. PROVIDED
fig = plt.figure(figsize=(12,8))

# Create upper-left axis. PROVIDED
ax = fig.add_subplot(2,2,1)

# Create upper-right axis. PROVIDED
ax = fig.add_subplot(2,2,2)

# Create lower-left axis. PROVIDED
ax = fig.add_subplot(2,2,3)

# Create lower-right axis. PROVIDED
ax = fig.add_subplot(2,2,4)

```