**Course:** TVM4174 - Hydroinformatics for Smart Water Systems - Spring 2022

# Using Plotly Package for Interactive Visualization

*Developed by Bastian Habbel and Leonardo Sigmund*

# Introduction

The plotly Python library is an interactive, open-source plotting library that supports over 40 unique chart types covering a wide range of statistical, financial, geographic, scientific, and 3-dimensional use-cases. Built on top of the Plotly JavaScript library (plotly.js), plotly enables Python users to create interactive web-based visualizations that can be displayed in Jupyter notebooks, saved to standalone HTML files, or served as part of pure Python-built web applications using Dash. The plotly Python library is sometimes referred to as "plotly.py" to differentiate it from the JavaScript library.

Full details for the plotly library are given in the [Plotly Documentation](https://plotly.com/python/). This notebook aims to give a basic understanding of the possibilities of this package in the field of water distribution, general data sciences and mapping.

The sublibraries which are used in this notebook are `plotly.express` as `px`'and `plotly.graph_object` as `go`. Eventhough it's not necessary to reload the package for every chapter, we decided to leave it in the code so that the chapters can be used indepedent.

# Previous knowledge

This notebook requires some previous knowledge some packages. In case you have difficulties in understanding the code, we recommend you to take a look on [Mark Bakker's Tutorials](https://mbakker7.github.io/exploratory_computing_with_python/).
- `matplotbib`: We refer to it in some parts since the working process is similar.
  - Notebook 1: [Basics and Plotting](https://nbviewer.org/github/mbakker7/exploratory_computing_with_python/blob/master/notebook1_basics_plotting/py_exploratory_comp_1_sol.ipynb)
- `numpy`: Used for functions
  - Notebook 4: [Functions](https://nbviewer.org/github/mbakker7/exploratory_computing_with_python/blob/master/notebook4_functions/py_exploratory_comp_4_sol.ipynb)
- `pandas`: In general useful for data handling with dataframes
  - Notebook 8: [Pandas and time series](https://nbviewer.org/github/mbakker7/exploratory_computing_with_python/blob/master/notebook8_pandas/py_exploratory_comp_8_sol.ipynb)
- `wntr`: This package should be known from the class, it is used to analyze water distribution networks. There may be a good notebook available on blackboard which explains the necessary basics for this package.

# Content

**0. [Installation of the package](#0.-Installation-of-the-package)**

**1. [Plotting data series](#1.-Plotting-data-series)**
  - Basic bar plot
  - Customized plot
  - Range slider and selectors
  - Basic line plot

**2. [Statistical Analysis: Histogram](#2-Statistical-Analysis:-Histogram)**

**3. [3D plotting](#3.-3D-plots)**
  - 3D scatter plots
  - 3D surface plots

**4. [Map plotting](#4.-Map-plotting)**

**5. [Plotting a water distribution network](#5.-Plotting-a-water-distribution-network)**

**6. [Animations](#6.-Animations)**

**7. [Saving figures](#7.-Saving-figures)**

There are some tasks included, which you can use to test your knowledge. The [Solutions to the tasks](#Task-solutions) are at the end of this notebook.

Let's start!

# 0. Installation of the package

Plotly can be installed using `pip`. Once installed on the kernel, this step can be skipped. Be aware, maybe the version is updated...

```python
pip install plotly==5.7.0
```

# 1. Plotting data series

### Import data

First, we need some example data. In this case, we use annual rainfall data for Rotterdam which is stored under `rotterdam_rainfall_2012.txt`. Here, we prepare the data since we start with a raw textfile.

```python
import pandas as pd

#1. read data
raindailyRotterdam = pd.read_csv('data/rotterdam_rainfall_2012.txt', sep=',', skiprows=9, parse_dates=['YYYYMMDD'],skipinitialspace=True)

#2. convert to mm/d
raindailyRotterdam.iloc[:,2] = raindailyRotterdam.iloc[:,2] * 0.1

#3. replace false values
raindailyRotterdam.loc[raindailyRotterdam.RH<0.0, 'RH'] = 0.0
```

### Basic bar plot

The library `express`, usually imported as `xp`, is build on basis of the library `graph_objects`, but makes the code more simple.
Use Plotly Express to show the data in an interactive graph:

```python
import plotly.express as px

fig = px.bar(raindailyRotterdam, x='YYYYMMDD', y='RH')
fig.show()
```

You can already zoom in the graph and see more details compared to other methods. Give it a try with your mouse! There is a menu on the top-right corner which e.g. allows you to come back to the initial state.

### Customized plot

We can store and costumize the figure, similar to the procedure used in `matplotlib`:

- `ticklabelmode` indicates where the labels are positioned.
- `dtick` defines the inferval of labels.

```python
#store the plot in a variable
fig = px.bar(raindailyRotterdam, x='YYYYMMDD', y='RH')

#add a title. The content of the following line could also be part of the brackets above:
fig.update_layout(title = 'Rainfall distribution Rotterdamm')

#costumize the axis
fig.update_xaxes(title='2012', ticklabelmode="period")
fig.update_yaxes(title='Precipitation [mm/day]', dtick='2')

#show the graph
fig.show()
```

### Rangeslider and selectors

As you may have noticed, it's sometimes hard to zoom into a part of the graph without changing the scaling in y-direction without purpose. Therefore, Plotly has invented additional navigation tools that will be shown in this chapter.

#### Rangeslider
A rangeslider can be added to allow a closer look on the data series by setting the boolean `rangeslider_visible` on true:

```python
fig = fig.update_xaxes(rangeslider_visible=True)
```

More information about the rangeslider can be found in the [documentation](https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-rangeslider).

#### Selectors
Additional selectors for pre-defined time-intervals might be useful as well:

```python
fig = fig.update_xaxes(
    rangeslider_visible=True,
    rangeselector=dict(
        buttons=list([
            dict(count=7, label="7 days", step="day", stepmode="todate"),
            dict(count=1, label="1 month", step="month", stepmode="backward"),
            dict(count=6, label="1/2 year", step="month", stepmode="backward"),
            dict(step="all")
        ])
    )
)
```

It is important to mention that it's necessary to parse the dates before so that plotly knows where to set the timesteps. Same here, in case you have further questions, they are probably answered in the [documentation](https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-rangeselector).

Let's have a look on the result:

```python
fig.show()
```

The selectors appear on top of the graph and the slider on the bottom.

Give it a try! You can always come back to the initial view using the navigation in the top-right corner.

### Basic line plot

Some data, for instance yearly trends, are better to show in a line than in a barplot. Plotly express provides interactive line charts for this purpose as well as will be shown in the following.

Gapminder Foundation is a non-profit venture registered in Stockholm, Sweden, that promotes sustainable global development and achievement of the United Nations Millennium Development Goals by increased use and understanding of statistics and other information about social, economic and environmental development at local, national and global levels. This also touches our topic, the water distribution networks. Let us have a global view on the development of water distribution in the least-developed countries regarding water access.

Therefore, we first import the data about about overall water access, provided by Gapminder Foundation and the World Bank. A look in the dataframe tells us that there is data of 168 countries worldwide, whereas not all the datasets are complete. We will use Plotly to visualize the development of the 10 countries having the worst access to water in 2000.

```python
#Using Pandas to read the csv file and sort the data
water = pd.read_csv('data/gapminder_water_access.csv', index_col=0)
ranking2000 = water.sort_values('2000')
worst2000 = ranking2000.head(10)
```

```python
#Using plotly for visualization:
fig = px.line(worst2000.T, color_discrete_sequence=px.colors.diverging.balance)

#add a title. The content of the following line could also be part of the brackets above:
fig.update_layout(title = 'Development of 10 countries with poorest water distribution in year 2000')

#costumize the axis
fig.update_xaxes(title='Year', ticklabelmode="period", dtick = 1)
fig.update_yaxes(title='overall water access [%]', dtick='10')
```

As you can see, eventhough the situation is not perfect yet, a lot of improvement has taken place!

Even if the lines have different colors, it might be difficult to distinguish between the different countries. The interactive graphic helps out in two different ways: Either, you can hover over the lines and you will see the country as well as the x & y parameters, or you can use the legend on the right side to deselect (one click) or to isolate (double click) one country. Give it a try!

### Task 1: show a time-series analysis

In this task, you have to combine the two examples from above: You should create a visualization of the precipitation measurement in Sagelva from 2018 to 2020.

- create a pandas dataframe from the csv file named `'Sagelva_Precipitation.csv'`from the folder `'data'` and have a look on it
- create a barplot using the plotly express library. Be aware of the following things:
  - Bars: The bars should not add up but stand beside each other --> use `barmode`
  - Title: Add a `title` for the plot
  - Axis: Add a titles and make sure that the dates are shown correctly on the x axes
  - Legend: make sure that you can select and deselect the different years
- Add the additional navigation tool `rangeslider`

```python
# Your code here...
```

[Solution to task 1](#Solution-to-task-1)

# 2. Statistical analysis: Histogram

Back to the first data set about the precipitation in Rotterdam.

A statistical analysis of the data can be helpful to find a pattern. The express library has therefore as well some tools inclded of which one will be shown here. On top, we use the `graph objects` library, usually imported as `go`, which is the basis for the plotly express library, in order to add a trace and show the daily precipitation as well.

```python
import plotly.express as px
import plotly.graph_objects as go

fig = px.histogram(raindailyRotterdam, x='YYYYMMDD', y='RH', histfunc='avg', title="Monthly Average Rainfall", range_y=['-1','25'], range_x=['2012-01-01', '2012-12-31'])
fig.update_traces(xbins_size="M1")
fig.update_xaxes(title='2012',showgrid=True, ticklabelmode="period", dtick="M1", tickformat="%b\n%Y")
fig.update_layout(bargap=0.05)
fig.add_trace(go.Scatter(mode='markers', x = raindailyRotterdam['YYYYMMDD'], y = raindailyRotterdam['RH'], name="daily"))
```

### Task 2: Time series analysis

Please use the rainfall data of Rotterdam to create a barplot of the total rainfall per month. Represent the daily variation with a line.

```python
# Your code here...
```

[Solution to task 2](#Solution-to-task-2)

# 3. 3D plots

Plotly enables you to create 3-dimensional graphs, that you can pan and zoom using the mouse.

### 3D scatter plots

The simplest form of these are scatter plots. These can be used to graph point that are dependend on 2 variables.

Further examples can be found in the official [documentation](https://plotly.com/python/3d-scatter-plots/).

Here is an example of the measured pressures at different scensors (hydrants) for different scenarios in a water distribution system. Used are the final results of the 2nd Assignment of the Hydroinformatics course.

The margins are removed, because axis labels can't move into the margin areas in 3d plots.

```python
# import packages
import plotly.express as px
import numpy as np
import pandas as pd

#read in data and convert into usable format
df = pd.read_csv('data/pressures.csv', decimal='.', sep=',')
df = df.melt('Sensors')
df.columns = ['Sensors', 'Scenarios', 'Pressure [m]']
df2 = pd.read_csv('data/SSE_values.csv', decimal='.', sep=',')
df2 = df2.melt('Sensors')
df.insert(3, 'SSE', df2["value"])

#plot the data
fig = px.scatter_3d(df, x='Sensors', y='Scenarios', z='Pressure [m]', height=700)

#remove margins
fig.update_layout(margin=dict(l=0, r=0, t=0, b=0))

fig.show()
```

You can use your mouse to rotate the 3D plot and the mousewheel to zoom in. Try it out!

As a forth dimension color can be used. In the following plot the sum of squared errors (SSE) between the measured and simulated pressures over all scenarios and sensors in the water distribution system are indicated by their `color`.
Furthermore can the `opacity`be changed

```python
#plot the data
fig = px.scatter_3d(df, x="Sensors", y="Scenarios", z='Pressure [m]', color = "SSE",
                    opacity = .6, title="Measured pressures", width=800, height=800)

fig.show()
```

To change the appearence of 3-dimenionsal plots the function `fig.update_scenes` is used. Here are a few examples of what can be changed. Also check the [documentation](https://plotly.com/python/reference/layout/scene/).

- `xaxis_nticks` changes the number of ticks on the xaxis.
- `camera_projection_type`can be used to change the camera projection between "perspective" and "orthographic".
- `xaxis_color` changes all colors associated with this axis.
- `xaxis_gridcolor`can be used to change the color of the x-axis grid. RGB-values can also be used to define the color.
- `xaxis_backgroundcolor`is used to change the color of the backround.
- `xaxis_tickangle`is used to change the angle of the ticklabels.
- `xaxis_ticks`is used to disable the tickmarks
- `xaxis_tickfont` is used to change the `color`, the `family`(the font) and the `size`of the tick labels.

To avoid the labels from getting cut off and make sure the title is displayed on the sides, the `margin` can be set with the function `fig.update_layout`. In 3d plots, axis labels don't actually pertrude into this margin area though.

```python
# change appearence
fig.update_scenes(xaxis_nticks=12,
                  camera_projection_type="orthographic",
                  xaxis_color="black",
                  xaxis_gridcolor='rgb(204, 204, 204)',
                  yaxis_gridcolor='rgb(204, 204, 204)',
                  zaxis_gridcolor='rgb(204, 204, 204)',
                  xaxis_backgroundcolor="white",
                  yaxis_backgroundcolor="white",
                  zaxis_backgroundcolor="white",
                  xaxis_tickangle= 30,
                  xaxis_ticks="", yaxis_ticks="",
                  xaxis_tickfont=dict(size=11, family="PT Sans Narrow"),
                  yaxis_tickfont=dict(size=11, family="PT Sans Narrow"),
                  zaxis_tickfont=dict(size=11, family="PT Sans Narrow"))

# increase margin
fig.update_layout(margin=dict(l=50, r=50, t=100, b=50))
```

### 3D surface plots

The Rastrigin function is a well-known function, whith a lot of local optima, but only one global optimum. It is defined as:

\begin{align}
f(\mathbf{x}) \ = \ a \cdot D + \sum_{i=1}^{D} \left(x_{i}^{2} - a \cdot \cos (2 \pi x_i) \right)
\end{align}

For more details check it out on [Wikipedia](https://de.wikipedia.org/wiki/Rastrigin-Funktion)!

To display it as a 3-dimensional surface plot, we use the `go.Figure`function, part of `plotly.graph_objects`.

```python
# define the rastrigin function for D dimensions
def rastrigin(xvector, a=15):

    D = len(xvector)
    value = D * a
    for x in xvector:
        value += x ** 2 - a * np.cos(2 * np.pi * x)
    return value
```

```python
# calculate data for desired range
x = np.linspace(-7, 7, 250)
X, Y = np.meshgrid(x, x)
Z = rastrigin([X,Y])
```

```python
#create surface figure
import plotly.graph_objects as go

fig = go.Figure(data=[go.Surface(x=X, y=Y, z=Z)],)

fig.update_layout(title='Rastrigin function',
                  width=800, height=800,
                  margin=dict(l=65, r=50, b=90, t=90))

fig.show()
```

You can see the many local optima, but there is only one global minimum, which is located at (0,0).

### Task 3: Create a surface plot of a monkey saddle

Use your gained knowledge to create a 3d surface plot of a "monkey saddle".
A monkey saddle is the name of the following function:
\begin{align}
z \ = \ x^{3}-3xy^{2}
\end{align}

For more information check it out on [Wikipedia](https://en.wikipedia.org/wiki/Monkey_saddle)!

- Show the graph in a range from -10 to 10 (in x and y direction)
- Change the colors to make it look like "dark mode"

```python
# Your code here...
```

[Solution to task 3](#Solution-to-task-3)

# 4. Map plotting

The map feature can be useful for any kind of geographical information. For instance, data regarding water-distribution networks can be visualized as well as socio-geographical behaviors like the distribution of a population within a country.

For this task, we import a table with cities and small towns in Norway. The data is downloaded from [SimpleMaps.com](https://simplemaps.com/data/no-cities) and adapted to the unicode to be readable with pandas. More information about pandas can be found in [Mark Bakkers Notebook 8](https://nbviewer.org/github/mbakker7/exploratory_computing_with_python/blob/master/notebook8_pandas/py_exploratory_comp_8_sol.ipynb).

```python
#Installation of the Packages
import pandas as pd
import plotly.express as px

#read data
placesNo = pd.read_csv('data/places_norway.csv')
```

We can use the [Scatter Mapbox](https://plotly.com/python/mapbox-layers/) function which requires latitudinal and longitudinal coordinates and can show us the cities in a color code according to the population.

- `dataframe, lat='...', lon='...` This function needs to know where to find the coordiates in the dataframe. Coordinates are given in the Decimal Degree format with one latitude (y - direction: North/South) and one longitude(x - direction: East/West) value.
- `layout.mapbox.style` defines the lowest layer, also known as base map.
  - It refers to an open library and is used by known services as STRAVA, lonely planet and national geographic.
  - Default is a white background, so if you want to have the map in the background you have to set a style.
    - Three commonly used are `'open-street-map'`, `'carto-positron'` and `'stamen-terrain'`.
    - Further styles can be seen as well in the [documentation](https://plotly.com/python/mapbox-layers/).
  - It is possible to use satellite images provided by the Unitest States Geological Survey (USGS).
- `hover_name` defines the upper row which is shown when you move your mouse and hover about a point on the map.
- `hover_data` will be shown in the box as well
- `zoom` defines the initial zoom level. 1 shows the entire world, 10 is suitable to show points in a city.
- The layout is updated to adapt the outputsize and add a title.

```python
map = px.scatter_mapbox(placesNo, lat='lat', lon='lng',
                        mapbox_style = 'carto-positron',
                        hover_name = 'City',
                        hover_data = ["Population", "Status"],
                        color = 'Administration',
                        color_discrete_sequence=px.colors.cyclical.IceFire,
                        size = 'Population',
                        zoom=3.1)
map.update_layout(title='Cities in Norway',
                  width=950, height=800)
map.show()
```

As well as in the time series analysis, we can use the mouse to navigate through the map. Thereby we can extract more knowledge out of the interactive map as  would be possible in a standard map. Give it a try and find out the population of Trondheim!

### Task 4:  Create a map

Let's use the gained knowledge and create a map of all the swedish cities:
- The csv- file you need is called `'places_sweden.csv'`
- Select a darker basemap as in the example and adapt the color scheme.
- Make sure that the city names, the authority type as well as the population is shown. You may have to take a look into the dataframe to see the correct labels.
- Does this map need another initial zoom- level?

```python
# Your code here...
```

[Solution to task 4](#Solution-to-task-4)

# 5. Plotting a water distribution network

The `wntr`package can be used as a link between python and an EPANET network model. More information about EPANET is given in the lectures as well as in the [documentation](https://epanet22.readthedocs.io/en/latest/index.html).

In the `wntr`package there is an implementation of `plotly`, which can be used to create basic interactive plots of the water network model. [Documentation](https://wntr.readthedocs.io/en/stable/apidoc/wntr.graphics.network.html?highlight=plot_interactive)

But this functionality is quite limited, as only `node_attribute`s can be displayed. The following example shows how to create this type of interactive html file using the `wntr`package.

```python
import wntr
```

```python
# get elevation data
wn = wntr.network.WaterNetworkModel('data/Exercise_Original.inp')
elevation = wn.query_node_attribute('elevation')

# create plot (creates an html file)
wntr.graphics.plot_interactive_network(wn, filename='data/Interactive_Model.html', auto_open=False,
                            node_attribute=elevation, node_cmap = "magma",
                            title= "Elevation of Network Model",
                            figsize = (854,480))
```

To display an html file inside of jupyter notebooks, the `IPython.display` can be used.

```python
# display html file in jupyter notebooks
from IPython.display import IFrame
IFrame(src='data/Interactive_Model.html', width=980, height=550)
```

## 6. Animations

To create a simple animaton in a scatter plot, `animation_frame`and `animation_group`can be used.
- `animation_frame` defines over which data the animation takes place. We use the time in years.
- `animation_group`: rows with the same group are considered as the same object and therefore animated from one frame to the next.

Check out the [documentation](https://plotly.com/python/animations/) for more details.

```python
# Animated scatter plot
import pandas as pd
import plotly.express as px

df_worlddata = px.data.gapminder()
fig = px.scatter(df_worlddata, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])
fig.update_xaxes(title='GDP per capita [PPP]')
fig.update_yaxes(title='Life Expectancy [years]')
fig.show()
```

You can make use of the interactive features again! By hovering over the circles you can see, which country they represent and all their associated data. Furthermore, you can use the legend on the right side to deselect (one click) or to isolate (double click) one continent, to make the number of countries less overwhelming.

Try it out yourself!

The same concept can also be applied to barplots using the `px_bar` function.

```python
# Animated bar plot
px.bar(df_worlddata, x="continent", y="pop", color="continent",
  animation_frame="year", animation_group="country", range_y=[0,4000000000])
fig.update_xaxes(title='Continent')
fig.update_yaxes(title='Population')
```

### Task 6: Animate population change

Use the gained knowledge to create a barplot animation for the relative population change of African countries since 1952.
- Use the gapminder data as a basis
- Display the countries' absolute population as a color palette

```python
# Your code here...
```

[Solution to task 6](#Solution-to-task-6)

# 7. Saving figures

It might be useful for you to save your figures in order to use them somewhere else. For sure, you can simply make screenshots and add them to your text. But the interesting part of the plotly package is that it's interactive. Therefore, it has to be saved in a different way.

### html

The easiest way is to write a html file which you can for instance use on another homepage or add to an email:

```python
fig.write_html('data/fig.html')
```

The last figures saved under the variable fig is saved now in the location `data/fig.html` or the path that you have chosen. It's important that the folder already exists. Take care to not overwrite your files!

Now, we can have a look on the saved figure as well here in the notebook:

```python
# display html file in jupyter notebooks
from IPython.display import IFrame
IFrame(src='data/fig.html', width=980, height=550)
```

You can use this code as well to open any other html file from your computer.

### Dash

Since it can open a whole new chapter to write a web application which uses the plotly library, we recommend you to go to the documentation of [Dash](https://plotly.com/dash/) in case you want to use the data on another platform. As well, for all the plots shown above, there a ways to let it run on a server using Dash, it is described in each documentation chapter at the end of the page. It might be worth it, there are many possibilities and as mentioned before, this is used by big compainies like National Geographic, Lonely Planet, and so on.

That's it, we hope you have learned something :)

[Click here to go back to the start](#Introduction)

# Task solutions

# Solution to task 1

```python
import plotly.express as px
import pandas as pd

#1. read data
raindaily = pd.read_csv('data/Sagelva_Precipitation.csv', parse_dates=['Date'], index_col=0)

#create barplot
fig = px.bar(raindaily, barmode='group')

#add a title
fig.update_layout(title = 'Daily Precipitation in Sagelva')

#costumize the axis
fig.update_xaxes(title='Year', ticklabelmode="period", rangeslider_visible=True)
fig.update_yaxes(title='Daily precipitation [mm]')
```

Continue with chapter 2: [Statistical analysis](#2.-Statistical-analysis:-Histogram)

# Solution to task 2

```python
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd

#prepare data
raindailyRotterdam = pd.read_csv('data/rotterdam_rainfall_2012.txt', sep=',', skiprows=9, parse_dates=['YYYYMMDD'],skipinitialspace=True)
raindailyRotterdam.iloc[:,2] = raindailyRotterdam.iloc[:,2] * 0.1
raindailyRotterdam.loc[raindailyRotterdam.RH<0.0, 'RH'] = 0.0

#make plot
fig = px.histogram(raindailyRotterdam, x='YYYYMMDD', y='RH', histfunc='sum', title="Monthly Total Rainfall", range_y=['-1','160'], range_x=['2012-01-01', '2012-12-31'])
fig.update_traces(xbins_size="M1")
fig.update_xaxes(title = '2012', showgrid=True, ticklabelmode="period", dtick="M1", tickformat="%b\n%Y")
fig.update_layout(bargap=0.05)
fig.add_trace(go.Scatter(mode='lines', x = raindailyRotterdam['YYYYMMDD'], y = raindailyRotterdam['RH'], name="daily"))
```

Continue with chapter 3: [3D plotting](#3.-3D-plots)

# Solution to task 3

```python
#define the Monkey saddle function
def monkey(x,y):
    z=x**3-3*x*y**2
    return z
```

```python
#create xyz data for this formula
import numpy as np
r = np.linspace(-10, 10, 250)
x,y = np.meshgrid(r, r)
z = monkey(x,y)
```

```python
#make graph
import plotly.graph_objects as go
fig = go.Figure(data=[go.Surface(x=x, y=y, z=z)],)

fig.update_layout(title='Monkey saddle function',
                  width=800, height=800,
                  margin=dict(l=65, r=50, b=90, t=90))

#dark mode
fig.update_scenes(xaxis_gridcolor='white',
                  yaxis_gridcolor='white',
                  zaxis_gridcolor='white',
                  xaxis_backgroundcolor="black",
                  yaxis_backgroundcolor="black",
                  zaxis_backgroundcolor="black")
fig.show()
```

Continue with chapter 4: [Map plotting](#4.-Map-plotting)

# Solution to task 4

```python
import pandas as pd
import plotly.express as px

placesSe = pd.read_csv('data/places_sweden.csv')
map = px.scatter_mapbox(placesSe, lat='lat', lon='lng',
                        mapbox_style = 'carto-darkmatter',
                        hover_name = 'City',
                        hover_data = ["Population", "Status"],
                        color = 'Administration',
                        color_discrete_sequence=px.colors.cyclical.IceFire,
                        size = 'Population',
                        zoom=3.25)
map.update_layout(title='Cities in Sweden',
                  width=950, height=800)
map.show()
```

Continue with chapter 5: [Plotting a water distribution network](#5.-Plotting-a-water-distribution-network)

# Solution to task 6

```python
#import packages
import pandas as pd
import plotly.express as px
import warnings
warnings.filterwarnings('ignore')

#import data
df_worlddata = px.data.gapminder()
df_africa = df_worlddata.loc[df_worlddata['continent']=='Africa']

#calculate relative population
for i,line in df_africa.iterrows():
    if line["year"]==1952:
        basepop = line["pop"]
    df_africa.at[i,"rel_pop"]= line["pop"]/basepop
```

```python
#create barplot
fig = px.bar(df_africa, x="country", y="rel_pop", color="pop",
             animation_frame="year", animation_group="country", range_y=[0,8],
             title= "Relative population change in African countries since 1952")

fig.update_layout(width=1000, height=800)
fig['layout']['updatemenus'][0]['pad']=dict(r= 10, t= 150)
fig['layout']['sliders'][0]['pad']=dict( t= 150,)

#update axes labels
fig.update_xaxes(title='Country')
fig.update_yaxes(title='Relative Population (compared to 1952)')

fig.update_scenes(xaxis_nticks=100)
fig.show()
```

Finish with chapter 7: [Saving figures](#7.-Saving-figures)

Group_Projects/Plotly Package for Interactive Visualization/Plotly Package for Interactive Visualization.ipynb


# 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: **Feb 18, 2022**

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

## Aims and Overarching Motivation

### Monday February 14

Discussion of conditions for conservative forces and summing up our
discussion on conservative forces. Discussion of potential surfaces
and their interpretations.

**Reading suggestion**: Taylor sections 4.6, 4.9, 4.10 and 5.1 and 5.2 on harmonic oscillations.

### Wednesday February 16

The Earth-Sun problem and energy-conserving algorithms and how to encode in more efficient ways various algorithms for solving the equations of motion (Euler, Euler-Cromer and Velocity Verlet).

* [Links to Julie's material on code reusability](https://github.com/mhjensen/Physics321/tree/master/doc/pub/week7/ipynb)

**Reading suggestions**: Taylor section 4.8 and these notes

### Friday February 18

Working on the Earth-Sun problem and hw 5. Hints on various exercises.

**Reading suggestions:** Taylor chapters 3 and 4 and these notes.

## The curl of a force and 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 turn 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}$

$$
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.
$$

## Graphing the potential energy and what we can learn from that

This is taken from homework 4, exercises 5.

A particle is under the influence of a force $F=-kx+kx^3/\alpha^2$, where $k$ and $\alpha$ are constants and $k$ is positive.

Determine $V(x)$ and discuss the motion. It can be convenient here to
make a sketch/plot of the potential as function of $x$.

We assume that the potential is zero at say $x=0$. Integrating the force from zero to $x$ gives

$$
V(x) = -\int_0^x F(x')dx'=\frac{kx^2}{2}-\frac{kx^4}{4\alpha^2}.
$$

## Making the plot

The following code plots the potential. We have chosen values of $\alpha=k=1.0$. Feel free to experiment with other values. We plot $V(x)$ for a domain of $x\in [-2,2]$.

```
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt
import math

x0= -2.0
xn = 2.1
Deltax = 0.1
alpha = 1.0
k = 1.0
#set up arrays
x = np.arange(x0,xn,Deltax)
n = np.size(x)
V = np.zeros(n)
V = 0.5*k*x*x-0.25*k*(x**4)/(alpha*alpha)
plt.plot(x, V)
plt.xlabel("x")
plt.ylabel("V")
plt.show()
```

## Interpreting the results

From the plot here (with the chosen parameters)
1. we see that with a given initial velocity we can overcome the potential energy barrier

and leave the potential well for good.
1. If the initial velocity is smaller (see next exercise) than a certain value, it will remain trapped in the potential well and oscillate back and forth around $x=0$. This is where the potential has its minimum value.

2. If the kinetic energy at $x=0$ equals the maximum potential energy, the object will oscillate back and forth between the minimum potential energy at $x=0$ and the turning points where the kinetic energy turns zero. These are the so-called non-equilibrium points.

## Final interpretations

What happens when the energy of the particle is $E=(1/4)k\alpha^2$? Hint: what is the maximum value of the potential energy?

From the figure we see that
the potential has a minimum at at $x=0$ then rises until $x=\alpha$ before falling off again. The maximum
potential, $V(x\pm \alpha) = k\alpha^2/4$. If the energy is higher, the particle cannot be contained in the
well. The turning points are thus defined by $x=\pm \alpha$. And from the previous plot you can easily see that this is the case ($\alpha=1$ in the abovementioned Python code).

## 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

$$
\frac{dv_x}{dt}=-\frac{GM_{\odot}}{r^3}x,
$$

$$
\frac{dx}{dt}=v_x,
$$

$$
\frac{dv_y}{dt}=-\frac{GM_{\odot}}{r^3}y,
$$

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

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

The four coupled differential equations

$$
\frac{dv_x}{dt}=-\frac{GM_{\odot}}{r^3}x,
$$

$$
\frac{dx}{dt}=v_x,
$$

$$
\frac{dv_y}{dt}=-\frac{GM_{\odot}}{r^3}y,
$$

$$
\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$)

$$
v_{x,i+1}=v_{x,i}-h\frac{4\pi^2}{r_i^3}x_i,
$$

$$
x_{i+1}=x_i+hv_{x,i},
$$

$$
v_{y,i+1}=v_{y,i}-h\frac{4\pi^2}{r_i^3}y_i,
$$

$$
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.

```
# 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{_auto1} \tag{1}
\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{2}
\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{_auto2} \tag{3}
\end{equation}
$$

and

$$
\begin{equation}
v_{n+1}=v_{n}+(\Delta t) a_{n}+O(\Delta t^2).
\label{_auto3} \tag{4}
\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.

## Hints for exercises 2 and 3

**Taylor exercise 3.11:**

This exercise is discussed in Taylor's chapter 3.2.

Consider the rocket of mass $M$ moving with velocity $v$. After a
brief instant, the velocity of the rocket is $v+\Delta v$ and the mass
is $M-\Delta M$. Momentum conservation gives

$$
\begin{eqnarray*}
Mv&=&(M-\Delta M)(v+\Delta v)+\Delta M(v-v_e)\\
0&=&-\Delta Mv+M\Delta v+\Delta M(v-v_e),\\
0&=&M\Delta v-\Delta Mv_e.
\end{eqnarray*}
$$

## Exercise 2

In the second step we ignored the term $\Delta M\Delta v$ since we
can assume it is small. The last equation gives

$$
\begin{eqnarray}
\Delta v&=&\frac{v_e}{M}\Delta M,\\
\nonumber
\frac{dv}{dt}dt&=&\frac{v_e}{M}dM.
\end{eqnarray}
$$

Here we let $\Delta v\rightarrow dv$ and $\Delta M\rightarrow dM$.
We have also assumed that $M(t) = M_0-kt$.
Integrating the expression with lower limits $v_0=0$ and $M_0$, one finds

$$
\begin{eqnarray*}
v&=&v_e\int_{M_0}^M \frac{dM'}{M'}\\
v&=&v_e\ln(M/M_0)\\
&=&v_e\ln[(M_0-k t)/M_0].
\end{eqnarray*}
$$

We have ignored gravity here. If we add gravity as the external force, we get when integrating an additional terms $-gt$, that is

$$
v(t)=v_e\ln[(M_0-k t)/M_0]-gt.
$$

## Exercise 3, more rockets

This is a continuation of the previous exercise and most of the relevant background material can be found in Taylor chapter 3.2.

Taking the velocity from the previous exercise and integrating over time we find the height

$$
y(t) = y(t_0=0)+\int_0^tv(t')dt'.
$$

You need to insert $v(t)$ from the previous exercise

To do the integral over time we recall that $M(t)=M_0-\Delta M t$. We assumed that $\Delta M=k$ is a constant.
We use that $M_0-M=kt$ and assume that mass decreases by a constant $k$ times time $t$.

## Some more manipulations which have to be done

We will need  to compute an integral which goes like

$$
\int_0^t \ln{M(t')}dt' = \int_0^t \ln{(M_0-kt')}dt'.
$$

and defining the variable $u=M_0-kt'$, with $du=-kdt'$ and the new limits $M_0$ when $t=0$ and $M_0-kt$ when time is equal to $t$, we have

$$
\int_0^t \ln{M(t')}dt' = \int_0^t \ln{(M_0-kt')}dt'=-\frac{1}{k}\int_{M_0}^{M_0-kt} \ln{(u)}du=-\frac{1}{k}\left[u\ln{(u)}-u\right]_{M_0}^{M_0-kt}.
$$


```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Philosopher', sans-serif;
    font-weight: 400;
    font-size: 2.2em;
    line-height: 100%;
    color: rgb(0, 80, 120);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Philosopher', serif;
    font-weight: 400;
    font-size: 1.9em;
    line-height: 100%;
    color: rgb(245,179,64);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Philosopher', serif;
    margin-top:12px;
    margin-bottom: 3px;
    font-style: italic;
    color: rgb(94,127,192);
}

.text_cell_render h4 {
    font-family: 'Philosopher', serif;
}

.text_cell_render h5 {
    font-family: 'Alegreya Sans', sans-serif;
    font-weight: 300;
    font-size: 16pt;
    color: grey;
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h6 {
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 10pt;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 100%;
}

</style>

```python
from sympy import init_printing, Matrix, symbols
init_printing()
```

# Solving $A\underline{x}=\underline{b}$

## Finding the complete solution to the set for in a linear system

In this section, we focus on the row view of matrices in order to solve systems of linear equations.  Our aim to to find possible solutions for $\underline{x}$.  Consider the example in (1), taken from the previous lecture.

$$\begin{align}&{x_1}+2{x}_{2}+2{x}_{3}+2{x}_{4}={b}_{1}\\&2{x}_{1}+4{x}_{2}+6{x}_{3}+8{x}_{4}={b}_{2}\\&3{x_1}+6{x}_{2}+8{x}_{3}+10{x}_{4}={b}_{3}\end{align}\tag{1}$$

The matrix of coefficients and vectors are shown in (2).

$$\begin{bmatrix}1&2&2&2\\2&4&6&8\\3&6&8&10\end{bmatrix}\begin{bmatrix}{x}_{1}\\{x}_{2}\\{x}_{3}\\{x}_{4}\end{bmatrix}=\begin{bmatrix}{b}_{1}\\{b}_{2}\\{b}_{3}\end{bmatrix}\tag{2}$$

Multiplying the matrix of coefficients and the column vector of unknowns is shown in (3), indication that we are adding scalar multiples of vectors.

$${x}_{1}\begin{bmatrix}1\\2\\3\end{bmatrix}+{x}_{2}\begin{bmatrix}2\\4\\6\end{bmatrix}+{x}_{3}\begin{bmatrix}2\\6\\8\end{bmatrix}+{x}_{4}\begin{bmatrix}2\\8\\10\end{bmatrix}=\begin{bmatrix}{b}_{1}\\{b}_{2}\\{b}_{3}\end{bmatrix}\tag{4}$$

Since the third row is the addition of ($1\times$) row one and ($1\times$) row two, solutions on the right-hand side must be of the form shown in (5).

$${b}_{3}={b}_{1}+{b}_{2}\tag{5}$$

The question now is: _What values can $\underline{x}$ possibly take?_

From what we have seen up until now, $A\underline{x}=\underline{b}$ is solvable (exactly) when $\underline{b}$ is in the column space of $A$, that is to say, it must be a linear combination of the columns.

We need to find two solutions, one called the _particular_ and one the _nullspace_ solution.

### The particular solution: $\underline{x}_\text{particular}$

A particular solution is one that solves a specific case for $\underline{b}$.  Consider the reduced row-echelon form of the matrix of coefficients, $A$.

```python
A.rref()
```

Note that columns $2$ and $4$ have zero values as their respective pivots.

To find $\underline{x}_\text{particular}$ or $\underline{x}_\text{p}$, we set all free variables to $0$ (in this example case that would be ${x_2}={x_4}=0$, since these are _columns without pivots_).  Then we solve $A\underline{x}=\underline{b}$ for the pivot variables.

Let's see that in action and calculate $\underline{x}_\text{p}$ for (6).

$$\underline{b} =\begin{bmatrix}1\\5\\6\end{bmatrix}\tag{6}$$

The equation (6) above is a proper solution as $1+5=6$ in accordance with (5).

Below, we create an augmented matrix (that is one with $\underline{b}$ included).

```python
A_augm = Matrix([[1, 2, 2, 2, 1], [2, 4, 6, 8, 5], [3, 6, 8, 10, 6]])
A_augm
```

We can reduce this to reduced row-echelon form using the `.rref()` method.

```python
A_augm.rref() # The video example is not solved to reduced row echelon form
```

So, we are left with a new matrix (called `A1` below) that omits columns $2$ and $4$.

```python
A1 = Matrix([[1, 0, -2] ,[0, 1, (3 / 2)] ,[0, 0, 0]]) # The video example is not solved to reduced row echelon form
A1
```

From the last row in the reduced row-echelon form, we note that we can set $x_4$ to any value.  We have already set is to $0$, though.  From the second last row, we can read that $1{x_3}+{x_4}=\frac{3}{2}$, or then, ${x_3}=\frac{3}{2}$.  Knowing that ${x_2}=0$, we read from the first row that $1{x_1}+2{x_2}+0{x_3}-2{x_4}={x_1}=-2$ as shown in (7).

$$\begin{align}&x_4=0\\&{x_3}=\frac{3}{2}\\&{x_1}=-2\end{align}\tag{7}$$

Thus, for $\underline{x}_\text{p}$ we have (8).

$$\underline{x}_\text{p}=\begin{bmatrix}-2\\0\\{\frac{3}{2}}\\0\end{bmatrix}\tag{8}$$

### The nullspace solution: $\underline{x}_\text{nullspace}$

Here we have $\underline{b}$ as in (9).  (We will simplify the notation to $\underline{x}_\text{n}$.)

$$\underline{b}=\begin{bmatrix}0\\0\\0\end{bmatrix}\tag{9}$$

Reduction to reduce row-echelon form and following the same principles as above leads us to two nullspace solutions that we can easily calculate using the `.nullspace()` method for the augmented matrix.

```python
A_augmented_null = Matrix([[1, 2, 2, 2, 0], [2, 4, 6, 8, 0], [3, 6, 8, 10, 0]])
A_augmented_null.rref()
```

```python
A.nullspace()
```

$$\begin{bmatrix}\left[\begin{matrix}-2\\1\\0\\0\end{matrix}\right], & \left[\begin{matrix}2\\0\\-2\\1\end{matrix}\right]\end{bmatrix}$$

Let's have a look at how we got here.  We begin with the last row of the reduced row-echelon form.  Since we are free to set a value for $x_4$ (the whole row consisting of zeros), we set it to $t$.  Solving back up through rows $2$ and $1$, we have (10) below.

$$\begin{align}&{x_4}=t\\&{x_3}+2t=0\\&{x_3}=-2t\\&{x_1}+2{x_2}-2t=0\\&{x_1}=2t-2{x_2}\end{align}\tag{10}$$

The simplest value to give $t$ is $0$ and with ${x_2}=1$, we have (11).

$$\begin{align}&{x_4}=0\\&{x_3}=0\\&{x_2}=1\\&{x_1}=-2\end{align}\tag{11}$$

If we set $t=1$ and ${x_2}=0$, we have (12), the other nullspace solution.

$$\begin{align}&{x_4}=1\\&{x_3}=-2\\&{x_2}=0\\&{x_1}=2\end{align}\tag{12}$$

Why did we alternate by giving the unknowns the values $0$ and $1$?  Well, these are the simplest values to give (other than $0$ and $0$, which will be trivial).

### The full set of solutions

We get the full set of solutions as $\underline{x}=\underline{x}_\text{p}+\underline{x}_\text{n}$.  This is shown to be so in (13).

$$\begin{align}&A{\underline{x}}_\text{p}=b\\&A{\underline{x}}_\text{n}=0\\{\therefore}\quad&{A}\left({\underline{x}}_\text{p}+{\underline{x}}_\text{n}\right)=\underline{b}\end{align}\tag{13}$$

Since we can have constant multiples of the nullspace vector, we have a final solution in (14).

$$ \overline { x } ={ \overline { x }  }_{ P }+{ \overline { x }  }_{ N }=\begin{bmatrix} -2 \\ 0 \\ \frac { 3 }{ 2 }  \\ 0 \end{bmatrix}+{ c }_{ 1 }\begin{bmatrix} -2 \\ 1 \\ 0 \\ 0 \end{bmatrix}+{ c }_{ 2 }\begin{bmatrix} -2 \\ 0 \\ -2 \\ 1 \end{bmatrix} \tag{14}$$

## Rank

We discussed rank in the previous lecture.  For any ${m}\times{n}$ matrix we have a rank (number of pivots).  We cannot have more pivots than rows, therefor, for a matrix $A$, $m\le\text{rank}\left(A\right)$ and $n\le\text{rank}\left(A\right)$.

### The case of full column rank for a matrix $A$, i.e. $\text{rank}\left(A\right)=n$

This implies that there are no free variables and the nullspace only has the zero vector (it is a subspace and MUST contain the zero vector.  Thus $\underline{x}=\underline{x}_\text{p}$ ONLY (if it exists).

Consider the example with $2$ columns below.  One column is not a linear combination of the other and the rank will be $2$.  With only 2 unknowns and a rank of $2$, the nullspace will contain only the zero vector, $\underline{0}$.

```python
A = Matrix([[1, 3], [2, 1], [6, 1], [5, 1]])
A
```

```python
A.nullspace()
```

```python
A.rref() # Just to show the reduced row echelon form
```

### The case of full row rank, i.e. $\text{rank}\left(A\right)=m$

Here, every row has a pivot.  For which $\underline{b}$ will the the set be solvable?  For ALL $\underline{b}$.  How many free variables?  We are left with $n-\text{rank}\left(A\right)$ or $n-m$ free variables.

```python
A2 = A.transpose() # Creating a new matrix, which is the transpose of A above (just as an example)
A2
```

```python
A2.nullspace() # Showing the two nullspace solutions
```

```python
A2.rref()
```

### The case of full (row and column) rank, i.e. $\text{rank}\left(A\right)=m=n$

Let's do an example.

```python
A = Matrix([[1, 2], [3, 1]])
A
```

```python
A.nullspace() # Only the zero vector
```

```python
A.rref()
```

This is by definition an invertible matrix (which we will learn about later).

Let's look at an non-invertible matrix.  For this $2\times2$ matrix we will have one free variable (no pivot).

```python
A = Matrix([[2, 4], [3, 6]])
A
```

```python
A.nullspace()
```

```python
A.rref()
```

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_8_Solving_nonhomogeneous_systems.ipynb