# 2 Visualising your data

Scientific undertakings are rather pointless if your findings are not communicated to someone else (a colleague, the scientific community, the public, etc.)

The gold standard of scientific communication is the ***figure*** - a presentation of your findings in pictorial form that maximizes reader comprehension of your ideas.

In this notebook, we will consider a range of Python visualisations, from simple line plots, to complex dynamic figures. I will attempt (and fail) to be exhaustive, as I hope this notebook can serve as a sort of reference when you come to do your own figures (*"what was the command for a scatter plot again?"*)

"***Why have you tried to cram so much into this notebook?***"

*Think about learning Python as like learning a new language. Once you have complete mastery of the vocabulary (and the rules for using it) you have total flexibility in how you express yourself... BUT you have to **know** a word exists before you can **use** it. *

*Python is similar - you have total control over all aspects of plotting, but first you need to know the commands that do the different things. Hence, my attempt to be exhaustive here.*

## 2.1D - line plots, histograms, subplots

The workhorse of Python plotting is the [matplotlib](https://matplotlib.org/index.html) module (a particularly good reference is the [gallery](https://matplotlib.org/gallery.html) - choose your plot type by **thumbnail** sample and click to get the **source code**).

We can make Python plots **display** in the notebook by calling a special IPython [magic](http://ipython.readthedocs.io/en/stable/interactive/python-ipython-diff.html?highlight=magics#magics) function.

```python
%matplotlib inline
```

### 2.1.1 The basics

Now, let's **import** the matplotlib module and create an **empty figure and axes**.

```python
from matplotlib import pyplot as plt
f,ax = plt.subplots(1,1)                  # creates a 'matrix' of subplots, in this case, 1 row and 1 column

# CHANGE THIS PLOT to create two subplots, one on top of the other
```

The `subplots` function returns two outputs: a 'figure' object, which we have assigned to the variable `f` and a set of 'axes' objects (in this case, just one), which we have assigned to the variable `ax`.

By default, both axes plot from 0 to 1, although this will be automatically adjusted as we add lines, points, data, etc.

We can **resize** the figure and **save a copy** of it to the file system.

```python
f,ax = plt.subplots(1,1)                           # 1 row of subplots, 1 column of subplots
f.set_size_inches(3,8)                             # 3 inches wide, 8 inches high
plt.savefig('my_empty_figure.png', dpi = 300)      # dpi is the resolution, 300 is good for many applications

# **to do**
# CHANGE THIS PLOT so that the figure is short and wide
```

***Verify that the file `my_empty_figure.png` has been created and corresponds to the awkwardly tall axis above.***

***Modify the code above to produce two SQUARE subplots, side-by-side.*** You will need to modify BOTH the lines `f,ax = plt.subplots(1,1)` and `f.set_size_inches(3,8)`.

### 2.1.2 Line plots

Let's make some simple line plots.

```python
import numpy as np
f,ax = plt.subplots(1,1,figsize=(8,4))            # can set figsize here directly

# get some topograpy data
x,z = np.genfromtxt('../data/topo.csv',delimiter=',',skip_header=1).T

ax.plot(x,z)                                      # the 'plot' command is actually a 'method' called on the 'ax' object

```

Want to change the ***color*** of the line?

```python
f,ax = plt.subplots(1,1,figsize=(8,4))
ax.plot(x,z,'r')                                 # r = red, k = black, b = blue, g = green, c = cyan, m = magenta, y = ??

# **to do**
# CHANGE THIS PLOT to plot a green line
```

Want to change the ***style*** of the line? And add a marker?

```python
f,ax = plt.subplots(1,1,figsize=(8,4))
ax.plot(x,z,'r--s')                             # -- = dashed, : = dotted, -. = dash-dot, - = solid
                                                # s = square, ^ = up triangle, o = circle, . = dot, * = star, p = pentagon
# **to do**
# CHANGE THIS PLOT to plot a blue dotted line with circle markers
```

*"That plot: 3/10. It doesn't have axes labels or a title."*

```python
f,ax = plt.subplots(1,1,figsize=(8,4))
ax.plot(x,z,'r--s')

ax.set_xlabel('x')                                  # add a basic label to the x-axis
ax.set_ylabel('z', size = 12)                       # add a large label to the y-axis
ax.set_title(r'topography along profile $\alpha$', size = 14)      # add a title using LaTeX math mode

# **to do**
# CHANGE THIS PLOT so that:
# - the xlabel reads "along strike distance / km"
# - the ylabel reads "elevation / m"
```

*"6/10, the plot shouldn't exceed the limits of x axis, it's unseemly."*

```python
f,ax = plt.subplots(1,1,figsize=(8,4))
ax.plot(x,z,'r--s')
ax.set_xlabel('x')
ax.set_ylabel('z', size = 12)
ax.set_title(r'topography along profile $\alpha$', size = 14)

# set plot limits in x direction to '0' on the left and 'the last element of the array x' on the right
ax.set_xlim([0, x[-1]])

# **to do**
# CHANGE THIS PLOT so that the ylimits are set to 300 to 600
```

*"8/10 - I'd like a legend and a text annotation for the subplot."*

Of course!

```python
f,ax = plt.subplots(1,1,figsize=(8,4))
ax.plot(x,z,'r--s', label = 'profile $\\alpha$')                # add a label when you call the plot command
ax.set_xlabel('x')
ax.set_ylabel('z', size = 12)
ax.set_title(r'topography along profile $\alpha$', size = 14)
ax.set_xlim([0, x[-1]])

# add a legend
ax.legend(loc = 1)                                     # 1 = top right, 2 = top left, 0 = default, etc ...
# add a text annotation in the bottom left (this is great when you're doing subfigures)
ax.text(0.05, 0.05, "A", ha = 'left', va = 'bottom', transform=ax.transAxes, size = 15)

# **to do**
# CHANGE THIS PLOT so that:
# - the legend label reads "topography"
# - the legend appears in the bottom righthand corner
# - the text annotation is in the top lefthand corner and reads 'I'
```

*"9/10 - Can you put more data on there? And another subplot?"*

```python
# we'll add a second subplot (1 row, 2 columns) and 'unpack' the axes as 'ax1' and 'ax2'
f,(ax1,ax2) = plt.subplots(1,2,figsize=(10,4))
ax1.plot(x,z,'r--s', label = 'profile $\\alpha$')

xv = np.linspace(x[0],x[-1],101)
ax1.plot(xv, 110*np.cos(xv/x[-1]*2*np.pi)+420, 'g--', label='not a great model')       # transform and plot the data on the first axis

ax2.plot(x, z/1.e3, 'k-')       # plot a different transformation on the second axis

# let's set the labels for all axes simultaneously using a FOR loop
for ax in [ax1,ax2]:
    ax.set_xlabel('x')
    ax.set_ylabel('z', size = 12)

# add a legend for the first axis
ax1.legend(loc = 1)
# add a text annotation in the two plots
ax1.text(0.05, 0.05, "A", ha = 'left', va = 'bottom', transform=ax1.transAxes, size = 15)
ax2.text(0.05, 0.05, "B", ha = 'left', va = 'bottom', transform=ax2.transAxes, size = 15)

# **to do**
# CHANGE THIS PLOT so that:
# - the 2nd axis plots the line y propto b(1-exp(-a(x-xm)**2))
# - the 2nd axis has a legend, your choice of labels for the two lines
```

***10/10***

As you can see, plotting commands are additive and, depending on the complexity of your figure, will rapidly compound into a quite sizeable Python script.

(*This code also implemented in script: [module 2 - visualisation/line_plot.py](line_plot.py) - try running it at the command line* `ipython line_plot.py`)

#### Replicate the plot below (exercise)

```python
# **to do**
# write python commands to produce the same figure as above
```

### 2.1.3 Histograms

These are an okay way to present the **'frequency'** of binned values within a dataset.

First, let's generate some **random** 'data'.

```python
N = 100                       # number of data points to create
obs = np.random.randn(N)      # generate normally distributed data
mean, std = [2.5, 0.3]        # mean and standard deviation to transform the data
obs = obs*std + mean          # transform the data
print(obs)
```

Before we plot a histogram, let's look at some useful functions for ***summarising*** the data.

```python
# the basics
print('min =', np.min(obs))
print('max =', np.max(obs))
print('mean =', np.mean(obs))
print('std =', np.std(obs))

# more fancy
print('LQ =', np.percentile(obs,25))
print('median =', np.percentile(obs,50))
print('UQ =', np.percentile(obs,75))
print('count = ', len(obs))
print('sum =', np.sum(obs))
```

Let's plot a **histogram**: there are two steps and some **decision-making**.

1. Place the data into **bins**. This requires an **expert decision** on how many bins to use (rule-of-thumb, for $N$ data, use $\sqrt{N}/2$ bins)
2. Plot the histogram using the `bar` method.

```python
# create the "bin" as a vector of evenly spaced points
Nbins = 8                                                         # alternatively, Nbins = int(np.sqrt(N)/2)
bin_edges = np.linspace(np.min(obs), np.max(obs), Nbins+1)        # create the bins, a vector of bin edges
h,e = np.histogram(obs, bin_edges)                                # sort the data into their bins
print(e)                                                          # bin edges
print(h)                                                          # bin heights

# create the histogram
f,ax = plt.subplots(1,1)
ax.bar(left = e[:-1], height = h, width = e[1]-e[0], color = [0, 0.5, 0], edgecolor = 'k')
# Above, I have specified the bar 'color' as an [r g b] vector - this gives more flexibility in the type of colors that can
# be used. Experiment with changing the three values (make sure your values are between 0 and 1 though!)

# **to do**
# CHANGE THIS PLOT so that:
# - there are 20 bins
# - there are 1000 data points
```

***Is the shape of the plot as you expect? Why is it not perfectly normally distributed?***

***What happens as you increase the number of bins up or down? Describe the trade-offs.***

(*This code also implemented in script: [2_visualisation/histogram.py](histogram.py) - try running it at the command line* `ipython histogram.py`)

#### <homework>

The task below requires you to selectively copy-paste from the code above and modify for your purposes.

```python
# let's read in the production well data from python101.ipynb
time, mf = np.genfromtxt('../data/PW1.dat',delimiter=',',skip_header=True).T

# **to do**
# - create a series of bin_edges using the min and max of the data 'mf'
# - plot a histogram of 'mf'
# - extra: 'detrend' the data first by subtracting the mean of 'mf'
```

#### </homework>

## 2.2D - scatter plots, contour plots, colour maps, cartopy, KDE, images

Much of what we covered in the previous section - axis labels, legends, line styles - applies to the plots in this section. For brevity though, I won't include these commands.

### 2.2.1 Scatter plots

This is a favourite of mine for plotting earthquake locations, because we can modify the **size** and **colour** of the marker to reflect earthquake magnitude and time.

Let's have a look .

***First, ensure you have executed the code below Cell 1.5.2 in the previous notebook - this will download some earthquake data***.

```python
# load the earthquake data downloaded previously
data = np.genfromtxt('../1_data/nd_eqs.txt', delimiter = ',', skip_header=1)

# we will pick out four columns: date (col 1), lat (col 4), lon (col 5), magnitude (col 7)
date_all = data[:,0]                # extract all the rows, first column
lat_all = data[:,3]/180.*np.pi      # same, fourth column (coverted from degrees to radians)
lon_all = data[:,4]/180.*np.pi      # etc
mag_all = data[:,6]

# convert lat-lon to approximate x-y
r = 6371                        # radius of earth (km)
lat_avg = np.mean(lat_all)
x_all = r*lon_all*np.cos(lat_avg)
y_all = r*lat_all
```

Let's just do a really basic x-y scatter plot.

```python
f,ax = plt.subplots(1,1,figsize=(6,6))                # this command should be familiar by now...
ax.scatter(x_all,y_all)
```

Looks like most of the events are clustered in a single region $410<x<470$ and $5800<y<5950$. Let's just focus on those events.

To do that, we're going to need to **filter** our four data arrays - `date`, `x`, `y`, and `mag` - to retain *only events in the range we're interested in*.

```python
# use the 'where' function and conditions to find the data indices we're interested in
    # (while we're at it, get rid of the events smaller than M 0)
    # & = 'and' but when comparing arrays
inds = np.where((435 < x_all) & (x_all < 475) & (5900 < y_all) & (y_all < 5950) & (mag_all > 0.0))

# 'slice' the arrays to keep only the values satisfying the conditions above
date = date_all[inds]
x = x_all[inds]
y = y_all[inds]
mag = mag_all[inds]

# plot the clipped data
f,ax = plt.subplots(1,1,figsize=(6,6))
ax.scatter(x,y)
ax.set_aspect('equal', adjustable='box')    # this command avoids weird stretching of the axes which distorts the data

# **to do**
# CHANGE THIS PLOT so that only earthquakes larger than magnitude 1.0 are shown
```

Let's now modify the **size** of the markers so that they reflect the magnitude of the earthquake.

```python
f,ax = plt.subplots(1,1,figsize=(6,6))

# convert magnitude to a size between 1 and 100 (largest event is a 100 point circle)
    # first, rescale magnitudes to range between 0 and 1
s = (mag - np.min(mag))/(np.max(mag) - np.min(mag))
    # second, rescale to range between 1 and 100 (this will be the size of the marker)
s = s*(50-2)+1
ax.scatter(x,y,s)         # passing a third argument now, marker size
ax.set_aspect('equal', adjustable='box')

# **to do**
# CHANGE THIS PLOT to implement a DIFFERENT scaling of the sizes
```

Finally, let's distinguish the events based on the time they occurred.

However, before we can do that, we'll need to **convert** the 'datetime' - YYYYMMDD - that we read from the text file from a float to a string, and then **reinterpret** this as a 'decimal year'. For this task, we will use the Python [datetime](https://docs.python.org/3/library/datetime.html) module.

```python
# import the datetime module
from datetime import datetime

# convert the dates from float -> integer -> string
t0 = datetime.strptime('19910101', '%Y%m%d')           # reference time 1 Jan 1991
times = []                                             # an empty list for storing each date as it is calculated
for each_date in date:
    # first, convert from float -> integer -> string    (converting straight to a string leaves an awkward decimal point)
    str_date = str(int(each_date))
    # interpret each datestring
    t = datetime.strptime(str_date, '%Y%m%d')
    # take the difference between the datestring and the reference time
    dt = t - t0
    # find the total seconds and convert the date to decimal years
    times.append(dt.total_seconds()/(3600*24*365.25)+1991)
```

Now let's plot events with colours **corresponding** with earthquake time (and add a colour bar for good measure)

```python
f,ax = plt.subplots(1,1,figsize=(6,6))

CS = ax.scatter(x,y,s, c = times)            # use the new 'times' array to indicate to scale a color map
ax.set_aspect('equal', adjustable='box')
plt.colorbar(CS, ax = ax)                   # create a colorbar and attach it to the 'ax' axis
```

Not happy with this particular colour scheme? Let's change it (here is the [full selection](https://matplotlib.org/examples/color/colormaps_reference.html)).

***For super-users, consider using a [colour-blind friendly colourmap](http://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3).***

```python
f,ax = plt.subplots(1,1,figsize=(6,6))
import matplotlib
coolwarm = matplotlib.cm.get_cmap('coolwarm_r')     # import a new colormap - this one is 'coolwarm', reversed it with '_r'

CS = ax.scatter(x,y,s, c= times, cmap=coolwarm)     # pass the colormap in here
ax.set_aspect('equal', adjustable='box')
plt.colorbar(CS, ax = ax)

# **to do**
# CHANGE THIS PLOT to use a sequential colormap (your choice)
```

Phew - you're getting pretty expert at this!

**But we can do more...**

Let's say we have an **image** that outlines the reservoir in which these events are occurring (we do, this image is in the same folder as this notebook, `groningen_reservoir.png`). We can load the image into Python, plot it, and then superimpose the earthquakes over the top.

```python
f,ax = plt.subplots(1,1,figsize=(6,6))

im = plt.imread('groningen_reservoir.png')                  # use the imread function to read an image file
                                                            # 'im' is a MxNx3 array of image data (each pixel has three
                                                            # components, corresponding to an RGB color)

implot = ax.imshow(im, extent=[430, 475, 5902, 5950])     # plot the image and 'stretch' it to the given x and y limits

# then, let's plot the earthquakes over the top
CS = ax.scatter(x,y,s, c= times, cmap=coolwarm)
ax.set_aspect('equal', adjustable='box')
plt.colorbar(CS, ax = ax)
```

Starting to look pretty good right? You get the idea how a complex figure can be **built up** by just starting with some simple commands and then introducing **incremental improvements**.

(*This code also implemented in script: [2_visualisation/scatter_plot.py](scatter_plot.py) - try running it at the command line* `ipython scatter_plot.py`)

In the next section, we'll consider a **variation** on this figure, mainly as a vehicle to introduce a few concepts that are important for 2D plotting.

#### <homework>

Read in a set of porosity-permeability-rock type data and use a `scatter` plot to illustrate the poroperm relationship for each rock (color by rock type).

```python
# read in the poroperm data
phi,k,rt = np.genfromtxt('../data/poroperm.csv',delimiter=',',skip_header=1).T

# **to do**
# uncomment and complete the commands below to scatter plot permeability vs. porosity
# color the markers by rock type 'rt'

#f,ax =???
#ax.set_yscale('log')                       # use a log axes for permeability
#ax.scatter(???

```

#### </homework>

### 2.2.2 Contour plots

Let's say that instead of plotting the **individual** earthquakes themselves, we'd rather some visualisation of earthquake **density**.

One way to go about this is the **histogram route** - divide the area (this time 2D instead of 1D) into **rectangular bins**, **count** the earthquakes in each, and then **assign a colour** corresponding to the number of earthquakes.

I have done this in the cell below

```python
f,ax = plt.subplots(1,1)
f.set_size_inches(10,10)
im = plt.imread('groningen_reservoir.png')
implot = ax.imshow(im, extent=[430, 475, 5902, 5950])

# use histogram2d to add up EQs in discrete bins
xbin_edges = np.linspace(430,475,int((475-430)/1)+1)                    # divide x limits into 1 km bins
ybin_edges = np.linspace(5899, 5950, int((5950-5899)/1)+1)              # same for y
dens, xe, ye = np.histogram2d(x,y, [xbin_edges,ybin_edges])        # create the counting array
dens = np.fliplr(dens)                                             # flip the matrix columns, left to right

# create an empty RGBA figure (3 color components, 1 'alpha' component = transparency)
    # create the 'transparency layer'
alpha_dens = 0.*dens                       # all bins initially transparent
alpha_dens[np.where(dens>0)] = 1     # any bin with data is not transparent
dens_scale = dens/np.max(dens)*255         # scale to range [0,255] required for images
im_dens = np.array([dens_scale, dens_scale, dens_scale, alpha_dens])
implot = ax.imshow(im_dens.T, extent=[430, 475, 5899, 5950])

ax.set_aspect('equal', adjustable='box')
```

However, we can **do better** using something called a [Kernel Density Estimator](http://www.mglerner.com/blog/?p=28). This applies some smoothing to the histogram in an effort to combat arbitrary bin choice.

I will use an implementation of [KDE](http://scikit-learn.org/stable/modules/density.html#kernel-density-estimation) provided in a module called [scikit-learn](http://scikit-learn.org/stable/). This module provides a bunch of machine learning tools.

```python
f,ax = plt.subplots(1,1)
f.set_size_inches(10,10)
YlOrBr = matplotlib.cm.get_cmap('CMRmap_r')

# import the KDE
from sklearn.neighbors import KernelDensity
data = np.vstack([x,y]).T                    # format the data that we will use to train the KDE (the EQ locations)

# Set up a grid for eventual CONTOUR PLOTTING of the KDE
x0,x1,y0,y1 = [430, 475, 5899, 5950]                   # grid limits
xg = np.linspace(x0,x1,101)                            # 101 points in the x dir
yg = np.linspace(y0,y1,101)                            # same in the y
X, Y = np.meshgrid(xg,yg)                              # create a meshgrid, 2D arrays of x and y coords
xy = np.vstack([X.ravel(), Y.ravel()]).T               # combine the grid data together

# create the KDE - I will use a Gaussian kernal (that is, normally distributed location errors) with a bandwidth
# of 1 km (roughly the event location error)
kde = KernelDensity(bandwidth=1, kernel='gaussian', algorithm='ball_tree')   # set KDE properties
kde.fit(data)                                                                # train it with the EQ data
# now we need to evaluate the KDE at our grid locations
Z = np.exp(kde.score_samples(xy))
Z = Z.reshape(X.shape)
# plot contours of the density
levels = np.linspace(0, Z.max(), 11)                # the levels at which contours will be drawn
CS = ax.contourf(X, Y, Z, levels=levels, cmap=YlOrBr, zorder = 1)     # the (filled) contour method
plt.colorbar(CS,ax=ax)

# plot the reservoir outline over the top
im = plt.imread('groningen_reservoir.png')
im[:,:,-1] = 1.*(im[:,:,0]<1.e-6)
implot = ax.imshow(im, extent=[430, 475, 5899, 5950], zorder=2)
```

#### <neat> Cartopy

The final aspect of 2D plotting to show is a handy little package called [cartopy](http://scitools.org.uk/cartopy/docs/latest/index.html#). You'll have to install this package yourself (a good skill to have).

Assuming you have installed Anaconda, go to the command line and type `conda install -c conda-forge cartopy`. Anaconda will automatically download and install cartopy and any packages it relies on.

**Once you have done this, execute the cell below to plot a map of the world.**

```python
# make cartopy available
import cartopy.crs as ccrs

# here, I am positioning the axes myself - this gives you much more flexibility in constructing figures with multiple subplots
ax1 = plt.axes([0.10, 0.15, 0.35, 0.7], projection = ccrs.PlateCarree())    # note the different projections
ax2 = plt.axes([0.50, 0.15, 0.35, 0.7], projection = ccrs.Mollweide())

# adding simple coastlines to each plot
for ax in [ax1,ax2]:
    ax.coastlines()

f = plt.gcf()
f.set_size_inches(20,20)
```

I can zoom the plot in to show everyone's **second favourite** continent after [Zealandia](https://en.wikipedia.org/wiki/Zealandia), adding a little colour for splash.

```python
# code credit: Jeremy Riffault
import cartopy.feature as cfeature
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER

figsize = [7.48, 9.06]             # sets the dimensions of the figure to a full page
text_size = 10.                    # text size for axis ticks and annotations

# here we create the figure and set some its dimensions
    # fiddling with margins, extent is not strictly necessary, but does demonstrate the control you have
fig = plt.figure(figsize=figsize)
margin_x = [0.1, 0.02]
margin_y = [0.05, 0.02]
ax = plt.axes([margin_x[0], margin_y[0], 1-sum(margin_x), 1-sum(margin_y)], projection=ccrs.PlateCarree())
x_extent = [112, 154]              # longitude plot limits
y_center = -25                     # center of lattitude axis (limits computed to be consistent with lon extent)
y_delta = figsize[1]/figsize[0]*(x_extent[1]-x_extent[0])
y_extent = [y_center-y_delta/2, y_center+y_delta/2]
ax.set_extent(x_extent+y_extent, ccrs.PlateCarree())

# add lines
ax.coastlines(resolution='50m')

# add coloured land and ocean
land_50m = cfeature.NaturalEarthFeature('physical', 'land', '50m', edgecolor='k',facecolor=cfeature.COLORS['land'])
ax.add_feature(land_50m, linewidth=.01)
ocean_50m = cfeature.NaturalEarthFeature('physical', 'ocean', '50m', edgecolor='face',facecolor=cfeature.COLORS['water'])
ax.add_feature(ocean_50m);
```

We can do more!

```python
# code credit: Jeremy Riffault
fig = plt.figure(figsize=figsize)
ax = plt.axes([margin_x[0], margin_y[0], 1-sum(margin_x), 1-sum(margin_y)], projection=ccrs.PlateCarree())
ax.set_extent(x_extent+y_extent, ccrs.PlateCarree())
ax.coastlines(resolution='50m')
ax.add_feature(ocean_50m)
ax.add_feature(land_50m, linewidth=.01)

# add state provinces
states_provinces = cfeature.NaturalEarthFeature(category='cultural', name='admin_1_states_provinces_lines', scale='50m', facecolor='none', edgecolor='k')
ax.add_feature(states_provinces, linewidth=.5)

# add locations of two Enhanced Geothermal System projects
hb = {'lon': 140.45, 'lat':-27.495, 'txt':'Habanero'}
pl = {'lon': 139.7, 'lat':-30.2, 'txt':'Paralana'}
for EGS in [hb, pl]:
    # we'll use 'scatter' to add the points
    ax.scatter(EGS['lon'], EGS['lat'], marker = 's', c='r', zorder = 5, s = 3, lw=.1, edgecolors='k')
    # and 'text' to annotate them
    ax.text(EGS['lon']-0.5, EGS['lat'], EGS['txt'], fontsize = text_size, va='center', ha='right')

# here are some things we can do to pretty up the axes (again, nice to have control of, but not strictly necessary)
for axis in ['top','bottom','left','right']:
    ax.spines[axis].set_linewidth(0.5)
gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, alpha=0., linestyle = '--')
gl.xlabels_top = False
gl.ylabels_right = False
gl.xlines = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': text_size}
gl.ylabel_style = {'size': text_size}
ax.tick_params(direction='out', length=6, width=2)
```

For more resources on the use of Cartopy, refer to the [gallery](http://scitools.org.uk/cartopy/docs/latest/gallery.html) and the [Natural Earth](http://www.naturalearthdata.com/) database.

#### </neat>

## 2.3D - interactive view, meshed surfaces

Plotting in 3D comes up less often than the plot types we've looked at so far. Perhaps that is due to difficulties finding a good **view angle** when looking at 3D data.

However, there are times when 3D plots provide a really powerful visual aide. I'll just show some simple examples here.

```python
# import tools for 3D axes
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib import pyplot as plt
import numpy as np

# create a grid
xg = np.linspace(0,1,31)              # evenly spaced grid points
yg = np.linspace(0,1,31)
ymin,ymax = [0.15,0.85]               # create a smaller subgrid in the y-dir for coloring
i1 = np.argmin(abs(yg-ymin))
i2 = np.argmin(abs(yg-ymax))
yg2 = yg[i1:i2+1]                     # subsample y coords
[X,Y] = np.meshgrid(xg,yg)            # create the two mesh grids
[X2,Y2] = np.meshgrid(xg,yg2)

# create a custom surface
    # parameters
xm = np.mean(xg)*0.8
ym = np.mean(yg)*1.2
sx = 0.02*3.
sy = 0.04*3.
    # function defining the surface in terms of x, y and parameters
def r(X,Y):
    return (5-np.exp(-((X-xm)**2/sx+(Y-ym)**2/sy)))*(1-(X/4)**2)*(1+(Y/4)**2)

# create a figure with a 3D projection
fig = plt.figure(figsize=[15,8])
ax = fig.add_subplot(111, projection='3d')

# plot the function as a wireframe over the large grid
ax.plot_wireframe(X, Y, r(X,Y), lw = 0.5, color = 'k')
    # shade part of the wireframe according to the function value
CS = ax.plot_surface(X2, Y2, r(X2,Y2), rstride=1, cstride=1,cmap=cm.Oranges, lw = 0.5)
plt.colorbar(CS, ax=ax)
```

It looks fine, but are we happy with that **view angle**? It's the **default** that Python chose - out of all possible angles, how can we expect Python to choose the **best**?

It would be nicer if we could **modify it in real-time**...

```python
# we'll add this IPython magic command
%matplotlib notebook

fig = plt.figure(figsize=[10,5])
ax = fig.add_subplot(111, projection='3d')

ax.plot_wireframe(X, Y, r(X,Y), lw = 0.5, color = 'k')
CS = ax.plot_surface(X2, Y2, r(X2,Y2), rstride=1, cstride=1,cmap=cm.Oranges, lw = 0.5)
plt.colorbar(CS, ax=ax)

# and also a show() command
plt.show()

# execute this cell and click and drag the subplot below until you find a view you are happy with
# HINT: depending on your Python version, you'll need to execute the cell twice OR restart the Python kernel (hotkey: 0 0)
# and then execute the cell above and then this one (I don't know why this is the case...)
```

(*This code also implemented in script: [2_visualisation/mesh_plot.py](mesh_plot.py) - try running it at the command line* `ipython mesh_plot.py`)

## 2.4D - interactive plots, movies

Because the fourth dimension is **time**, right?

Anyway, this section is about **moving** plots. There are two flavours we'll cover: interactive Jupyter Notebook plots with slider bars, and movies.

### 2.4.1 Interact

Interactive plots are **excellent** in Jupyter notebooks. The `interact` method essentially **wraps around** any plot written as a **function**, adding slider bars, radio buttons, etc., that can be used to **update** plotting parameters in **real-time**.

Let's look at the example of contamination due to a chemical spill into a groundwater aquifer with local flow velocity, $v$.

\begin{equation}
C(x,t) = \frac{M}{2\sqrt{\pi D t}}\exp\left(-\frac{(x-vt)^2}{4Dt}\right),
\end{equation}

where $C$ is concentration, $M$ is the mass of spilled contaminant, and $D$ is the diffusion coefficient.

```python
%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np
# plot contamination down stream after two years
f,ax = plt.subplots(1,1)
x = np.linspace(0,10,101)                       # a vector of distance values to plot over
M = 1000.                                       # mass of chemical spilled
D = 0.1                                         # aquifer diffusivity
v = 1.                                          # groundwater velocity
t = 2.                                          # time at which to plot solution

# compute chemical concentration
C = M/(2*np.sqrt(np.pi*D*t))*np.exp(-(x-v*t)**2/(4*D*t))
ax.plot(x,C, 'r-')
ax.set_xlim([0, 10])
ax.set_ylim([0, 1.e3])
ax.set_xlabel('distance downstream [km]')
ax.set_ylabel('concentration [kg/m^3]')
```

Let's turn it into a **function**. This is less difficult that you might think.

```python
# a function that plots downstream contamination after two years, with flow velocity v as an input
def plot_contamination(v):
    f,ax = plt.subplots(1,1)
    x = np.linspace(0,10,101)
    M = 1000.
    D = 0.1
    t = 2.
    C = M/(2*np.sqrt(np.pi*D*t))*np.exp(-(x-v*t)**2/(4*D*t))
    ax.plot(x,C, 'r-')
    ax.set_xlim([0, 10])
    ax.set_ylim([0, 1.e3])
    ax.set_xlabel('distance downstream [km]')
    ax.set_ylabel('concentration [kg/m^3]')

# call the function with v = 1.5 to produce the same plot as above
plot_contamination(v=1.5)

# change the value of v and rerun the cell - does the plot update in a manner that you expect?
```

Okay, now for the **magic**. We'll use `interact` from the ipywidgets module to turn this into an **interactive graph**.

```python
# first, let's get the interact function
from ipywidgets import interact

# then its as simple as one line
interact(plot_contamination, v = (0.2, 4.8, 0.2))

# play with the slider bar below and see how the graph is redrawn in real time, each time with an updated value v
# the first input to 'interact' is the function to be executed
# the second input says 'create a slider for the parameter v, let it vary between 0.2 and 4.8 in increments of 0.2)
```

#### <homework>

```python
# CHANGE THE INTERACTIVE PLOT ABOVE to make D the slider parameter, varying between 0.02 and 0.98 in increments of 0.06

# **change the function so D is the input**
def plot_contamination(v):
    f,ax = plt.subplots(1,1)
    x = np.linspace(0,10,101)
    M = 1000.
    D = 0.1
    t = 2.
    C = M/(2*np.sqrt(np.pi*D*t))*np.exp(-(x-v*t)**2/(4*D*t))
    ax.plot(x,C, 'r-')
    ax.set_xlim([0, 10])
    ax.set_ylim([0, 1.e3])
    ax.set_xlabel('distance downstream [km]')
    ax.set_ylabel('concentration [kg/m^3]')

# **change the interact call**
interact();
```

```python
# **to do**
# MAKE ANOTHER CHANGE so that BOTH v and D are slider parameters
# **your code here**
```

#### </homework>

We'll come back to the interact function in the **next notebook** when we look at fitting and calibrating models.

### 2.4.2 Movies

When you watch a movie, what does it actually comprise? A **sequence of still images**, flashed in front of you in sequence (usually about 24 each second).

We shall take a similar approach to creating movies in Python. You can already create still images (that's what this whole notebook is about) so now we just need to extend this idea to creating a **series** of still images, each one different from the previous.

Finally, we will need a way to **turn those images into a movie**.

The example I'll show is for stress changes on a fault as a rupture propagates along it (an earthquake). Let's create a 5 second movie, in which the position of the rupture front, `a`, goes from 0.25 to 10 km.

First, create a function that draws and saves a copy of the image.

```python
def plot_frame(i,a):
    ''' Plots a single frame of the fault rupture, at position a.

        i = frame number
        a = position of rupture front

        Stress changes are computed according to linear elastic fracture mechanics.
        s = s0 for x>>a         (far field stress)
        s = s0-ds for x<a       (stress drop inside rupture)
        s ~ K/sqrt(x) for x>a   (decaying stresses with singularity at crack tip)
    '''
    f,ax = plt.subplots(1,1)
    x = np.linspace(0,10.,1001)

    # calculate the stress field
    s0 = 25.                      # pre-earthquake stress
    ds = 3.                       # stress drop
    K = s0*np.sqrt(np.pi*a)       # stress intensity factor
    y = s0*np.ones(len(x))        # background stress vector
    y[np.where(x<a)] = s0-ds      # stress behind the rupture
    # stress increase in front of the rupture
    y[np.where(x>=a)] = s0+K/np.sqrt(2*np.pi*(x[np.where(x>=a)]-a)*1.e3)

    # plot the stress changes
    ax.plot(x,y,'k-')                  # plot stress changes
    ax.plot([a,a], [s0-ds, 30], 'k:')  # stress singularity
    ax.set_xlim([x[0], x[-1]])
    ax.set_ylim([21, 30])
    ax.set_xlabel('position along fault / km')
    ax.set_ylabel('stress / MPa')

    # save the frame in a separate folder and name it using the index i
    plt.savefig('all_frames/frame{:04d}.png'.format(i), dpi=300)
    plt.close(f)                   # it will be important to close all these figures when we're looping

# this part should be familiar!
import os
if not os.path.isdir('all_frames'):
    os.makedirs('all_frames')

plot_frame(0,1.5)
```

**Verify that a file called `frame0000.png` has been created in a directory `all_frames`.**

Now, let's create all the frames we'll need. We'll use a for loop to call `plot_frame` a bunch of times.

```python
FPS = 10           # ten frames per second, let's not be greedy
secs = 5           # total seconds
Nframes = secs*FPS  # total frames

# each frame has a different value a, lets create a vector of a values
avals = np.linspace(0.25, 10.0, Nframes)

# then loop over each avalue and create the frame
for i,a in enumerate(avals):               # enumerate is a handy function that both loops over the values in an array
    plot_frame(i,a)                        # AND gives you a corresponding index
```

Once this cell has finished running, there should be a large number of figures in the directory `all_frames`. Last step is to 'glue' them all together. We'll use an external command line tool called [ffmpeg](https://www.ffmpeg.org/documentation.html). (Note, if you downloaded this course from GitHub, you'll need to download a copy of ffmpeg.exe and drop it into the `module 2 - visualisation` folder.)

This isn't a course on use of ffmpeg. I have provided the Python code to call out to ffmpeg to create a video, but if you want to get fancy you'll need to do your own Googling.

**Execute the cell below to create a short movie. (This won't work if you're on Microsoft Azure)**

```python
# we'll use os.system - a handy command that let's us implement command line calls without returning to the command line
os.system('ffmpeg -framerate {:d} -i all_frames/frame%04d.png earthquake_movie.mp4'.format(FPS))
```

**Verify that the movie you've created corresponds to the plotted frames.**

Last job - tidy your bedroom!

```python
from glob import glob
fls = glob('all_frames/*.png')
for fl in fls: os.remove(fl)
os.rmdir('all_frames')
```

(*This code also implemented in script: [2_visualisation/eq_movie.py](eq_movie.py) - try running it at the command line* `ipython eq_movie.py`)

# What now?

You now have the basic tools to create all sorts of figures. Usually if I write a paper, I'll have a separate Python script to create each figure. Rerunning simulations and getting new output? No problem, just run each figure's Python script and it is automatically updated!

Want some other plotting options? Look into the [seaborn](https://seaborn.pydata.org/), [plotly](https://plot.ly/) and [bqplot](https://bqplot.readthedocs.io/en/stable/) modules.

We've seen now how Python can (1) manage your data and (2) pretty it up for you. The final task is to look at **Modelling in Python**.

**Open the [Models](../3_models/Models.ipynb) notebook in the `3_models` folder.**


# PHY321: Conservative Forces, Examples and Theory

**[Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/)**, Department of Physics and Astronomy and Facility for Rare Ion Beams (FRIB), Michigan State University, USA and Department of Physics, University of Oslo, Norway

Date: **Mar 3, 2021**

Copyright 1999-2021, [Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/). Released under CC Attribution-NonCommercial 4.0 license

## Aims and Overarching Motivation

### Monday

Short repetition from last week about conservative forces. Discussion
of conditions for conservative forces and the Earth-Sun gravitional
force example.  **Reading suggestion**: Taylor sections 4.3, 4.4 and 4.8.

### Wednesday

Potential curves and discussion of the Earth-Sun example, analytical and numerical considerations.
**Reading suggestions**: Taylor section 4.6, 4.8 and 4.9.

### Friday

Earth-Sun,  conservative forces and potential energy.
**Reading suggestion**: Taylor sections 4.8 and 4.9.

If we get time, we start with harmonic oscillations and Hooke's law. **Reading suggestion**: Taylor section 5.1.

## One Figure to Rule All Forces (thx to Julie)

Figure 1:

## Repetition from last week: Work, Energy, Momentum and Conservation laws

Energy conservation is most convenient as a strategy for addressing
problems where time does not appear. For example, a particle goes
from position $x_0$ with speed $v_0$, to position $x_f$; what is its
new speed? However, it can also be applied to problems where time
does appear, such as in solving for the trajectory $x(t)$, or
equivalently $t(x)$.

## Energy Conservation
Energy is conserved in the case where the potential energy, $V(\boldsymbol{r})$, depends only on position, and not on time. The force is determined by $V$,

$$
\begin{equation}
\boldsymbol{F}(\boldsymbol{r})=-\boldsymbol{\nabla} V(\boldsymbol{r}).
\label{_auto1} \tag{1}
\end{equation}
$$

## Conservative forces

We say a force is conservative if it satisfies the following conditions:
1. The force $\boldsymbol{F}$ acting on an object only depends on the position $\boldsymbol{r}$, that is $\boldsymbol{F}=\boldsymbol{F}(\boldsymbol{r})$.

2. For any two points $\boldsymbol{r}_1$ and $\boldsymbol{r}_2$, the work done by the force $\boldsymbol{F}$ on the displacement between these two points is independent of the path taken.

3. Finally, the **curl** of the force is zero $\boldsymbol{\nabla}\times\boldsymbol{F}=0$.

## Forces and Potentials

The energy $E$ of a given system is defined as the sum of kinetic and potential energies,

$$
E=K+V(\boldsymbol{r}).
$$

We define the potential energy at a point $\boldsymbol{r}$ as the negative work done from a starting point $\boldsymbol{r}_0$ to a final point $\boldsymbol{r}$

$$
V(\boldsymbol{r})=-W(\boldsymbol{r}_0\rightarrow\boldsymbol{r})= -\int_{\boldsymbol{r}_0}^{\boldsymbol{r}}d\boldsymbol{r}'\boldsymbol{F}(\boldsymbol{r}').
$$

If the potential depends on the path taken between these two points there is no unique potential.

## Example (relevant for homework 5)

We study a classical electron which moves in the $x$-direction along a surface. The force from the surface is

$$
\boldsymbol{F}(x)=-F_0\sin{(\frac{2\pi x}{b})}\boldsymbol{e}_1.
$$

The constant $b$ represents the distance between atoms at the surface of the material, $F_0$ is a constant and $x$ is the position of the electron.

This is indeed a conservative force since it depends only on position
and its **curl** is zero, that is $-\boldsymbol{\nabla}\times \boldsymbol{F}=0$. This means that energy is conserved and the
integral over the work done by the force is independent of the path
taken.

## Example Continues

Using the work-energy theorem we can find the work $W$ done when
moving an electron from a position $x_0$ to a final position $x$
through the integral

$$
W=\int_{x_0}^x \boldsymbol{F}(x')dx' =  -\int_{x_0}^x F_0\sin{(\frac{2\pi x'}{b})} dx',
$$

which results in

$$
W=\frac{F_0b}{2\pi}\left[\cos{(\frac{2\pi x}{b})}-\cos{(\frac{2\pi x_0}{b})}\right].
$$

Since this is related to the change in kinetic energy we have, with $v_0$ being the initial velocity at a  time $t_0$,

$$
v  = \pm\sqrt{\frac{2}{m}\frac{F_0b}{2\pi}\left[\cos{(\frac{2\pi x}{b})}-\cos{(\frac{2\pi x_0}{b})}\right]+v_0^2}.
$$

## The potential energy from this example

The potential energy, due to energy conservation is

$$
V(x)=V(x_0)+\frac{1}{2}mv_0^2-\frac{1}{2}mv^2,
$$

with $v$ given by the velocity from above.

We can now, in order to find a more explicit expression for the
potential energy at a given value $x$, define a zero level value for
the potential. The potential is defined, using the work-energy
theorem, as

$$
V(x)=V(x_0)+\int_{x_0}^x (-F(x'))dx',
$$

and if you recall the definition of the indefinite integral, we can rewrite this as

$$
V(x)=\int (-F(x'))dx'+C,
$$

where $C$ is an undefined constant. The force is defined as the
gradient of the potential, and in that case the undefined constant
vanishes. The constant does not affect the force we derive from the
potential.

We have then

$$
V(x)=V(x_0)-\int_{x_0}^x \boldsymbol{F}(x')dx',
$$

which results in

$$
V(x)=-\frac{F_0b}{2\pi}\left[\cos{(\frac{2\pi x}{b})}-\cos{(\frac{2\pi x_0}{b})}\right]+V(x_0).
$$

We can now define

$$
-\frac{F_0b}{2\pi}\cos{(\frac{2\pi x_0}{b})}=V(x_0),
$$

which gives

$$
V(x)=-\frac{F_0b}{2\pi}\left[\cos{(\frac{2\pi x}{b})}\right].
$$

## Force and Potential

We have defined work as the energy resulting from a net force acting
on an object (or sseveral objects), that is

$$
W(\boldsymbol{r}\rightarrow \boldsymbol{r}+d\boldsymbol{r})= \boldsymbol{F}(\boldsymbol{r})d\boldsymbol{r}.
$$

If we write out this for each component we have

$$
W(\boldsymbol{r}\rightarrow \boldsymbol{r}+d\boldsymbol{r})=\boldsymbol{F}(\boldsymbol{r})d\boldsymbol{r}=F_xdx+F_ydy+F_zdz.
$$

The work done from an initial position to a final one defines also the difference in potential energies

$$
W(\boldsymbol{r}\rightarrow \boldsymbol{r}+d\boldsymbol{r})=-\left[V(\boldsymbol{r}+d\boldsymbol{r})-V(\boldsymbol{r})\right].
$$

## Getting to $\boldsymbol{F}(\boldsymbol{r})=-\boldsymbol{\nabla} V(\boldsymbol{r})$

We can write out the differences in potential energies as

$$
V(\boldsymbol{r}+d\boldsymbol{r})-V(\boldsymbol{r})=V(x+dx,y+dy,z+dz)-V(x,y,z)=dV,
$$

and using the expression the differential of a multi-variable function $f(x,y,z)$

$$
df=\frac{\partial f}{\partial x}dx+\frac{\partial f}{\partial y}dy+\frac{\partial f}{\partial z}dz,
$$

we can write the expression for the work done as

$$
W(\boldsymbol{r}\rightarrow \boldsymbol{r}+d\boldsymbol{r})=-dV=-\left[\frac{\partial V}{\partial x}dx+\frac{\partial V}{\partial y}dy+\frac{\partial V}{\partial z}dz \right].
$$

## Final expression

Comparing the last equation with

$$
W(\boldsymbol{r}\rightarrow \boldsymbol{r}+d\boldsymbol{r})=F_xdx+F_ydy+F_zdz,
$$

we have

$$
F_xdx+F_ydy+F_zdz=-\left[\frac{\partial V}{\partial x}dx+\frac{\partial V}{\partial y}dy+\frac{\partial V}{\partial z}dz \right],
$$

leading to

$$
F_x=-\frac{\partial V}{\partial x},
$$

and

$$
F_y=-\frac{\partial V}{\partial y},
$$

and

$$
F_z=-\frac{\partial V}{\partial z},
$$

or just

$$
\boldsymbol{F}=-\frac{\partial V}{\partial x}\boldsymbol{e}_1-\frac{\partial V}{\partial y}\boldsymbol{e}_2-\frac{\partial V}{\partial z}\boldsymbol{e}_3=-\boldsymbol{\nabla}V(\boldsymbol{r}).
$$

And this connection is the one we wanted to show.

## Net Energy

The net energy, $E=V+K$ where $K$ is the kinetic energy, is then conserved,

$$
\begin{eqnarray}
\frac{d}{dt}(K+V)&=&\frac{d}{dt}\left(\frac{m}{2}(v_x^2+v_y^2+v_z^2)+V(\boldsymbol{r})\right)\\
\nonumber
&=&m\left(v_x\frac{dv_x}{dt}+v_y\frac{dv_y}{dt}+v_z\frac{dv_z}{dt}\right)
+\partial_xV\frac{dx}{dt}+\partial_yV\frac{dy}{dt}+\partial_zV\frac{dz}{dt}\\
\nonumber
&=&v_xF_x+v_yF_y+v_zF_z-F_xv_x-F_yv_y-F_zv_z=0.
\end{eqnarray}
$$

## In Vector Notation

The same proof can be written more compactly with vector notation,

$$
\begin{eqnarray}
\frac{d}{dt}\left(\frac{m}{2}v^2+V(\boldsymbol{r})\right)
&=&m\boldsymbol{v}\cdot\dot{\boldsymbol{v}}+\boldsymbol{\nabla} V(\boldsymbol{r})\cdot\dot{\boldsymbol{r}}\\
\nonumber
&=&\boldsymbol{v}\cdot\boldsymbol{F}-\boldsymbol{F}\cdot\boldsymbol{v}=0.
\end{eqnarray}
$$

Inverting the expression for kinetic energy,

$$
\begin{equation}
v=\sqrt{2K/m}=\sqrt{2(E-V)/m},
\label{_auto2} \tag{2}
\end{equation}
$$

allows one to solve for the one-dimensional trajectory $x(t)$, by finding $t(x)$,

$$
\begin{equation}
t=\int_{x_0}^x \frac{dx'}{v(x')}=\int_{x_0}^x\frac{dx'}{\sqrt{2(E-V(x'))/m}}.
\label{_auto3} \tag{3}
\end{equation}
$$

Note this would be much more difficult in higher dimensions, because
you would have to determine which points, $x,y,z$, the particles might
reach in the trajectory, whereas in one dimension you can typically
tell by simply seeing whether the kinetic energy is positive at every
point between the old position and the new position.

## The Earth-Sun system

We will now venture into a study of a system which is energy
conserving. The aim is to see if we (since it is not possible to solve
the general equations analytically) we can develop stable numerical
algorithms whose results we can trust!

We solve the equations of motion numerically. We will also compute
quantities like the energy numerically.

We start with a simpler case first, the Earth-Sun system  in two dimensions only.  The gravitational force $F_G$ on the earth from the sun is

$$
\boldsymbol{F}_G=-\frac{GM_{\odot}M_E}{r^3}\boldsymbol{r},
$$

where $G$ is the gravitational constant,

$$
M_E=6\times 10^{24}\mathrm{Kg},
$$

the mass of Earth,

$$
M_{\odot}=2\times 10^{30}\mathrm{Kg},
$$

the mass of the Sun and

$$
r=1.5\times 10^{11}\mathrm{m},
$$

is the distance between Earth and the Sun. The latter defines what we call an astronomical unit **AU**.

## The Earth-Sun system, Newton's Laws

From Newton's second law we have then for the $x$ direction

$$
\frac{d^2x}{dt^2}=-\frac{F_{x}}{M_E},
$$

and

$$
\frac{d^2y}{dt^2}=-\frac{F_{y}}{M_E},
$$

for the $y$ direction.

Here we will use  that  $x=r\cos{(\theta)}$, $y=r\sin{(\theta)}$ and

$$
r = \sqrt{x^2+y^2}.
$$

We can rewrite

$$
F_{x}=-\frac{GM_{\odot}M_E}{r^2}\cos{(\theta)}=-\frac{GM_{\odot}M_E}{r^3}x,
$$

and

$$
F_{y}=-\frac{GM_{\odot}M_E}{r^2}\sin{(\theta)}=-\frac{GM_{\odot}M_E}{r^3}y,
$$

for the $y$ direction.

## The Earth-Sun system, rewriting the Equations

We can rewrite these two equations

$$
F_{x}=-\frac{GM_{\odot}M_E}{r^2}\cos{(\theta)}=-\frac{GM_{\odot}M_E}{r^3}x,
$$

and

$$
F_{y}=-\frac{GM_{\odot}M_E}{r^2}\sin{(\theta)}=-\frac{GM_{\odot}M_E}{r^3}y,
$$

as four first-order coupled differential equations

4
1

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

4
2

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

4
3

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
\frac{dy}{dt}=v_y.
$$

## Building a code for the solar system, final coupled equations

The four coupled differential equations

4
5

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

4
6

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

4
7

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
\frac{dy}{dt}=v_y,
$$

can be turned into dimensionless equations or we can introduce astronomical units with $1$ AU = $1.5\times 10^{11}$.

Using the equations from circular motion (with $r =1\mathrm{AU}$)

$$
\frac{M_E v^2}{r} = F = \frac{GM_{\odot}M_E}{r^2},
$$

we have

$$
GM_{\odot}=v^2r,
$$

and using that the velocity of Earth (assuming circular motion) is
$v = 2\pi r/\mathrm{yr}=2\pi\mathrm{AU}/\mathrm{yr}$, we have

$$
GM_{\odot}= v^2r = 4\pi^2 \frac{(\mathrm{AU})^3}{\mathrm{yr}^2}.
$$

## Building a code for the solar system, discretized equations

The four coupled differential equations can then be discretized using Euler's method as (with step length $h$)

5
2

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

5
3

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

5
4

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
y_{i+1}=y_i+hv_{y,i},
$$

## Code Example with Euler's Method

The code here implements Euler's method for the Earth-Sun system using a more compact way of representing the vectors. Alternatively, you could have spelled out all the variables $v_x$, $v_y$, $x$ and $y$ as one-dimensional arrays.

```
%matplotlib inline

# Common imports
import numpy as np
import pandas as pd
from math import *
import matplotlib.pyplot as plt
import os

# Where to save the figures and data files
PROJECT_ROOT_DIR = "Results"
FIGURE_ID = "Results/FigureFiles"
DATA_ID = "DataFiles/"

if not os.path.exists(PROJECT_ROOT_DIR):
    os.mkdir(PROJECT_ROOT_DIR)

if not os.path.exists(FIGURE_ID):
    os.makedirs(FIGURE_ID)

if not os.path.exists(DATA_ID):
    os.makedirs(DATA_ID)

def image_path(fig_id):
    return os.path.join(FIGURE_ID, fig_id)

def data_path(dat_id):
    return os.path.join(DATA_ID, dat_id)

def save_fig(fig_id):
    plt.savefig(image_path(fig_id) + ".png", format='png')

DeltaT = 0.001
#set up arrays
tfinal = 10 # in years
n = ceil(tfinal/DeltaT)
# set up arrays for t, a, v, and x
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
# Initial conditions as compact 2-dimensional arrays
r0 = np.array([1.0,0.0])
v0 = np.array([0.0,2*pi])
r[0] = r0
v[0] = v0
Fourpi2 = 4*pi*pi
# Start integrating using Euler's method
for i in range(n-1):
    # Set up the acceleration
    # Here you could have defined your own function for this
    rabs = sqrt(sum(r[i]*r[i]))
    a =  -Fourpi2*r[i]/(rabs**3)
    # update velocity, time and position using Euler's forward method
    v[i+1] = v[i] + DeltaT*a
    r[i+1] = r[i] + DeltaT*v[i]
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
fig, ax = plt.subplots()
#ax.set_xlim(0, tfinal)
ax.set_ylabel('y[AU]')
ax.set_xlabel('x[AU]')
ax.plot(r[:,0], r[:,1])
fig.tight_layout()
save_fig("EarthSunEuler")
plt.show()
```

## Problems with Euler's Method

We notice here that Euler's method doesn't give a stable orbit. It
means that we cannot trust Euler's method. In a deeper way, as we will
see in homework 5, Euler's method does not conserve energy. It is an
example of an integrator which is not
[symplectic](https://en.wikipedia.org/wiki/Symplectic_integrator).

Here we present thus two methods, which with simple changes allow us to avoid these pitfalls. The simplest possible extension is the so-called Euler-Cromer method.
The changes we need to make to our code are indeed marginal here.
We need simply to replace

```
    r[i+1] = r[i] + DeltaT*v[i]
```

in the above code with the velocity at the new time $t_{i+1}$

```
    r[i+1] = r[i] + DeltaT*v[i+1]
```

By this simple caveat we get stable orbits.
Below we derive the Euler-Cromer method as well as one of the most utlized algorithms for sovling the above type of problems, the so-called Velocity-Verlet method.

## Deriving the Euler-Cromer Method

Let us repeat Euler's method.
We have a differential equation

$$
\begin{equation}
y'(t_i)=f(t_i,y_i)
\label{_auto4} \tag{4}
\end{equation}
$$

and if we truncate at the first derivative, we have from the Taylor expansion

$$
\begin{equation}
y_{i+1}=y(t_i) + (\Delta t) f(t_i,y_i) + O(\Delta t^2), \label{eq:euler} \tag{5}
\end{equation}
$$

which when complemented with $t_{i+1}=t_i+\Delta t$ forms
the algorithm for the well-known Euler method.
Note that at every step we make an approximation error
of the order of $O(\Delta t^2)$, however the total error is the sum over all
steps $N=(b-a)/(\Delta t)$ for $t\in [a,b]$, yielding thus a global error which goes like
$NO(\Delta t^2)\approx O(\Delta t)$.

To make Euler's method more precise we can obviously
decrease $\Delta t$ (increase $N$), but this can lead to loss of numerical precision.
Euler's method is not recommended for precision calculation,
although it is handy to use in order to get a first
view on how a solution may look like.

Euler's method is asymmetric in time, since it uses information about the derivative at the beginning
of the time interval. This means that we evaluate the position at $y_1$ using the velocity
at $v_0$. A simple variation is to determine $x_{n+1}$ using the velocity at
$v_{n+1}$, that is (in a slightly more generalized form)

$$
\begin{equation}
y_{n+1}=y_{n}+ v_{n+1}+O(\Delta t^2)
\label{_auto5} \tag{6}
\end{equation}
$$

and

$$
\begin{equation}
v_{n+1}=v_{n}+(\Delta t) a_{n}+O(\Delta t^2).
\label{_auto6} \tag{7}
\end{equation}
$$

The acceleration $a_n$ is a function of $a_n(y_n, v_n, t_n)$ and needs to be evaluated
as well. This is the Euler-Cromer method.

**Exercise**: go back to the above code with Euler's method and add the Euler-Cromer method.

## Deriving the Velocity-Verlet Method

Let us stay with $x$ (position) and $v$ (velocity) as the quantities we are interested in.

We have the Taylor expansion for the position given by

$$
x_{i+1} = x_i+(\Delta t)v_i+\frac{(\Delta t)^2}{2}a_i+O((\Delta t)^3).
$$

The corresponding expansion for the velocity is

$$
v_{i+1} = v_i+(\Delta t)a_i+\frac{(\Delta t)^2}{2}v^{(2)}_i+O((\Delta t)^3).
$$

Via Newton's second law we have normally an analytical expression for the derivative of the velocity, namely

$$
a_i= \frac{d^2 x}{dt^2}\vert_{i}=\frac{d v}{dt}\vert_{i}= \frac{F(x_i,v_i,t_i)}{m}.
$$

If we add to this the corresponding expansion for the derivative of the velocity

$$
v^{(1)}_{i+1} = a_{i+1}= a_i+(\Delta t)v^{(2)}_i+O((\Delta t)^2)=a_i+(\Delta t)v^{(2)}_i+O((\Delta t)^2),
$$

and retain only terms up to the second derivative of the velocity since our error goes as $O(h^3)$, we have

$$
(\Delta t)v^{(2)}_i\approx a_{i+1}-a_i.
$$

We can then rewrite the Taylor expansion for the velocity as

$$
v_{i+1} = v_i+\frac{(\Delta t)}{2}\left( a_{i+1}+a_{i}\right)+O((\Delta t)^3).
$$

## The velocity Verlet method

Our final equations for the position and the velocity become then

$$
x_{i+1} = x_i+(\Delta t)v_i+\frac{(\Delta t)^2}{2}a_{i}+O((\Delta t)^3),
$$

and

$$
v_{i+1} = v_i+\frac{(\Delta t)}{2}\left(a_{i+1}+a_{i}\right)+O((\Delta t)^3).
$$

Note well that the term $a_{i+1}$ depends on the position at $x_{i+1}$. This means that you need to calculate
the position at the updated time $t_{i+1}$ before the computing the next velocity.  Note also that the derivative of the velocity at the time
$t_i$ used in the updating of the position can be reused in the calculation of the velocity update as well.

## Adding the Velocity-Verlet Method

We can now easily add the Verlet method to our original code as

```
DeltaT = 0.01
#set up arrays
tfinal = 10 # in years
n = ceil(tfinal/DeltaT)
# set up arrays for t, a, v, and x
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
# Initial conditions as compact 2-dimensional arrays
r0 = np.array([1.0,0.0])
v0 = np.array([0.0,2*pi])
r[0] = r0
v[0] = v0
Fourpi2 = 4*pi*pi
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up forces, air resistance FD, note now that we need the norm of the vecto
    # Here you could have defined your own function for this
    rabs = sqrt(sum(r[i]*r[i]))
    a =  -Fourpi2*r[i]/(rabs**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    rabs = sqrt(sum(r[i+1]*r[i+1]))
    anew = -4*(pi**2)*r[i+1]/(rabs**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
fig, ax = plt.subplots()
ax.set_ylabel('y[AU]')
ax.set_xlabel('x[AU]')
ax.plot(r[:,0], r[:,1])
fig.tight_layout()
save_fig("EarthSunVV")
plt.show()
```

You can easily generalize the calculation of the forces by defining a function
which takes in as input the various variables. We leave this as a challenge to you.

## Additional Material: Link between Line Integrals and Conservative forces

The concept of line integrals plays an important role in our discussion of energy conservation,
our definition of potentials and conservative forces.

Let us remind ourselves of some the basic elements (most of you may
have seen this in a calculus course under the general topic of vector
fields).

We define a path integration $C$, that is we integrate
from a point $\boldsymbol{r}_1$ to a point $\boldsymbol{r}_2$.
Let us assume that the path $C$ is represented by an arc length $s$. In three dimension we have the following representation of $C$

$$
\boldsymbol{r}(s)=x(s)\boldsymbol{e}_1+y(s)\boldsymbol{e}_2+z(s)\boldsymbol{e}_3,
$$

then our integral of a function $f(x,y,z)$ along the path $C$ is defined as

$$
\int_Cf(x,y,z)ds=\int_a^bf\left(x(s),y(s),z(s)\right)ds,
$$

where the initial and final points are $a$ and $b$, respectively.
## Exactness and Independence of Path

With the definition of a line integral, we can in tunrn set up the
theorem of independence of integration path.

Let us define
$f(x,y,z)$, $g(x,y,z)$ and $h(x,y,z)$ to be functions which are
defined and continuous in a domain $D$ in space. Then a line integral
like the above is said to be independent of path in $D$, if for every
pair of endpoints $a$ and $b$ in $D$ the value of the integral is the
same for all paths $C$ in $D$ starting from a point $a$ and ending in
a point $b$. The integral depends thus only on the integration limits
and not on the path.

## Differential Forms

An expression of the form

$$
fdx+gdy+hdz,
$$

where $f$, $g$ and $h$ are functions defined in $D$, is a called a first-order differential form
in three variables.
The form is said to be exact if it is the differential

$$
du= \frac{\partial u}{\partial x}dx+\frac{\partial u}{\partial y}dy+\frac{\partial u}{\partial z}dz,
$$

of a differentiable function $u(x,y,z)$ everywhere in $D$, that is

$$
du=fdx+gdy+hdz.
$$

It is said to be exact if and only if we can then set

$$
f=\frac{\partial u}{\partial x},
$$

and

$$
g=\frac{\partial u}{\partial y},
$$

and

$$
h=\frac{\partial u}{\partial z},
$$

everywhere in the domain $D$.

## In Vector Language

In vector language the above means that the differential form

$$
fdx+gdy+hdz,
$$

is exact in $D$ if and only if the vector function (it could be a force, or velocity, acceleration or other vectors we encounter in this course)

$$
\boldsymbol{F}=f\boldsymbol{e}_1+g\boldsymbol{e}_2+h\boldsymbol{e}_3,
$$

is the gradient of a function $u(x,y,z)$

$$
\boldsymbol{v}=\boldsymbol{\nabla}u=\frac{\partial u}{\partial x}\boldsymbol{e}_1+\frac{\partial u}{\partial y}\boldsymbol{e}_2+\frac{\partial u}{\partial z}\boldsymbol{e}_3.
$$

## Path Independence Theorem

If this is the case, we can state the path independence theorem which
states that with functions $f(x,y,z)$, $g(x,y,z)$ and $h(x,y,z)$ that fulfill the above
exactness conditions,  the line integral

$$
\int_C\left(fdx+gdy+hdz\right),
$$

is independent of path in $D$ if and only if the differential form under the integral sign is exact in $D$.

This is the path independence theorem.

We will not give a proof of the theorem. You can find this in any vector analysis chapter in a mathematics textbook.

We note however that the path integral from a point $p$ to a final point $q$ is given by

$$
\int_p^q\left(fdx+gdy+hdz\right)=\int_p^q\left(\frac{\partial u}{\partial x}dx+\frac{\partial u}{\partial y}dy+\frac{\partial u}{\partial z}dz\right)=\int_p^qdu.
$$

Assume now that we have a dependence on a variable $s$ for $x$, $y$ and $z$. We have then

$$
\int_p^qdu=\int_{s_1}^{s_2}\frac{du}{ds}ds = u(x(s),y(s),z(s))\vert_{s=s_1}^{s=s_2}=u(q)-u(p).
$$

This last equation

$$
\int_p^q\left(fdx+gdy+hdz\right)=u(q)-u(p),
$$

is the analogue of the usual formula

$$
\int_a^bf(x)dx=F(x)\vert_a^b=F(b)-F(a),
$$

with $F'(x)=f(x)$.

## Work-Energy Theorem again

We remember that a the work done by a force
$\boldsymbol{F}=f\boldsymbol{e}_1+g\boldsymbol{e}_2+h\boldsymbol{e}_3$ on a displacemnt $d\boldsymbol{r}$
is

$$
W=\int_C\boldsymbol{F}d\boldsymbol{r}=\int_C(fdx+gdy+hdz).
$$

From the path independence theorem, we know that this has to result in
the difference between the two endpoints only. This is exact if and
only if the force is the force $\boldsymbol{F}$ is the gradient of a scalar
function $u$. We call this scalar function, which depends only the
positions $x,y,z$ for the potential energy $V(x,y,z)=V(\boldsymbol{r})$.

We have thus

$$
\boldsymbol{F}(\boldsymbol{r})\propto \boldsymbol{\nabla}V(\boldsymbol{r}),
$$

and we define  this as

$$
\boldsymbol{F}(\boldsymbol{r})= -\boldsymbol{\nabla}V(\boldsymbol{r}).
$$

Such a force is called **a conservative force**. The above expression can be used to demonstrate
energy conservation.

## Additional Theorem

Finally we can define the criterion for exactness and independence of
path. This theorem states that if $f(x,y,z)$, $g(x,y,z)$ and
$h(x,y,z)$ are continuous functions with continuous first partial derivatives in the domain $D$,
then the line integral

$$
\int_C\left(fdx+gdy+hdz\right),
$$

is independent of path in $D$ when

$$
\frac{\partial h}{\partial y}=\frac{\partial g}{\partial z},
$$

and

$$
\frac{\partial f}{\partial z}=\frac{\partial h}{\partial x},
$$

and

$$
\frac{\partial g}{\partial x}=\frac{\partial f}{\partial y}.
$$

This leads to the **curl** of $\boldsymbol{F}$ being zero

$$
\boldsymbol{\nabla}\times\boldsymbol{F}=\boldsymbol{\nabla}\times\left(-\boldsymbol{\nabla}V(\boldsymbol{r})\right)=0!
$$

## Summarizing

A conservative force $\boldsymbol{F}$ is a defined as the partial derivative of a scalar potential which depends only on the position,

$$
\boldsymbol{F}(\boldsymbol{r})= -\boldsymbol{\nabla}V(\boldsymbol{r}).
$$

This leads to conservation of energy and a path independent line integral as long as the curl of the force is zero, that is

$$
\boldsymbol{\nabla}\times\boldsymbol{F}=\boldsymbol{\nabla}\times\left(-\boldsymbol{\nabla}V(\boldsymbol{r})\right)=0.
$$


# **5.1 Interface behavior governed by damage**

[](https://moodle.rwth-aachen.de/mod/page/view.php?id=551838) part 1

        Starting point

Having described the irreversible behavior of the material using the assumption of frictional stress transfer between the material components which induces permanent **plastic** deformation, we move on to the second possibility, where we idealize the irreversible changes in the material structure as a spring break. This idealization is the basis of **damage** models.

        Where are we heading

**Damage functions:** We are going to define several damage functions which are used in damage models available in finite element codes. Their definition is based on an assumed profile of breakage propagation. We can use different types of damage functions that express the state of material as an amount of broken bindings/micro-springs within a unit volume of the material structure. Even though we explain them using a one-dimensional idealization of the bond behavior, it is important to note, that these functions are directly applicable in two- and three-dimensional finite element models. To document this, we demonstrate that the derived damage functions can be used to model a two-dimensional interface with slip and shear stress defined as a vector.

**Damage evolution and energy dissipation:** From a longer perspective, we are preparing the grounds for the discussion of energy dissipation associated with irreversible changes within the material structures. Some of the damage functions presented below already employ the concept of dissipated energy within a damaged unit volume of material. They are used as precursors here to motivate the consideration of energy dissipation in the following tours.

Summarizing, in the present notebook, we are going to
* describe and visualize examples of damage functions in 1D
* show how to use an isotropic damage model to represent
  the material behavior of a 2D interface

# **Motivation and general aspects of damage**

## Fiber bundle behavior described using a damage function

To provide a motivation for the damage based description of the material behavior let us consider micrographs of a multi-filament yarn cross section embedded in the concrete matrix. Depending on the type of the yarn, the cross section can consist of 800-50000 carbon/glass/basalt filaments  with a diameter ranging between 8-30 $\mu m$. The micrograph below presents a non-penetrated 800 [tex] yarn. The unit [tex] represents the weight in grams per one km of the yarn
\begin{align}
[\mathrm{tex}] = [\mathrm{g}/\mathrm{km}]
\end{align}
The yarns can be penetrated by a homogenizing material, most commonly epoxy resin based material.
The picture below shows two cross section of a non-penetrated glass yarn. The shapes of the cross section are circular (on the left) and flat (on the right).

To describe the load transfer and the failure process of a yarn, the tree-dimensional representation of the structure has been reproduced using slicing technique in collaboration with IMB, ibac and GIA RWTH Aachen. The pictures show significant geometric irregularity both along the length of the yarn and in the direction of the cross-section.

The primary purpose of the reinforcement is to transfer the tensile load. Let us therefore construct an idealization which can be used to describe its tensile response as a result breakage process of the individual elastic filaments. The stress-strain curve measured in a tensile test on a yarn delivers a softening type of response. We use the example of the yarn to describe the relation between tensile stress versus strain as a filament breakage process. However, the same type of idealization applies also for the bond-slip law $\tau(s)$ that can be regarded as an irregular structure of many springs that fail in a sequence.

Note that the fiber bundle as a system of discrete springs can be applied both
in tension and in shear. The damage function explained below are applicable in
both situations. Moreover, they can be used also in two- and three dimensional
configurations as well.

## Mathematical framework for the definition of damage models

The tensile response of a yarn can be expressed as
\begin{align}
\sigma &= \psi E_\mathrm{b} \varepsilon = (1 - \omega) E_\mathrm{b} \varepsilon
\end{align}

This expression relates the level of stress directly to the fraction of the broken filaments represented by the dimensionless damage variable
\begin{align}
\omega \in (0,1)
\end{align}
The fraction of unbroken filaments is introduced as a degree of integrity
\begin{align}
\psi = 1 - \omega.
\end{align}
The profile of the integrity and damage functions corresponding to the above stress-strain curve has the following shape.

In the elastic regime with no damage, the value of $\omega$ remains zero $\omega = 0$. After the breakage of the first filament, it starts to grow up to a complete damage with $\omega = 1$. Having used the fiber bundles as a suitable picture to  motivate the damage modeling for tensile response, we will return to the bond-slip law $\tau(s)$ in the sequel with the goal to  mathematically describe the bond and pullout behavior within the damage framework.

Instead of explicitly prescribing the nonlinear bond slip law as a nonlinear curve let us prescribe
a nonlinear curve governing the evolution of stiffness.

\begin{equation}
\label{EQ:bond_damage_model}
\tau = \left(1 - \omega(\kappa)\right)E_\mathrm{b} \; s
\end{equation}

where $\omega(\kappa)$ is the nonlinear damage function and $\kappa$ is the state variable
that is equivalent to maximum slip $s$ (or strain $\varepsilon$) attained during
the loading history.

        Apparent and effective stress

By rearranging the terms in the [above damage equation](#damage_general)  we can introduce the notion of effective stress as

\begin{equation}
\tilde{\tau} = \frac{\tau}{1-\omega} = E_b \; s
\end{equation}

Note that effective stress $\tilde{\tau} \geq \tau$. While the apparent stress is related to the original material area, the effective stress is related to the undamaged material area. In other words, it can be interpreted as the stress acting on the remaining fraction of springs. The effective stress is related to the instantaneous, or still effective, cross section and not to the initial cross sectional area of the unit material zone.

## How to identify the shape of the damage function?

There are several ways how to justify a particular shape of the damage function.
Let us distinguish three ways in which the damage functions are introduced.

 1. **Definition based on an experiment:** Given a measured $\sigma(\epsilon)$ curve, the damage level $\omega$ can be resolved directly by rearranging the [stress-strain equation](#damage_general), i.e.
 \begin{align}
 \omega = \left(1 - \dfrac{1}{E_\mathrm{b}} \right) \dfrac{\tau}{s}
 \end{align}
 or the integrity function
 \begin{align}
 \psi = \dfrac{\tau}{E_\mathrm{b} s}
 \end{align}
 2. **Definition based on probabilistic density function of fiber strength**: Let us remark, that a the properties of a damage function as a non-decreasing function within a range (0,1) are equal to a cumulative probability density function. This fact provides the possibility to introduce the damage function as an integrated probability density function of a  filament strength within the fiber bundle model as indicated in the [stress strain response](#sig_eps_damage).
 3. **Definition based on the amount of dissipated energy**: To narrow down the possible shapes of the damage profile, theoretical arguments based on energetic interpretation of the damage process can be used to scale the damage function to a obtain the desired amount of energy dissipation. This aspect will be addressed more in detail in Tour 6 introducing energy dissipation as an effective means of describing the localization and fracture of material exhibiting damage.

```python
%matplotlib widget
import sympy as sp
import numpy as np
import matplotlib.pylab as plt
sp.init_printing()
```

# **Classification of damage evolution functions**

[](https://moodle.rwth-aachen.de/mod/page/view.php?id=551838) part 2

        Let us sort the damage

## Linear damage function

With the image of a material structure as a fiber-bundle with linear elastic fiber response and brittle failure we can consider a case with linear distribution of fiber breaking strain. This corresponds to the case with equidistant breaking strain or slip of all springs constituting the material structure

$$\Delta s^\mathrm{break} = s_{i+1}^\mathrm{break} - s_i^\mathrm{break} = \mathrm{constant}$$

In such a case, the damage function would be a piecewise linear function with a value zero until the first fiber break $s_0$, linear profile until the last spring break $s_\mathrm{u}$ and plateau at the level 1 afterwards, i.e.

\begin{align}
 \omega &=
 \left\{
 \begin{array}{cl}
 0, & \kappa \leq \kappa_0 \\
 \frac{\kappa - \kappa_0}{\kappa_\mathrm{u}-\kappa_0} & \kappa_0 < \kappa \leq \kappa_\mathrm{u} \\
 1, & \mathrm{otherwise}
 \end{array}
 \right.
\end{align}

To see this type of damage function in action, let us import the prepared damage material model component `MATS1D5BondSlipD`. This model provides several damage functions in a polymorphic trait `omega_fn`. Let us select the `linear` damage function and construct an  instance of bond-slip model `bs_linear_damage`

```python
from bmcs_cross_section.pullout import MATS1D5BondSlipD
bs_linear_damage = MATS1D5BondSlipD(omega_fn='linear', s_max=0.05)
bs_linear_damage.omega_fn_
```

$\displaystyle \begin{cases} 0 & \text{for}\: \kappa < \kappa_{0} \\\frac{\kappa - \kappa_{0}}{- \kappa_{0} + \kappa_{u}} & \text{for}\: \kappa < \kappa_{u} \\1 & \text{otherwise} \end{cases}$

The last line in the previous cell renders the symbolic definition of the linear damage function to demonstrate that it is identical with the [above specification](#eq:linear_damage). The attribute of the bond-slip model `omega_fn_` can be instantiated as any other model component to verify the shape of the damage function.

```python
bs_linear_damage.omega_fn_.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

With the damage function at hand, let us plot the corresponding stress slip function. Let us assume that the loading is monotonically increasing so that we do not need to consider unloading
$$ \tau = (1 - \omega(\kappa)) E_\mathrm{b}s$$
Apparently, if $\omega(\kappa)$ is linear, then $\tau$ must be quadratic. Let us verify this by rendering the material model `bs_damage_linear`

```python
bs_linear_damage.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

        Which kind of material structure would exhibit such response?

Using the fiber-bundle analogy, we can design a test which consists of a very large number of fibers
$i \in 0 \ldots N$.

All the fibers have the same breaking strain $\bar{\varepsilon}$.
The length of the fibers follows a linear function
$$
  \ell_i = \ell_0 + i \frac{\ell_\mathrm{u} - \ell_0}{N}, \;\;\; i = 0 \ldots N
$$

Upon loading, the fibers will break at the control displacement given as
$$
 u^\mathrm{break}_i = \bar{\varepsilon} \ell_i
$$
which exhibit the property of equidistant breakage [postulated above](#equidistant_breaks)
$$\Delta u^\mathrm{break} = u_{i+1}^\mathrm{break}
- u_i^\mathrm{break} =
\bar{\varepsilon} \frac{\ell_\mathrm{u} - \ell_0}{N} = \mathrm{constant}$$

        Can the damage function be applied also to a breaking strains within a local material structure?

The fiber length distribution represent an idealization within a unit length
of the material. Thus, given a nominal length $\ell$ of the fiber bundle, the above reasoning applies
also for strain which is related to the breaking displacement as
$$
\varepsilon^\mathrm{break}_i = \frac{u^{\mathrm{break}}_i}{\ell}
$$
The linear damage function has been certainly used to provide a mathematical interpretation using the fiber-bundle analogy. It is certainly not applicable for real problems in the simulation of damage evolution as it occurs in a continuum. Let us therefore extend the idea to more general situations.

## Damage function as a cumulative probability density of breaking strain

The previous example has demonstrated the meaning of the damage function as a distribution of the breaking strains which has been artificially assumed linear. In fact, the linear damage function
[specified above](#eq:linear_damage) can regarded as a special type of cumulative density function.
This brings us to the possibility to capture the breaking strain distribution using an existing
cumulative probability density function. In particular, we choose the [Weibull distribution](https://en.wikipedia.org/wiki/Weibull_distribution). Before, explaining the reason for this choice
let us construct another model component `bs_cdf_damage` with `omega_fn` set to `weibull-CDF`

```python
bs_linear_damage = MATS1D5BondSlipD(omega_fn='weibull-CDF', s_max=0.05)
bs_linear_damage.omega_fn_
```

$\displaystyle \begin{cases} 0 & \text{for}\: \kappa < 0 \\1 - e^{- \left(\frac{\kappa}{\lambda}\right)^{m}} & \text{otherwise} \end{cases}$

The variable $\kappa$ represents the state variable, i.e. slip or strain.
The parameters $\lambda$ and $m$ denote the so called scale and shape parameters, respectively.
Let us demonstrate their role by rendering the model component

```python
bs_linear_damage.omega_fn_.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

By interacting with the model component we can observe that the shape parameter $m$ controls the spread of the breaks along the control slip or strain variable and scale parameter $\lambda$ shifts the breaking range horizontally. The corresponding bond slip law then has the form

```python
bs_linear_damage.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

The qualitative shape of the displayed stress-strain curve can be conveniently used to reproduce the experimentally obtained response of a yarn observed in the tensile test.

        Fiber-bundle and test length - Weibull distribution can capture it

The figure shows an example of the stress-strain curves obtained experimentally in tensile tests on carbon yarns with varied test lengths reported by [Sugimoto et al. (2021)](https://link.springer.com/content/pdf/10.1557/s43578-020-00043-y.pdf). The authors use the Weibull strength distribution to interpret the and to scale the obtained fiber strength characteristics between individual yarn lengths. Since the Weibull probability distribution is based on the concept of a weakest link in a chain, it allows for a scaling of strength given a changed length. Indeed, as the figure above shows, the strength of bundles with the lengths 50, 100 and 150 exhibits a negative trend. This corresponds well with the scaling inherently present in the Weibull probability distribution. Simply speaking, the probability of failure increases with the increasing number of links within the chain. The fact that the length of 10 mm has lower strength is related to the technical difficulties involved in the testing of short yarns discussed e.g. by [Rypl et. al (2015)](../papers/yarn_tensile_test_example.pdf)

        Generalization - exponential damage function provides flexibility

[](https://moodle.rwth-aachen.de/mod/page/view.php?id=551838) part 3

The probabilistic interpretation of damage evolution has shown that an exponential functions provide a flexible tool to adjust the shape of the non-linear softening branch in the stress-strain response.
This flexibility has been exploited in many finite-element codes by providing several types of damage functions defined in terms of exponential function.

This can be documented by the fact that the function
\begin{align}
\omega(\kappa) = 1 - \exp(-\kappa)
\end{align}
approaches the value 1 for $\kappa \rightarrow \infty$. This function is usually combined with an elastic range $\kappa < \kappa_0$ where the damage is implicitly defined as zero. To demonstrate it, let us define it symbolic form using `sympy`

```python
kappa = sp.symbols('kappa')
omega_kappa_ = 1 - sp.exp(-kappa)
omega_kappa_
```

        To see the function 'life', let us use the following code

```python
get_omega_kappa = sp.lambdify(kappa, omega_kappa_) # executable expression
fig, ax = plt.subplots(1,1,figsize=(7,4), tight_layout=True) # plottable area
fig.canvas.header_visible=False # hide the header
kappa_arr = np.linspace(0.0,5,100) # generate the slip array from in the range (0, 0.5)
ax.plot(kappa_arr, get_omega_kappa(kappa_arr), linestyle='solid', color='black', label=r'$\omega$')
ax.fill_between(kappa_arr, get_omega_kappa(kappa_arr), color='gray', alpha=0.2)
ax.set_ylabel(r'$\omega$'); ax.set_xlabel(r'$\kappa$');
```

    Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …

Thus, the probability density function is only one possible form of the damage function. Let us exemplify three further types of damage function that can be encountered in the finite element codes.

## Damage function derived from strength and shape of softening branch

The first example shows a function which can control the slope damage evolution at the onset of damage $\kappa = \kappa_0$ by a parameter controlling its slope of the softening branch in the stress-strain diagram. It has the form
\begin{align}
\omega(\kappa) = 1 - \left[\frac{\kappa_0}{\kappa} \exp \left(- \frac{\kappa - \kappa_0}{\kappa_\mathrm{f} - \kappa_0}\right)\right]
\end{align}

```python
bs_exp_slope = MATS1D5BondSlipD(omega_fn='exp-slope', s_max=0.05)
bs_exp_slope.omega_fn_.trait_set(kappa_0=0.01, kappa_f=0.04)
bs_exp_slope.omega_fn_
```

$\displaystyle \begin{cases} 0 & \text{for}\: \kappa \leq \kappa_{0} \\1 - \frac{\kappa_{0} e^{\frac{- \kappa + \kappa_{0}}{- \kappa_{0} + kappa_\mathrm{f}}}}{\kappa} & \text{otherwise} \end{cases}$

```python
bs_exp_slope.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

        When to use this damage format?

In contrast to the cumulative probability function, the above shown function allows for an explicit specification of strength, i.e.
$$
f_\mathrm{t} = E_\mathrm{b} \kappa_0
$$
and the brittleness controlled by the $\kappa_f$ which represents the point on the horizontal axes which is intersected by a tangent of the softening branch constructed at the onset of damage, i.e. $s = \kappa_0$. In the present case, the material strength is equivalent to the onset of inelasticity. Such coupling, however, can be too restrictive. Therefore, an example of a more flexible representation of damage is shown in the next section.

## Damage function used in Abaqus

The damage function provided in `Abaqus` uses three parameters to control the softening branch.
\begin{align}
\omega(\tilde{s}) = \frac{\kappa_0}{\kappa}\left[ 1 - \frac{1 - \exp \displaystyle\left(- \alpha
\frac{\kappa - \kappa_0}{\kappa_u - \kappa_0}\right)}{1 - \exp(-\alpha)}  \right]
\end{align}

This model is available in the catalog of damage function `omega_fn` so that we instantiate it and verify that it is equivalent to the above formula

```python
bs_exp_slope = MATS1D5BondSlipD(omega_fn='abaqus', s_max=0.05)
bs_exp_slope.omega_fn_.trait_set(kappa_0=0.01, kappa_u=0.04)
bs_exp_slope.omega_fn_
```

$\displaystyle \begin{cases} 0 & \text{for}\: \kappa \leq \kappa_{0} \\1 - \begin{cases} 1 & \text{for}\: \kappa < \kappa_{0} \\\frac{\kappa_{0} \left(1 - \frac{1 - e^{- \frac{\alpha \left(\kappa - \kappa_{0}\right)}{- \kappa_{0} + \kappa_{u}}}}{1 - e^{- \alpha}}\right)}{\kappa} & \text{for}\: \kappa < \kappa_{u} \\0 & \text{otherwise} \end{cases} & \text{otherwise} \end{cases}$

```python
bs_exp_slope.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

Apparently, the parameter $\kappa_\mathrm{u}$ controls the point of ultimate damage at which the stress=strain curve hits the zero level. A small value of the parameter $\alpha$ renders a linear softening branch. By increasing the value of $\alpha$ a nonlinear softening with steep decay at the onset of damage is obtained.

## Damage function accounting for fracture energy
The last example of the damage function controls the softening branch by prescribing the area below the curve denoted as $G_\mathrm{f}$. This means, a smaller value of $G_\mathrm{f}$ makes the material more brittle, while a larger value increases the ductility
\begin{align}
\omega(\kappa) = 1 - \displaystyle{\frac{\kappa_0}{\kappa}}
\exp\left(\frac{2 E_\mathrm{b} \kappa_0 ( \kappa - \kappa_0) }
{E_\mathrm{b} \kappa^2_0 - 2 G_\mathrm{f}}
 \right)
\end{align}

```python
bs_fracture_energy = MATS1D5BondSlipD(omega_fn='fracture-energy', s_max=0.05)
bs_fracture_energy.omega_fn_.trait_set(kappa_0=0.01, G_f=1)
bs_fracture_energy.omega_fn_
```

$\displaystyle 1 - \begin{cases} 1 & \text{for}\: \kappa < \kappa_{0} \\e^{\frac{\left(\kappa - \kappa_{0}\right) \left(\sqrt{E_{b}} \sqrt{- E_{b} \kappa_{0}^{2} + 4 G_{f}} + E_{b} \kappa_{0}\right)}{E_{b} \kappa_{0}^{2} - 2 G_{f}}} & \text{otherwise} \end{cases}$

Even though the expressions look different, they are equivalent. The implemented version has been rendered using the `sympy` algebra system and can be transformed in to a simpler format given above. Let us test the flexibility of the provided function.

```python
bs_fracture_energy.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

Note that, for large values of $G_\mathrm{f}$, maximum value of stress can become larger than the stress at the onset of damage, i.e. $\tau(\kappa_0)$. Thus, in contrast to the abacus damage function, material strength is not available as a model parameter. However an algebraic rearrangement identifying the maximum stress and resolving $\kappa_0$ as a function of strength is possible.

        Keep fracture energy in mind, we will revisit it

This shape of the damage function introduces physical interpretation into the control of the softening branch. As we will show in Tour 6, the area below the softening stress-strain curve represents the energy dissipated during the loading process within a unit volume of material.

## Summary and classification of damage evolution functions

| Name | Symbol | Material parameters |
| :- | :-: | :-: |
| linear damage | $\omega_1(\kappa)$ | $\kappa_0, \kappa_\mathrm{u}$ |
| cumulative probability density | $\omega_2(\kappa)$ | $\lambda, m$|
| softening slope | $\omega_3(\kappa)$ | $\kappa_0, \kappa_\mathrm{f}$ |
| softening slope and curvature | $\omega_4(\kappa)$ | $\kappa_0, \kappa_\mathrm{u}, \alpha$ |
| fracture energy | $\omega_5(\kappa)$ | $\kappa_0, G_\mathrm{f}$ |

# **Sheet interface - damage in 2D**

[](https://moodle.rwth-aachen.de/mod/page/view.php?id=551838) part 4

## Equivalent measure of strain

So far, the scalar variable $\kappa$ could be considered equivalent to the maximum slip attained during the loading history. However, it can be defined also for two and three dimensional problems. To simulate the damage in continuum
problems, an equivalent strain can be defined in terms of strain tensor invariants. To provide a simple example,
let us consider the application of the damage framework
for the modeling of two-dimensional interface damage.

In this case, equivalent strain can then be defined as the distance from the undeformed configuration with zero slip $s_x, s_y = [0,0]$
\begin{align}
\kappa =  \sqrt{ s^2_x(\theta) + s_y^2(\theta) }
\end{align}

```python
s_x, s_y = sp.symbols('s_x, s_y')
kappa_ = sp.sqrt( s_x**2 + s_y**2 )
kappa_
```

Thus, the elastic domain is represented by a circle with the radius $\kappa_0$ in $s_x$ and $s_y$ plane.

```python
get_kappa = sp.lambdify( (s_x, s_y), kappa_, 'numpy' )
phi = np.linspace(0, 2*np.pi, 500)
sx, sy = np.sin(phi), np.cos(phi)
fig, ax = plt.subplots(1,1, figsize=(4,4), tight_layout=True)
fig.canvas.header_visible = False
ax.plot(sx,sy,label=r'$\kappa_0 = 1$')
ax.fill(sx,sy, color='gray', alpha=0.2)
ax.set_xlabel(r'$s_x$'); ax.set_ylabel(r'$s_y$');
ax.set_aspect('equal'); ax.legend();
```

    Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …

        Other examples of equivalent strain definition

To show the flexibility of the the inelastic domain specification using the
equivalent measure let us provide two more examples which are used
to define a tensile elastic limit for brittle materials.
Given a strain and stress tensors $\boldsymbol{\varepsilon}$ and $\boldsymbol{\sigma}$,
the elastic limit can be introduced using the maximum
principal tensile stress $\sigma_i$ and divide it by the $E$ modulus as follows.
\begin{equation}\label{eq:rankine_eq}
\kappa = \frac{1}{E} \mathrm{max}(\sigma_i), \qquad i=1,2,3
\end{equation}
The principal stresses $\sigma_i$ are obtained as the eigenvalues of the stress tensor $\boldsymbol{\sigma}$.

Another option is to define a norm of the strain tensor expressed as follows
\begin{equation}\label{eq:mazars_eq}
\kappa = ||\langle
{\boldsymbol{\varepsilon}}
\rangle || = \sqrt{\langle \boldsymbol{\varepsilon} \rangle  : \langle \boldsymbol{\varepsilon} \rangle} = \sqrt{ {\langle \varepsilon_1\rangle}^2   + {\langle\varepsilon_2 \rangle}^2 + {\langle\varepsilon_3 \rangle}^2}
\end{equation}
In this expression, the McAuley brackets $\langle . \rangle$ means the positive part of (.), e.g. $\langle x \rangle = (x+|x|)/2$. And $\varepsilon_1, \varepsilon_2, \varepsilon_3$ are the principal strains.

## Reversibility threshold function

While plastic models introduce the definition of elastic domain by defining a threshold criterion for the values of stress variables, in damage models, the elastic domain is delimited using an equivalent strain measure $\kappa$.
\begin{align}
f := \kappa - \kappa_0 \le 0
\end{align}
This condition is also referred to as **damage initiation criterion** or loading function in the literature.

## How to treat unloading and reloading?

While in plastic model, the consistency has been used to derive the evolution equations and to distinguish unloading from loading steps, in damage models the unloading and reloading is distinguished automatically using the following criterion:
\begin{align}
\kappa(t) = \max_{\theta < t} \kappa(\theta) = \max_{\theta < t} \sqrt{ s^2_x(\theta) + s_y^2(\theta) }
\end{align}

This expression means that the equivalent strain $\kappa$ can grow only if the new value at time $t$ is larger than any other value of $\kappa$ attained during the previous time stepping $\theta < t$.

## Constitutive law between slip and bond stress vectors

To return to the case of an interface damage, we need to relate the slip vector components to the stress vector components.
\begin{align}
\tau_x &= (1 - \omega(\kappa)) \, E_b \, s_x \\
\tau_y &= (1 - \omega(\kappa)) \, E_b \, s_y
\end{align}

This kind of definition introduces **isotropic damage**. It uses a single damage variable for both slip directions:

> ** As a consequence, damage increment caused in direction $x$ directly affects the behavior in direction $y$**

        Let see how does the isotropic damage evolves in a single point

To visualize the response of a material point with two control slip  vector components $s_x, s_y$ and two stress vector components $\tau_x, \tau_y$ in a 3D diagram, we will plot the loading histories in the space $\left[s_x, s_y, \sqrt{\tau_x^2 + \tau_y^2}\right]$. The vertical axis represents the norm of the stress vector. We will make use of an interactive model component `damage_2d_explorer`.

```python
import damage2d_explorer as de
bs = MATS1D5BondSlipD(omega_fn='fracture-energy', s_max=0.05)
bs.omega_fn_.trait_set(G_f=10, kappa_0=0.001)
explore = de.Explore(bs=bs, s_min=-0.1, s_max=0.1)
explore.interact()
```

    VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…

# **Characterization of damage models**

## Main properties
 * Independent definition of **elastic domain** using equivalent strain measure and **damage evolution** using damage function
 * The definitions of equivalent strain defining the inelastic domain and damage function can be arbitrarily combined
 * **Isotropic damage** - affects all loading directions equally
 * At any state in history, radial unloading returns back to origin with zero stress
 * Suitable to model brittle fracture

## Damage versus plasticity in view of algorithmic treatment

Recall that for models based on plasticity, return mapping was necessary to return back to an admissible state.
In case of damage models, the relation between strain and stress can be introduced as an explicit function.
This means that  no return-mapping is necessary. Given a strain increment,
the corresponding stress can be directly evaluated.

## Damage versus multi-linear models in view of algorithmic treatment

Recall that the multi-linear models of the bond behavior also allowed for an explicit evaluation without return-mapping. The question might arise: What is the added value of a damage model compared to multi-linear elastic models? They both can represent the non-linear response of a monotonically loaded pullout test. The reason is twofold

 1. Damage model can distinguish unloading/reloading by remembering the measure of maximum strain $\kappa$ attained during history as demonstrated in the example below.
 2. Damage model can introduce inelasticity in 2D and 3D material models. Multilinear laws are only limited to one-dimensional model. This aspect of damage models is shown in the example of an interface damage model below.

# Open questions

 * How is the damage model embedded in a FE simulation
     * As a material subroutine returning the stress and stiffness for a given slip/strain and $\kappa_n$
 * What is the instantaneous stiffness at control time $t$? needed in FE simulation
     * Secant stiffness is available directly as $\psi E_b$
     * Consistent algorithmic stiffness can be calculated as $\left. \frac{\partial \tau}{\partial s} \right|_t$
 * Can the damage model represent cracking?
     * Yes but we need to talk about energy first

# Links to manuals of non-linear FE tools

This brief introduction of damage modeling is meant as an entry point to reading more thorough documentation, e.g. the parts of software manuals describing damage.

 - [ABAQUS Manual: available damage functions](https://classes.engineering.wustl.edu/2009/spring/mase5513/abaqus/docs/v6.5/books/usb/default.htm?startat=pt04ch11s06abm39.html)
 - [ATENA Manual: Stress-strain relations for concrete (Pages 18-21)](https://www.cervenka.cz/assets/files/atena-pdf/ATENA_Theory.pdf)

        Exercise X0501: Bond-slip law expressed as damage function

        Exercise X0502: Bond-slip law expressed as damage function

      4.3: Cyclic pullout of textile fabrics and CFRP sheets

    5.2 Pullout behavior governed by damage

```python

```