# Things to cover (this slide will be removed)

1. Introduction/Flashy examples of what you can do
    1. Show off some of the cooler notebooks/lessons?
    2. Woo the audience.

2. Accessing Content

    1. logging in/github?
    2. requesting new content?
    3. how it's organized

3. Doin' stuff with the content
    1. how to run stuff, what you can do
    2. changing it if you want
    3. interactive activity of making things happen?
    4. examples where no coding is required?
4. Basic trouble shooting.
    1. go over some error messages and how to fix them?
    2. reload - kill kernel - run all

5. nbgitpuller for automation
    1. how to set up automated assignments and grading

6. How to use this in a classroom

** NEED MORE IMAGES SO THE DEMO IS MORE EXCITING **

# Callysto Introductory Workshop

# Today's Agenda
- Introduction to Callysto
- What is computational thinking?
- How Callysto incorporates computational thinking into current curriculum objectives
- Hands on demonstrations of classroom-ready pre-made notebooks
    - And how to modify them to your classroom specific needs!
    - Break in the middle
- Future Plans
    - How to request new content
    - Developing your own content
- Anonymous feedback form

# Introduction to Callysto
- Callysto is a cost-free learning program for grades 5-12 using Jupyter notebooks
- Teaches traditional curriculum goals through the use of computational thinking
    - Incorporates real world data in many cases
    - Demonstrates problem solving skills?
- Completely free for classroom use
- Only requires a modern web browser - you do not need to install anything on your local machine
- Funded by the federal government through the CanCODE initiative.

# [CanCODE](https://www.canada.ca/en/innovation-science-economic-development/programs/science-technology-partnerships/cancode.html?utm_campaign=not-applicable&utm_source=cancode_vanity-url&utm_medium=vanity-url): Preparing Canada's Youth For a Digital Future

- In order to prepare Canada's youth for an ever more competitive job market which requires digital skills, the federal government is supplying funding via the CanCODE initiative.
- This project aims to teach Canadian students more than just how to use technoligy - **it aims to introduce Canadian students of all ages to the idea of 'Computational thinking'**
- Does not necessarily mean learning how to code.

## Computational Thinking

>"Simply put, [computational thinking] is “thinking (or problem solving) like a computer scientist.” It is the thought processes involved in understanding a problem and expressing its solutions in such a way that a computer can potentially carry out the solution. [computational thinking] is fundamentally about using analytic and algorithmic concepts and strategies most closely related to computer science to formulate, analyze and solve problems."
>
>-[Shuchi Grover](https://www.edsurge.com/news/2018-02-25-the-5th-c-of-21st-century-skills-try-computational-thinking-not-coding)

## Computational thinking is not:

- Writing code.
- Using computers to automate and solve problems.
- Only applicable to math and science.

## Computational thinking is:
- A problem solving methodology.
- Thinking algorithmically.
- Problem abstraction and modeling.
- Problem modularization.
- Finding solutions to abstract and complicated problems.

## Computational thinking can:
- Be embedded in all subjects and assist in curriculum outcomes.
    - ** EXAMPLES OF THE ABOVE **
- Be learned and taught without writing a single line of code.
    - Of course, there is no reason why you couldn't write code.
- Help teach course content, provide "real world" examples, and the thought processes that go into that analysis.

# Callysto and Jupyter Notebooks
- Will allow students to experiment and explore concepts easily and quickly.
- Will allow students to tie in real-time data with their current learning objectives.
- Will enable students to ask questions and explore problems in ways that traditional pen and paper demonstrations cannot

# Embedding Computational Thinking in Current Objectives With Callysto
- Our notebooks use concepts of computer science to teach and demonstrate standard curriculum goals.
- No programming required to understand many of our notebooks.
- You will not need to create new lessons which incorporate this for you - simply ask our development team to develop content, or even create your own if you're truly interested.

# First Demo
- Link to demo
- Instructions
    - To run the notebook, from the top menu simply click `Cell` $\rightarrow$ `Run All`
    - Looking at any code is not necessary, simply click the `Hide Code` button at the top
    to view or hide code.
    - Take sometime to familiarize yourself with the notebook.

- Demo specific instructions

# Tutorial on Editing a Notebook
** For this one I was thinking we'd have the teachers do this directly in a notebook/new notebook and we could have them create all these things themselves while we run a demo at the front of the room ***
- If you like the content of a notebook, but you want to add/remove some detail or custom questions, you're free to do that directly in the Notebook itself. And the process is no more complicated than Microsoft Word!
- By double clicking on a markdown, or text cell, you will enter edit mode.
    - Simply begin typing your additional information, then press `Ctrl + Enter`
    - You can add titles
    - You can create lists
    - You can create links
    - You can use block quotes
    - You can create tables
    - You can write equations
    - You can embed images

- If you want to create a new cell, from the top menu simply click the '**+**'. This will create a new cell below your currently selected one. Then, from the dropdown menu which defaults to `Code` click this menu, and select `Markdown`. You have now created a new cell for writing text

- [A link to a list of markdown options](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet)

# Second Demo
- Link to demo
- Demo specific instructions

# 15 Minute Break
- Grab a coffee and feel free to ask us any questions you may have.

# Third Demo
- Link to demo
- Demo specific instructions

# Final Demo
- Link to demo
- Demo specific instructions

# Distributing and Collecting Student Notebooks
 - There are a few potential ways you can distribute and collect notebooks from students
    - Email them a link to the notebook as we've done here
    - To collect assignments, do so using `nbgrader`, a notebook add-on that will allow you to collect assignments quickly, and even automate grading. (DO WE WANT TO INCLUDE THIS IN THE DEMO)

# Accessing More Ready Made Content
1. Go to [hub.callysto.ca](https://hub.callysto.ca/)
2. Log in with an existing Google account.
3. Download content using nbgitpuller **HOW ARE WE HAVING THE TEACHERS/STUDENTS DOWNLOAD THE REPOSITORY???**
4. Celebrate

- All this content is hosted on the Rapid Access Cloud (RAC) and can be used directly in your browser.
- There is no need to install any packages or files to your, or your student's computers.
- Tour of the Hub

# Current Alberta Curriculum Outcomes
- The current Alberta curriculum competency requirements are

# Conclusion
- With Callysto, computational thinking is naturally incorporated into the existing curriculum outcomes

- I note that this slide will be our conclusion. If everything works out well, the workshop will leave the teachers with the impression we want: after they've tested out sample notebooks they'll realize that if they use Callysto they'll be meeting their new competency goal of Computational thinking in parallel with their other objectives and curriculum outcomes.

# Requesting Custom Content
- Demos shown today/content online not right for you? Ask us to create some content that will work well in your classroom
- Contact us at callysto@cybera.ca ** IS THIS THE CORRECT EMAIL?**
    - Tell us what you would like to see in a notebook that you would use in the classroom
- Work with our developers to create some content

# Creating your own content
- If you know Python, you can create and distribute your own content right on the hub already.
- If you would like to learn python/how to use the hub attend our next workshop at TIME AND LOCATION IF WE KNOW IT

# Questions?

# Please take a moment to fill out a short survey about today's workshop

- [Link to survey](https://docs.google.com/forms/d/e/1FAIpQLSfg7K3CxYeUf8_9hbWnVcd77rqZZPT-5uIe_1JbLJscpP9AmQ/viewform?usp=sf_link)

# Using Ready Made Content
1. Once you have the files, you can browse them from the Jupyter hub as you would on your computer.
- Open DEMONSTRATION NOTEBOOK
- In the top menu, click `Cell` $\rightarrow$ `Run All`
- Walk through of notebook
- Note Show/Hide code button
- Demo of FIRST DEMONSTRATION

# Basic Jupyter/Python ( I don't know if this is part of the presentation)
I suggest having a "Do this section" in the presentation, but making a separate notebook for the teachers to use themselves to keep them engaged/learn.

# Basic Markdown Styles

Markdown is quite versatile and supports titles, italic and bold fonts, itemized lists, hyperlinks, images, tables, etc... In fact, every non-code cell in this presentation was created using Markdown! A few of the basic markdown commands are

1. Titles
    - Title text is rendered with a pound sign (`#`) at the beginning of a line, followed by a space and the text you wish to turn into a title/header. For example, `# This is a title` would create a title at any location within your cell. and
    - Adding additional pound signs (example: `### Smaller title or heading`) creates different headings.
- Italics
    - _Italics_ are done by surrounding a word with underscores (`_`). For example `_this would be italics_` would render in italics.
- Bold
    - **Bold** lettering is done with double asterisks (`**`). For example, `**bold choice**` would render as bold text.

- Lists
    - Lists can be generated simply by starting a new line and typing `1.` will start a list. Subsequent items can simply be started with a dash `-`, and the markdown will take care of the numbering for you.
    - You can also make sub-items by starting a new line within your list, and pressing `Tab`, then either using dashes for points, or starting with `1.` or `A.` for sublist items.

- Raw text
    - Raw text, or text which ignores any of the style commands can be rendered using an open comma ( \`). For example \` sentence \` would render as `sentence`, and would escape any and all other markdown commands.

- Math
    - Markdown also supports Latex style equations. For a variable x, you can render it in-line by surrounding it like so: `$x$` which renders as $x$. if you want to render an equation proper, you can do it either like
    ```$$ f(x) = x^2 $$``` or
    ```
\begin{equation}
f(x) = x^2
\end{equation}
       ```
    which both render as
    \begin{equation}
    f(x) = x^2
    \end{equation}

# Demo Number Two
1. Run stuff
- Change stuff
- Break stuff?
- get comfortable with it

```python
# Anything after a pound sign is a comment -- it is not run as code

# The most basic command is the classic "hello world" command, which displays the statement "hello world"
# on the screen. In Python with a Jupyter note book, this is done by typing:

# print("Hello world!")

# then pressing Ctrl + Enter in the specified cell. Try deleting the pound sign in the line
# above and running this cell. Did you get an "IndentationError"? If so, this is because leading
# whitespace in Python is used as a "boundary" so to speak (more on this in a few minutes!). For
# now, simply move the print statement to the left edge of the cell and try again.

```

```python
# You can also do basic mathematical operations

print(1 + 1)
print(10 * 6)
print(2 / 4)
print(10 - 9)
# You can also apply certain manipulations to characters,
# which are refered to as strings in computer science talk

print("a" + "b")
print("a" * 10)

```

```python
# you can also give things names, known as "variables". Here 'a' and 'b' are now being
# defined as variables, and we're assigning them the values 10 and 20 respectively via the
# '=' operation.
a = 10
b = 20
print(a + b)
```

```python
# variables are shared between cells as well, where we've used the ',' in the print
# statment in order to display both variables on the command line.
print(a,b)
```

```python
# You can also do things multiple times using a "for" loop
for i in range(10):
    print(i)

# where you'll notice that counting starts at 0 and we ignore the number 10. Here we've assigned each value to
# the variable i above. Notice how it counts and we need a colon. What happens if you remove the colon?
# Also notice how our print statement is indented under the for loop. This is because python sees everything
# indented one level as "inside the loop" -- anything indented like the print statement will be executed
# every time the program goes through the loop. This is why white space is so important in Python

```

```python
# You can also test the "truth" of something with an if statement, which does something if something
# is true

for i in range(10):
    if i == 5:
        print(i == 5,i)
    else:
        print(i == 5,i)

# notice the next level of indentaion for the if statement as well Also notice the double equals. Also notice
# how it prints True

```

```python
# A handy way to know what something does is type its name followed by a question mark
print?

```

[](https://github.com/callysto/curriculum-notebooks/blob/master/LICENSE.md)


```python
import glob
import os

import pathlib
import shutil

import re
```

```python
def change_suffix(file_name, from_suffix, to_suffix):
    # ファイルの拡張子を得る
    sf = pathlib.PurePath(file_name).suffix

    # 変更対象かどうか判定する
    if sf == from_suffix:
        # ファイル名(拡張子除く)を得る
        st = pathlib.PurePath(file_name).stem

        # 変更後のファイル名を得る
        to_name = st + to_suffix

        # ファイル名を変更する
        shutil.copyfile(file_name, to_name)

    return to_name
```

```python
change_suffix('sample.abc', '.abc', '.xyz')

file_name = glob.glob('*.tex')[0]
file_name = change_suffix(file_name, '.tex', '.txt')
```

```python
def tex_trans(data_lines):

    # data_lines = data_lines.replace('documentclass', 'bocumentclass')

    # \begin{document} 以下を抽出
    data_lines = re.search(r'\\begin{document}[\s\S]*\\end{document}', data_lines).group()
    # data_lines = data_lines[data_lines.find(r'\begin{document}')+16:]

    # data_lines = data_lines.replace('\n', '')

    # 余計な空白を一つに変換
    data_lines = re.sub('[ ]+', ' ', data_lines)
    # 複数改行を一つの改行に変換
    data_lines = re.sub('[\n]+', '\n', data_lines)
    # 改行後の空白を削除
    data_lines = re.sub('\n'+'[ ]+', '\n', data_lines)

    # 方程式の変換
    equations = re.findall(r'\\begin{equation}[\s\S]*?\\end{equation}', data_lines)
    for equation in equations:
        data_lines = data_lines.replace(equation, 'EQUATION')
    aligns = re.findall(r'\\begin{align}[\s\S]*?\\end{align}', data_lines)
    equations = re.findall(r'\\begin{equation\*}[\s\S]*?\\end{equation\*}', data_lines)
    for equation in equations:
        data_lines = data_lines.replace(equation, 'EQUATION')
    for align in aligns:
        data_lines = data_lines.replace(eq, 'EQUATION')
    commentouts = re.findall(r'%.*\n', data_lines)
    for com in commentouts:
        data_lines = data_lines.replace(com, '')
    functions = re.findall(r'(\n\\[\s\S]*?)(?=\n)', data_lines) #
    # functions = re.findall(r'(\n\\[\s\S]*?)\n', data_lines) # これだと重複は持ってこないことに注意
    for func in functions:
        data_lines = data_lines.replace(func, '')
    eqs = re.findall(r'\$[\s\S]*?\$', data_lines)
    for eq in eqs:
        data_lines = data_lines.replace(eq, 'EQ')

    data_lines = data_lines.lstrip(r'\begin{document}')
    data_lines = data_lines.rstrip(r'\end{document}')

    # data_lines = data_lines.replace(re.search(r'\\begin{equation}[\s\S]*\\end{equation}', data_lines).group(), 'EQUATION')

    # 文中の改行を削除
    data_lines = re.sub(r'\n(?=[a-z])', ' ', data_lines)
    data_lines = re.sub(r'\n(?=[0123456789])', ' ', data_lines)
    data_lines = re.sub('[ ]+', ' ', data_lines)

    return data_lines
```

```python
with open(file_name, encoding='utf-8') as f:
    data_lines = f.read()

data_lines = tex_trans(data_lines)

# 同じファイル名で保存
new_file_name = 'output.txt'
with open(new_file_name, mode='w', encoding='utf-8') as f:
    f.write(data_lines)
```

```python

```


# Resolução: Processo trainee Equipe ZebraAerodesign - Desempenho

# Orientações básicas

- GitHub da equipe zebra: https://github.com/Zebra-Aerodesign

## Baixando e instalando anaconda navigator

1. Tutorial para baixar anaconda navigator https://www.youtube.com/watch?v=_eK0z5QbpKA
    1. Link baixar o anaconda navigator https://www.anaconda.com/

## Configurando o git e o github

1. Baixe o git em https://git-scm.com/
2. Crie uma conta no Github em https://github.com
3. Baixe o Github desktop em https://github.com
4. Dúvidas sobre Github assista a playlist do prof. Gustavo Guanabara
https://shorturl.at/rNSZ2

## Configurando a biblioteca zebralib

1. https://github.com/Zebra-Aerodesign/ZebraLib
2. Clone o repositório do Github no seu computador em C:\Users\\....\Anaconda3
3. Vá em C:\Users\arthu\Anaconda3\Lib\site-packages
4. Crie um arquivo no bloco de notas chamado ZebraLib.pth e salve na pasta site-packages
5. Dentro do arquivo coloque o local onde está a biblioteca exemplo: C:\Users\arthu\Anaconda3\ZebraLib
6. Sucesso! Agora é só importar a biblioteca no seu código e se divertir ... :)

# Entender mais sobre programação básica e avançada

## Básica - Canal curso em video prof. gustavo guanabara link das playlist

1. Curso de algorimos e lógica de programação: https://youtu.be/8mei6uVttho
2. Programação em python mundo 1: https://youtu.be/S9uPNppGsGo
3. Programação em python mundo 2: https://youtu.be/nJkVHusJp6E
4. Programação em python mundo 3: https://youtube.com/playlist?list=PLHz_AreHm4dksnH2jVTIVNviIMBVYyFnH
5. Programação em python exercícios: https://youtube.com/playlist?list=PLHz_AreHm4dm6wYOIW20Nyg12TAjmMGT-
6. Conceitos de Prog. orientada a objeto (assistir aulas em vermelho): https://youtu.be/KlIL63MeyMY

## Avançada - link das playlist

1. Jamal UFTPR - Cálculo numérico: https://www.youtube.com/watch?v=Ow9Hx04qnaI&list=PLleETO-tMJixSTImpC54tjToBzw38NDLr
2. APMonitor - Computação para engenheiros: https://www.youtube.com/watch?v=gY0sEokk2h0&list=PLLBUgWXdTBDi-E--rwBujaNkTejLNI6ap
3. APMonitor site - Tutoriais e exercícios: http://apmonitor.com/che263/index.php/Main/PythonIntroduction

## Onde encontrar ajuda com exemplos

1. Aprender sobre *numpy*: https://numpy.org/doc/stable/reference/routines.html
2. Aprender a plotar gráfico com *matplotlib*: https://matplotlib.org/stable/gallery/index.html
3. Aprender computação ciêntifica com *scipy*: https://docs.scipy.org/doc/scipy/reference/index.html

# Glossário

1. **$b$** :                       Envergadura [$m$]
2. **$S$** :                       Área da asa [$m^2$]
3. **$Af$** :                      Afilamento
15. **$AR$** :                     Alongamento ou aspect ratio (AR) na asa retangular
4. **$C_L$** :                     Coeficiente de sustentação qualquer
4. **$CL_n$** :                    Coeficiente de sustentação com ângulo de incidência ou natural
5. **$C_{Lmax}$** :                Coeficiente de sustentação máximo da aeronave, Cl de estol
5. **$C_{LLO}$** :                 Coeficiente de sustentação para garantir a decolagem com o menor comprimento de pista possível
20. **$C_{D0}$** :                 Coeficiente de arrasto parasita (coef zero-lift)
20. **$C_{Di}$** :                 Coeficiente de arrasto induzido (coef due-lift) --> $K.C_{Lmax}^2$
21. **$C_D$** :                    Coeficiente de arrasto total --> $C_{D} = C_{D0} + K.C_{Lmax}^2$
6. **$S_{wet}$** :                 Área molhada do avião [$m^2$]
7. **$c$** :                       Corda média [$m$]
8. **$N_{max}$** :                 Fator de carga máximo estrutural do Diagrama Vn != turn_Nmax
8. **$\phi$** :                    Fator de efeito solo
8. **$\rho$** :                    Densidade do ar local [$kg/m^3$]
8. **$D$** :                       Força de arrasto [$N$]
8. **$L$** :                       Força de sustentação [$N$]
8. **$T$** ou **$T_d$** :          Tração disponível [$N$]
8. **$T_r$**  :                    Tração requerida [$N$]
9. **$h$** :                       Altura da asa em relação ao chão [$m$]
9. **$\mu$** :                     Coefiente de atrito com o solo
10. **$mv$** :                     Massa vazia da aeroave [$Kg$]
11. **$a_o$** :                    Ângulo de incidência da asa [Graus]
12. **$a_{max}$** :                Ângulo maximo da asa (ângulo de estol) [Graus]
16. **$e_{0}$** :                  Fator eficiênte de Oswald
17. **$K$** :                      Constante de proporcionalidade
18. **$MTOW$** :                   Massa total de decolagem (Maximum Take-Off Weight)[$N$]
18. **$M$** :                      Massa total da aeronave [$kg$]
19. **$W$** :                      Peso total do avião     [$N$]
22. **$V_{stall}$** :              Velocidade mínima em que a força de sustentação consegue equilibrar o peso da aeronave
23. **$V_{lo}$** :                 Velocidade de decolagem (lift-off) [ 20% maior que $V_{stall}$ ]
24. **$V_{l}$** :                  Velocidade de aproximação/pouso (landing) [ 30% maior que $V_{stall}$ ]
24. **$S_{lo}$** :                 Distância de pista necessária para a decolagem [$m$]
24. **$S_{l}$** :                  Distância de pista necessária para o pouso [$m$]

## Decolagem

Perfil da decolagem. O objetivo é calcular a **distância** de decolagem até a velocidade **$V_{lo}$** o ultimo instânte onde há contato com o solo.

Diagrama de corpo livre do avião durante a decolagem. Onde $V_{R}$ é a velocidade com o vento relativo, $L$ força de sustentação, $D$ força de arrasto ou tração requerida, $T$ tração disponível, $F_{at}$ força de atrito com o solo, $N$ normal (relação W-L) e $W$ peso total da aeronave.

Efeito solo

\begin{equation}
    \phi = \frac{(16h/b)^2}{1 +(16h/b)^2}
\end{equation}

Coeficientes aerodinâmicos

\begin{equation}
    C_D = (C_{D0} + \phi. K C_{L}^2 )
\end{equation}

\begin{equation}
    C_{LLO} = \frac{\pi.e_{0}.AR.\mu}{2 \phi}
\end{equation}

Velocidades importantes
\begin{equation}
    V_{estol} = \sqrt{\frac{2.W}{\rho . S. C_{L_{max}}}}
\end{equation}

\begin{equation}
    V_{lo} = 1.2 V_{estol}
\end{equation}

```python

```

Forças aerodinâmicas
\begin{equation}
    D = \frac{1}{2}. \rho. v^2. S. (C_{D0} + \phi. K C_{L}^2 )
\end{equation}

\begin{equation}
    L = \frac{1}{2}. \rho. v^2. S. C_{L}
\end{equation}

Aproximação pra distância de decolagem
\begin{equation}
    S_{lo} = \frac{1.44W^2}{g S \rho C_{Lmax} [ T-[D + \mu(W-L)] ] }_{0.7V_{lo}}
\end{equation}

```python

```

### Desafio 1
Determine o $AR$, alongamento da asa, **com programação orientada a objeto**.

\begin{equation}
    AR = \frac{b^2}{S}
\end{equation}

Dados: $b = 2.48m$ e $S = 0.90m^2$

*Algoritmo para programação*
1. Crie uma classe 'avião'.
2. Crie o construtor da classe que receba 2 parametros $(S, b)$.
3. $AR$ é um atributo da classe e deve ser calculado no construtor.

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

#Classe
class avião:
    def __init__(self, S, b): #Construtor da classe
        self.S = S
        self.b = b     #Atributos
        self.AR = (self.b**2)/self.S

a = avião(0.9, 2.48)
a.S, a.b, a.AR
```

### Desafio 2
Construa um método pra calcular a velocidade de estol **utilizando a classe avião**.

\begin{equation}
    V_{estol} = \sqrt{\frac{2.W}{\rho . S. C_{L_{max}}}}
\end{equation}

Dados: $\rho = 1.225kg/m^3, S = 0.90m^2, C_{Lmax} = 1,65, W=150N$

*Algoritmo para programação*
1. Crie uma classe 'avião'.
2. Crie o construtor da classe que receba 3 parametros $(S, b, C_{L_{max}})$.
3. $AR$ é um atributo da classe e deve ser calculado no construtor.
4. Implementar um método pra calcular a $V_{stol}$ tendo como argumento do método $W e \rho$

```python
#Classe
class avião:
    def __init__(self, S, b, CLmax): #Construtor da classe
        self.S = S
        self.b = b          #Atributos da classe
        self.CLmax = CLmax

        self.AR = (self.b**2)/self.S

    #Método da classe
    def v_Stol(self, W, rho): # Argumentos do método (W e rho)
        V_stol = (2*W/(rho*self.S*self.CLmax))**0.5
        return V_stol

a = avião(0.9, 2.48, 1.65)
a.v_Stol(150, 1.225)
```

### Desafio 3
Utilize a classe do desafio 2 para plotar um gráfico um gráfico de velocidade de stol por densidade do ar ( $V_{stol}$ x $\rho$). Plote as curvas para 3  pesos diferentes.

*Dados*: $W_1 = 85N, W_2= 100N, W_3=150N$

```python
#Instanciar o objeto
a = avião(0.9, 2.48, 1.65)

#Criar um vetor de densidades
densidade = np.linspace(0.6, 1.225)

#Aplicar o método V_stol(150, rho) para gerar o vetor de velocidades
velocidade_85N = a.v_Stol(85, densidade)
velocidade_100N = a.v_Stol(100, densidade)
velocidade_150N = a.v_Stol(150, densidade)

#Plotagem
fig, ax = plt.subplots() #criando janela
ax.set(title=r'$V_{stol}$ x $\rho$', xlabel=r'Densidade do ar [$kg/m^3$]', ylabel='$V_{stol}$ [$m/s$]') #nomes dos eixos e titulo

ax.plot(densidade, velocidade_85N, label=r'$V_{stol}$ pra W=85N') #plotagem do dados
ax.plot(densidade, velocidade_100N, label=r'$V_{stol}$ pra W=100N') #plotagem do dados
ax.plot(densidade, velocidade_150N, label=r'$V_{stol}$ pra W=150N') #plotagem do dados

ax.legend() #legenda
ax.grid(linestyle='dotted') #grid do fundo
```

### Desafio 4

Implemente 3 novos métodos dentro da classe avião. Um para calcular a força de sustentação $L$, outro o arrasto $D$ e para calcular o efeito solo $\phi$.

\begin{equation}
    L = \frac{1}{2}. \rho. v^2. S. C_{L}
\end{equation}

\begin{equation}
    D = \frac{1}{2}. \rho. v^2. S. (C_{D0} + \phi. K C_{L}^2 )
\end{equation}

\begin{equation}
    \phi = \frac{(16h/b)^2}{1 +(16h/b)^2}
\end{equation}

Calcule o efeito solo  $\phi$, força de sustentação $L$ e força de arrasto $D$. Para velocidade de 15$m/s$, $C_L = 1.5$ e $\rho = 1.225kg/m^3$.

*Dados:* $b=2.48, S = 0.90m^2, C_{Lmax} = 1.65, C_{D0}=0.12, h=0.35m$

*Algoritmo para programação*
1. Adicione 2 novos atributos no construtor $C_{D0}$ e $h$.
2. Já será calcula pra vc a constante $K$ no construtor.
3. Implemente: **efeito_Solo(self)** é um método que calcula ($\phi$) e busca a altura $h$ como atributo do avião (self.h).
4. Implemente: **sustentação(self, V, CL, rho)** é um método que calcula ($L$) com 3 argumentos de entrada. Velocidade, Cl qualquer, densidade do ar.
4. Implemente: **arrasto(self, V, CL, rho)** é um método que calcula ($D$) com 3 argumentos de entrada. Velocidade, Cl qualquer, densidade do ar.

```python
#Classe
class avião:
    def __init__(self, S, b, CLmax, C_D0, h): #Construtor da classe
        self.S = S
        self.b = b          #Atributos da classe
        self.CLmax = CLmax
        self. C_D0 = C_D0
        self.h = h

        self.AR = (self.b**2)/self.S
        self.K = 1/(3.14*0.75*self.AR)

    #Método da classe
    def v_Stol(self, W, rho): # Argumentos do método (W e rho)
        V_stol = (2*W/(rho*self.S*self.CLmax))**0.5
        return V_stol

    def efeito_Solo(self):
        zw = 16 * self.h /self.b
        phi = (zw ** 2) / (1 + zw ** 2)
        return phi

    def sustentação(self, V, CL, rho):
        L = 0.5 * (V ** 2) * rho * self.S * CL
        return L

    def arrasto(self, V, CL, rho):
        D = (0.5* (V ** 2)* rho* self.S* (self.C_D0 + self.efeito_Solo() * self.K * CL ** 2))
        return D
```

```python
avião_1 = avião(0.9, 2.48, 1.65, 0.12, 0.35)
avião_1.arrasto(15, 1.5, 1.225), avião_1.sustentação(15, 1.5, 1.225), avião_1.efeito_Solo()
```

### Desafio 5
*(Exemplo 4.9 - Pag 214. Miranda)* Determine o $C_{L}$ ideal para se garantir
uma decolagem com o menor comprimento de pista possível, conhecido como ($C_{LLO}$).

\begin{equation}
    C_{LLO} = \frac{\pi.e_{0}.AR.\mu}{2 \phi}
\end{equation}

Dados: $b = 2.48m$, $S = 0.90m^2$, $e_{0}$ = 0.717,  $\mu = 0,030$ e $C_{D} = 0.022$

*Algoritmo de solução*

*Algoritmo para programação*
1. Implemente: **CLL0_Ideal(self, mi)** é um método que calcula o ideal para se garantir a decolagem com o menor comprimento de pista possível.

```python
#Classe
class avião:
    def __init__(self, S, b, CLmax, C_D0, h): #Construtor da classe
        self.S = S
        self.b = b          #Atributos da classe
        self.CLmax = CLmax
        self. C_D0 = C_D0
        self.h = h

        self.AR = (self.b**2)/self.S
        self.K = 1/(3.14*0.75*self.AR)

    #Método da classe
    def v_Stol(self, W, rho): # Argumentos do método (W e rho)
        V_stol = (2*W/(rho*self.S*self.CLmax))**0.5
        return V_stol

    def efeito_Solo(self):
        zw = 16 * self.h /self.b
        phi = (zw ** 2) / (1 + zw ** 2)
        return phi

    def sustentação(self, V, CL, rho):
        L = 0.5 * (V ** 2) * rho * self.S * CL
        return L

    def arrasto(self, V, CL, rho):
        D = (0.5* (V ** 2)* rho* self.S* (self.C_D0 + self.efeito_Solo() * self.K * CL ** 2))
        return D

    def CLL0_Ideal(self, mi):
        cll0 = np.pi*0.717*self.AR*mi/(2*self.efeito_Solo())
        return cll0
```

```python
avião_1 = avião(0.9, 2.48, 1.65, 0.12, 0.35)
avião_1.CLL0_Ideal(0.03)
```

### Desafio 6
*(Exemplo 4.10 - Pag 217. Miranda)* Determine o comprimento de pista $ S_{lo}$ necessário para a decolagem com um peso total de $150N$ em uma pista localizada ao nível do mar $\rho$ =
1,225$kg/m^3$.

\begin{equation}
    S_{lo} = \frac{1.44W^2}{g S \rho C_{Lmax} [ T-[D + \mu(W-L)] ] }_{0.7V_{lo}}
\end{equation}

*Dados:* $b=2.48, S = 0.90m^2, C_{Lmax} = 1.65, C_{D0}=0.12, h=0.35m, \mu=0.03$

*Algoritmo para programação*
1. Implemente: **distancia_decolagem(self, W, rho)** um método que calcula a distância de decolagem (Equação acima).

*Algoritmo de para implementar o método **distancia_decolagem(self, W, rho)***
1. Considere tração disponível $T=25N$
1. Encontre a velocidade de decolagem $V_{lo} = 1.2V_{estol}$
2. Calcule o $C_{LLO}$ para substituir em $L$ e $D$.
3. Calcule $L$ e $D$ na velocidade igual a $0.7V_{lo}$.
4. Por fim, calcule $S_{lo}$

```python
#Classe
class avião:
    def __init__(self, S, b, CLmax, C_D0, h): #Construtor da classe
        self.S = S
        self.b = b          #Atributos da classe
        self.CLmax = CLmax
        self. C_D0 = C_D0
        self.h = h

        self.AR = (self.b**2)/self.S
        self.K = 1/(3.14*0.75*self.AR)

    #Método da classe
    def v_Stol(self, W, rho): # Argumentos do método (W e rho)
        V_stol = (2*W/(rho*self.S*self.CLmax))**0.5
        return V_stol

    def efeito_Solo(self):
        zw = 16 * self.h /self.b
        phi = (zw ** 2) / (1 + zw ** 2)
        return phi

    def sustentação(self, V, CL, rho):
        L = 0.5 * (V ** 2) * rho * self.S * CL
        return L

    def arrasto(self, V, CL, rho):
        D = (0.5* (V ** 2)* rho* self.S* (self.C_D0 + self.efeito_Solo() * self.K * CL ** 2))
        return D

    def CLL0_Ideal(self, mi):
        cll0 = np.pi*0.717*self.AR*mi/(2*self.efeito_Solo())
        return cll0

    def distancia_decolagem(self, W, rho, mi):
        T = 33.207

        vlo = self.v_Stol(W, rho)*1.2
        CLLO = self.CLL0_Ideal(mi)

        L = self.sustentação(0.7*vlo, CLLO, rho)
        D = self.arrasto(0.7*vlo, CLLO, rho)

        slo = (1.44*(W**2))/(9.81*self.S*rho*self.CLmax*(T - (D + mi*(W - L))))
        return slo
```

```python
avião_1 = avião(0.9, 2.48, 1.65, 0.022, 0.35)
avião_1.distancia_decolagem(150, 1.225, 0.03)
```

## Manobra

```python

```


```python
import theme
theme.load_style()
```

# Higher Order Shape Functions

This lecture by Tim Fuller is licensed under the
Creative Commons Attribution 4.0 International License.  All code examples are also licensed under the [MIT license](http://opensource.org/licenses/MIT).

# Topics

- [Introduction](#intro)
- [Quadratic Shape Functions](#quad-sf)
  - [Direct Construction of Quadratic Shape Functions](#dir-quad-sf)
- [Direct Construction of Shape Functions in Natural Coordinates](#dir-nat-sf)
  - [Linear Shape Functions in Natural Coordinates](#nat-lin-sf)
  - [Quadratic Shape Functions in Natural Coordinates](#nat-quad-sf)
  - [Higher Order Shape Functions in Natural Coordinates](#nat-higher-sf)
- [Example](#example)

#  Introduction[](#top)

In previous lectures, we have approximated the solution to the governing equations with linear shape functions.  Higher order approximate solutions, often times, can lead to increased accuracy, especially when extracting derivative values from the solution (i.e. stress and strain).

#   Quadratic Shape Functions[](#top)

Approximate the dependent variable $u$ by a quadratic function

$$
U_N = a + b x + c x ^ 2
= \begin{pmatrix} 1 & x & x^2 \end{pmatrix}
  \begin{pmatrix} a \\ b \\ c \end{pmatrix}
= \boldsymbol{p}(x) \boldsymbol{\alpha}
$$

We want to express the approximate solution to $u$ in terms of nodal displacements and not the constant coefficients $a$, $b$, and $c$.  Thus, we force the approximate solution $u$ to be equal to nodal displacements $u_1$, $u_2$, and $u_3$ and $x_1$, $x_2$, and $x_3$, respectively, on some finite element.

$$
\begin{align}
  u_1 &= a + b x_1 + c x_1^2 \\
  u_2 &= a + b x_2 + c x_2^2 \\
  u_3 &= a + b x_3 + c x_3^2 \\
\end{align} \longrightarrow
\begin{pmatrix} u_1 \\ u_2 \\ u_3 \end{pmatrix} =
\begin{pmatrix}
1 & x_1 & x_1^2 \\ 1 & x_2 & x_2^2 \\ 1 & x_3 & x_3^2
\end{pmatrix}
\begin{pmatrix} a \\ b \\ c \end{pmatrix}
\longrightarrow \boldsymbol{d} = \boldsymbol{M} \boldsymbol{\alpha}
$$

Then,

$$
U_N = \boldsymbol{p} \boldsymbol{M}^{-1} \boldsymbol{d} =
\boldsymbol{N} \boldsymbol{d} = \sum_{i=1}^3 N_i(x)u_i
$$

where the shape functions $N_i$ are

$$
N_i = \frac{2}{h_e^2}
\begin{pmatrix}
  \left(x - x_2\right)\left(x - x_3\right) \\
  -2\left(x - x_1\right)\left(x - x_3\right) \\
  \left(x - x_1\right)\left(x - x_2\right) \\
\end{pmatrix}
$$

##  Direct Construction of Quadratic Shape Functions

The method previously described is applicable to shape functions of any order, but, as you can imagine, the process becomes extremely involved and difficult for elements of order higher than 3 or so.  Fortunately, using the properties of shape functions that

- the sum of all shape functions sums to 1 at all points (**partition of unity**)
- the i$^\mathrm{th}$ shape function has the Kronecker delta property and is the only non‐zero function in the series at node i, where it has a value of 1.

any order shape function can be constructed directly.  For example, from the properties of shape functions we construct the quadratic shape function $N_1$ as

$$
\begin{align}
  \text{General form of a quadratic product of monomials} &\quad N_1 = \frac{(x-a)(x-b)}{c} \\
  \text{Kronecker delta property} &\quad N_1 = \frac{(x-x_2)(x-x_3)}{c} \\
  N_x(x_1) = 1 &\quad N_1 = \frac{(x-x_2)(x-x_3)}{(x_1-x_2)(x_1-x_3)} \\
\end{align}
$$

#  Direct Construction of Shape Functions in Natural Coordinates[](#top)

As discussed in [Lesson 12](Lesson12_NumericalIntegration.ipynb), for integration purposes, it is convenient to express element shape functions in the natural coordinates.  It turns out that since the element shape functions are interpolating polynomials, they can be formulated in the natural coordinates by normalizing the appropriate Lagrange interpolation functions in terms of $\xi$.  For an element of $n$ nodes, the $n^{\text{th}}$ Lagrange interpolating functions are formed such that

$$
L_i^n = c_i\left(\xi-\xi_1\right)\left(\xi-\xi_2\right)
\cdots
\left(\xi-\xi_{i-1}\right)\left(\xi-\xi_{i+1}\right)
\cdots
\left(\xi-\xi_n\right)
$$

The $c_i$ are found by noting that

$$
L_i^n(\xi_j) = \begin{cases}
1 & i = j \\
0 & \text{otherwise}
\end{cases}
$$

Thus the $c_i$ are given by

$$
c_i = \frac{1}{
\left(\xi_i-\xi_1\right)\left(\xi_i-\xi_2\right)
\cdots
\left(\xi_i-\xi_{i-1}\right)\left(\xi_i-\xi_{i+1}\right)
\cdots
\left(\xi_i-\xi_n\right)}
$$

Then $L_i^n$ is

$$
L_i^n = \frac{\left(\xi-\xi_1\right)\left(\xi-\xi_2\right)
\cdots
\left(\xi-\xi_{i-1}\right)\left(\xi-\xi_{i+1}\right)
\cdots
\left(\xi-\xi_n\right)}{
\left(\xi_i-\xi_1\right)\left(\xi_i-\xi_2\right)
\cdots
\left(\xi_i-\xi_{i-1}\right)\left(\xi_i-\xi_{i+1}\right)
\cdots
\left(\xi_i-\xi_n\right)}
$$

Formed in this way, the $L_i^n$ are the element shape functions $\phi_i$, expressed in the natural coordinates $\xi$.

Interpolation functions of this form are said to belong to the Lagrange family of interpolation polynomials.  The finite element models based on Lagrange interpolation polynomials are said to belong to the Lagrange family of finite elements.  All of the elements encountered in this course, thus far, have been Lagrange finite elements.

## Linear Lagrange Shape Functions

For a linear linear two node element, the shape functions are found from the linear Lagrange interpolating functions:

$$
\phi_1(\xi) = L_1^1(\xi) = \frac{\xi - \xi_2}{\xi_1 - \xi_2} = \frac{\xi - 1}{(-1) - 1} = -\frac{\xi - 1}{2}
$$
$$
\phi_2(\xi) = L_2^1(\xi) = \frac{\xi - \xi_1}{\xi_2 - \xi_1} = \frac{\xi - (-1)}{1 - (-1)} = \frac{\xi +1}{2}
$$

Or

$$
\{\phi\}
= \frac{1}{2}
\begin{Bmatrix}
1 - \xi \\
1 + \xi
\end{Bmatrix}
$$

## Quadratic Lagrange Shape Functions

For a linear linear two node element, the shape functions are found from the quadratic Lagrange interpolating functions with $\xi_1=-1$, $\xi_2=1$, $\xi_3=0$:

$$
\begin{align}
\phi_1(\xi) = L_1^2(\xi)  &= \frac{\left(\xi-\xi_2\right)\left(\xi-\xi_3\right)}
                                  {\left(\xi_1-\xi_2\right)\left(\xi_1-\xi_3\right)}
                           = \frac{\xi\left(\xi-1\right)}{2} \\
\phi_2(\xi) = L_2^2(\xi)  &= \frac{\left(\xi-\xi_1\right)\left(\xi-\xi_3\right)}
                                  {\left(\xi_2-\xi_1\right)\left(\xi_2-\xi_3\right)}
                           = \frac{\xi\left(\xi+1\right)}{2} \\
\phi_3(\xi) = L_3^2(\xi)  &= \frac{\left(\xi-\xi_1\right)\left(\xi-\xi_2\right)}
                                  {\left(\xi_3-\xi_1\right)\left(\xi_3-\xi_2\right)}
                           = -\left(\xi+1\right)\left(\xi-1\right)
\end{align}
$$

$$
\phi_i = \frac{1}{2}
\begin{Bmatrix}
-\xi(1 - \xi) \\ \xi(1+\xi) \\ 2(1 + \xi)(1 - \xi)\\
\end{Bmatrix}
$$

### Isoparametric Mapping

Based on our choice of the $\xi_i$, the isoparametric mapping for the quadratic map looks like

Mathematically, the mapping from physical to natural coordinates is similar in structure to that of the linear element and is given by

$$
x = \sum_{i=1}^3 N_i(\xi) x_i \Longrightarrow
-\frac{1}{2}\xi\left(1-\xi\right)x_1 +
\frac{1}{2}\xi\left(1+\xi\right)x_2 +
\left(1-\xi^2\right)x_3
$$

And the Jacobian is given by

$$
\begin{align}
J &= \frac{dx}{d\xi} = \sum_{i=1}^{3} \frac{dN_i(\xi)}{d\xi}x_i \\
  &= \frac{1}{2}\left(2\xi - 1\right)x_1 +
     \frac{1}{2}\left(2\xi + 1\right)x_2 +
     2\xi x_3
\end{align}
$$

Note that the Jacobian is no longer constant and must be included in the integration of the force and stiffness matrices.

## Higher Order Lagrange Shape Functions

Higher order Lagrange shape functions are derived similarly to the linear and quadratic.

# Example[](#top)

```python

```


# A Network-based Multimodal Infrastructure for Accelerating Medicine Partnership in Parkinson’s Disease Data Repurposing
---

**[Feixiong Cheng, Ph.D. (Communicating PI)](https://www.lerner.ccf.org/gmi/cheng/)**
Assistant Professor of Molecular Medicine
Cleveland Clinic Lerner College of Medicine of Case Western Reserve University
Email: chengf@ccf.org

**[Ignacio Mata, Ph.D. (MPI)](https://www.lerner.ccf.org/gmi/mata/)**
Assistant Professor of Molecular Medicine
Cleveland Clinic Lerner College of Medicine of Case Western Reserve University
Email: matai@ccf.org

### Table of Contents
1. [Introduction](#a1)
2. [Code Base](#a2)
    1. [Framework - Genome/Transcriptome Data Reticulotyping](#a2_1)
    2. [Framework - Deep Learning-based Graph Embedding](#a2_2)
        1. [Random Surfing with Restart](#a2_2_1)
        2. [Positive Pointwise Mutual Information](#a2_2_2)
        3. [Stacked Denoising Autoencoder](#a2_2_3)
    3. [Framework - Network Proximity](#a2_3)
        1. [Human Protein Interactome](#a2_3_1)
        2. [Disease Gene Network Modularity](#a2_3_2)
        3. [Network Proximity](#a2_3_3)
        4. [Random Sampling and Z-Score](#a2_3_4)
        5. [Network Proximity-based Drug Screening](#a2_3_5)
3. [Example Workflow](#a3)
    1. [Example - Genome/Transcriptome Data Reticulotyping](#a3_1)
    3. [Example - Network Proximity](#a3_3)

## Introduction

Parkinson's disease (PD) is a complex neurological disorder with multiple pathophysiological processes, including a-synuclein aggregation, neuro-inflammation, mitochondrial dysfunction, neuronal vulnerability, and neural network alterations. It is estimated that more than 1 million people with PD live in the United States and approximately 60,000 Americans are diagnosed each year. There are no treatments that slow the neurodegenerative process for PD; furthermore, the predisposition to PD involves a complex, polygenic, and pleiotropic genetic architecture. Traditional reductionist paradigm overlooks the inherent complexity of PD and has often led to treatments that are inadequate or fraught with adverse effects. Existing data resources, including genomics, transcriptomics, interactomics (protein-protein interactions), and phenomics (clinical data), have not yet been fully utilized and integrated to explore the pathobiology of PD. Understanding PD from the point-of-view of how cellular systems and molecular interactome perturbations underlie the disease (termed disease module) is the essence of network medicine. Systematic identification and characterization of novel underlying pathogenesis and disease module as well as a reticulotype (i.e., a patient's unique disease module that allows an exploration of how molecular network perturbations affect phenotype), will serve as a foundation for identifying and validating prognostic, progression, predictive biomarkers, and drug targets in PD. Integration of the genome, transcriptome, proteome, and the human interactome are essential for such identification. Given our preliminary results, we posit that network-based, integrative multi-omics analyses of large-scale existing PD cohorts enables a more complete mechanistic understanding of pathogenesis (termed 'reticulotyping') and the rapid development of targeted therapeutic intervention for PD.

This notebook contains the three main frameworks for this project. These frameworks are highly self-contained, and can be migrated on the cloud server with minimum effort and integrated in other workflows.
For example usages, skip to [Example Workflow](#a3).

## Code Base

This part of the notebook contains the implementation of the procedures shown in the workflow figure. The codes are grouped into three frameworks. These frameworks can be deployed on the cloud platform and integrated in user-defined workflows.

In all, these frameworks require the following Python 3.7+ packages:
* numpy
* scipy
* PyTorch
* networkx
* matplotlib

See [Example Workflow](#a3) for usages of these frameworks.

### Framework - Genome/Transcriptome Data Reticulotyping

This section implements the methods described in [A genome-wide positioning systems network algorithm for in silico drug repurposing](https://www.ncbi.nlm.nih.gov/pubmed/31375661) by Cheng F *et al*. Nat Commun. 2019 Aug 2;10(1):3476.

The following packages are used:

```python
import time
import random
import pickle
import numpy as np
from math import sqrt
from scipy.stats import hypergeom
import networkx as nx
import matplotlib.pyplot as plt
```

We define a module as a subgraph within the human protein–protein interactome and calculate the score of the module M as

\begin{align}
Z_m = \frac{\sum\limits _{i\in M} (S(i) - \mu)}{\sqrt{m}} \tag{1}
\end{align}

where $m$ is the number of genes in module M, $s(i)$ is the network-predicted score for gene i and $\mu$ is the average score over the whole gene set. We denote $\Gamma_{M}$ as the set of genes that interact with module $M$ in the human protein–protein interactome. Specifically, we first randomly select a protein (gene) as a “seed protein.” For each gene $i \in \Gamma_{M}$, we next calculate its connectivity significance in the human protein–protein interactome as

\begin{align*}
P(i) = \sum_{k = k_m}^{\min(k_i, m)}\frac{\left(\begin{array}{c}
m \\
k
\end{array}\right) \left(\begin{array}{c}
N-m \\
k_i-k
\end{array}\right)}{\left(\begin{array}{c}
N \\
k_i
\end{array}\right)} \tag{2}
\end{align*}

where $k_i$ is the degree of gene i, $m$ is the number of genes in the module, $k_m$ is the number of module genes that link to gene i, and $N$ is the total number of genes in the gene set. For each seed $i \in \Gamma_{M}$, we further calculate the expanded module score if gene i is added to the module as

\begin{align*}
Z_{m+1}(i) = \frac{(s(i) - \mu) + \sum_{i\in M}(s(i)- \mu)}{\sqrt{m+1}} \tag{3}
\end{align*}

The candidate gene i will be added to the module to build a new module by satisfying two criteria: (a) $P(i)$ in Eqn (2) is less than 0.05 (connectivity significance), and (b) $Z_{m+1}(i) > Z_m$. We repeat steps (a) and (b) until no more genes can be added by these criteria (Above steps are implemented in functions 'P_Cnnectivity' and 'SingleModule'). We built multiple enough modules by randomly selecting each seed gene in the human interactome (See function 'ModuleGeneration').

Finally, we assembled the top (e.g., top 1%) ranked modules with the highest module scores and defined the largest connected component by removing the isolated genes to define the final disease module (see function 'GPSnet').

```python
def P_Connectivity(k_m, k_i, n, N_NODES): # connectivity significance p value
    p_rv = hypergeom(N_NODES, n, k_i)
    return p_rv.cdf(min(k_i, n)) - p_rv.cdf(k_m - 1)

def SingleModule(idx, NODE_ID, P_CUTOFF, NODE_SCORE, NODE_NEIGHBOR, NODE_DEGREE, ID2POS):
    MEAN_NODE_SCORE = NODE_SCORE.mean()
    N_NODES = len(NODE_ID)
    last_added_pos = random.randrange(0, N_NODES)
    module_pos = [last_added_pos]  # [pos1, pos2, ...]
    module_id = {NODE_ID[last_added_pos]}  # {id1, id2, ...}
    score_sum = 0
    neighbors_id = set()

    while True:
        print(".", end="")
        # -------- compute current module score and neighbors -------- #
        module_size = len(module_id)
        score_sum += NODE_SCORE[last_added_pos]
        module_score = (score_sum - module_size * MEAN_NODE_SCORE) / sqrt(module_size)

        neighbors_id |= NODE_NEIGHBOR[NODE_ID[last_added_pos]]
        neighbors_id -= module_id
        if not len(neighbors_id):
            break
        neighbors_pos = np.array([ID2POS[id] for id in sorted(neighbors_id)])

        # -------- select nodes that could increase module score -------- #
        qualified_pos = neighbors_pos[module_score * sqrt(module_size) + NODE_SCORE[neighbors_pos] > module_score * sqrt(module_size + 1) + MEAN_NODE_SCORE]
        if not len(qualified_pos):
            break

        # -------- select nodes whose connectivity significance p-value <= P_CUTOFF -------- #
        k_m_ls = [len(NODE_NEIGHBOR[NODE_ID[pos]] & module_id) for pos in qualified_pos]
        k_i_ls = NODE_DEGREE[qualified_pos]
        qualified_csp_pos = [pos for pos, k_m, k_i in zip(qualified_pos, k_m_ls, k_i_ls) if P_Connectivity(k_m, k_i, module_size, N_NODES) <= P_CUTOFF]
        if not len(qualified_csp_pos):
            break

        # -------- randomly add one node to the module-------- #
        last_added_pos = random.choice(qualified_csp_pos)
        module_id.add(NODE_ID[last_added_pos])
        module_pos.append(last_added_pos)

    print("")
    module_score = (NODE_SCORE[np.array(module_pos)].sum() - len(module_id) * MEAN_NODE_SCORE) / sqrt(len(module_id))
    return module_id, module_score

def ModuleGeneration(dir_node_score, dir_nn, P_CUTOFF, N_EXPER):
    with open(dir_node_score) as fi:
        next(fi)
        NODE_ID = []
        NODE_SCORE = []
        for line in fi:
            id, score = line.strip("\n").split("\t")
            NODE_ID.append(id)
            NODE_SCORE.append(score)

    NODE_ID = np.array(NODE_ID, dtype=np.str)  # Array: pos -> id
    NODE_SCORE = np.array(NODE_SCORE, dtype=np.float)  # Array: pos -> score

    with open(dir_nn, 'rb') as fi:
        NODE_NEIGHBOR = pickle.load(fi)  # ['p1':{n1, n2, ...}, ...]

    NODE_DEGREE = np.array([len(NODE_NEIGHBOR[node]) for node in NODE_ID])  # Array: pos -> degree

    N_NODES = len(NODE_ID)
    MEAN_NODE_SCORE = NODE_SCORE.mean()
    ID2POS = {j: i for i, j in enumerate(NODE_ID)}  # {id -> pos}

    module_nid = {}
    module_ns = {}
    for idx in range(0, (0 + N_EXPER)):
        start_time = time.time()
        module, module_score = SingleModule(idx, NODE_ID, P_CUTOFF, NODE_SCORE, NODE_NEIGHBOR, NODE_DEGREE, ID2POS)
        module_nid[idx] = module
        module_ns[idx] = module_score
        print("--- Experiment %s takes %.1f seconds ---" % (idx, time.time() - start_time))
    return module_nid, module_ns, NODE_NEIGHBOR

def GPSnet(dir_node_score, dir_nn, dir_id2name, P_CUTOFF, N_EXPER, SCORE_CUT, FREQ_CUT):
    # SCORE_CUT: top ranked modules, e.g. SCORE_CUT = 0.1, top 0.1 or 10% ranked modules
    # FREQ_CUT: smallest acceptable node frequency, e.g. FREQ_CUT = 0.1, only include nodes which appears at least once in 10 selected modules
    ID2NAME = dict([line.split() for line in open(dir_id2name).read().splitlines()])
    module_nid, module_ns, NODE_NEIGHBOR = ModuleGeneration(dir_node_score, dir_nn, P_CUTOFF, N_EXPER)
    module_score = np.concatenate([np.array(list(module_ns.keys()))[:,None], np.array(list(module_ns.values()))[:,None]], axis = 1)
    sorted_module_score = module_score[module_score[:,1].argsort()[::-1]]
    sel_module = sorted_module_score[0: int(N_EXPER * SCORE_CUT), 0]
    assemble_module = set()
    for mid in sel_module:
        assemble_module |= module_nid[mid]

    node_freq = {}

    for nid in assemble_module:
        for mid in sel_module:
            if nid in module_nid[mid]:
                if nid in node_freq:
                    node_freq[nid] += 1
                else:
                    node_freq[nid] = 1

    nA_net, nB_net = zip(*[(ID2NAME[nid], ID2NAME[mid]) for nid in assemble_module for mid in NODE_NEIGHBOR[nid]&assemble_module if len(NODE_NEIGHBOR[nid] & assemble_module) > 0 and node_freq[nid] > int(FREQ_CUT * len(sel_module))])

    G = nx.Graph()
    G.add_edges_from(list(zip(nA_net, nB_net)))
    nG_LCC = max(nx.connected_components(G), key=len)
    net_LCC = G.subgraph(nG_LCC)
    return module_score, net_LCC
```

### Framework - Deep Learning-based Graph Embedding

This section implements the methods described in [Target identification among known drugs by deep learning from heterogeneous networks](https://pubs.rsc.org/en/content/articlehtml/2020/sc/c9sc04336e) by Cheng F *et al*. Chem. Sci., 2020, 11, 1775-1797

The following packages are used:

```python
import numpy as np
import torch
from torch.utils.data import TensorDataset, DataLoader
```

####  Random Surfing with Restart

Transition matrix $A$ captures the transition probabilities between different nodes. We consider a random surfing model with restart, which introduces a pre-defined restart probability at the initial node for every iteration. It takes both local and global topological connectivity patterns within the network into consideration to fully exploit the underlying direct or indirect relations between nodes. Thus, at each time, there is a probability $α$ that the random surfing procedure will continue, and a probability $1-α$ that it will return to the original node and restart the procedure. In vectorized form, the
probabilistic co-occurrence after *k* steps is:

\begin{align*}
P_k = \alpha P_{k-1}A+(1-\alpha)P_0 \tag{4}
\end{align*}

where $P_0$ is an identity matrix the same shape of $A$ indicating all nodes are at their original place before any movement.
The final probabilistic co-occurrence representation takes the number of steps into considerations, as large steps should have less weight (for proof, see original paper):

\begin{align*}
r=\sum_{k=1}^{K}P_k \tag{5}
\end{align*}

```python
def RandomSurfing(adj, steps=3, alpha=0.98):
    A = adj - np.diag(np.diag(adj))  # zeroing diagonal
    A = A / A.sum(axis=1, keepdims=1)  # row-wise scaling
    P0 = np.eye(len(A))
    P = P0
    M = np.zeros_like(A)
    for _ in range(steps):
        P = alpha * P @ A + (1 - alpha) * P0
        M += P
    return M
```

####  Positive Pointwise Mutual Information
We calculate the positive pointwise mutual information (PPMI) matrix using the probabilistic co-occurrence representation. The PPMI matrix can be viewed as a matrix factorization method which factorizes a co-occurrence representation to yield network representations.

\begin{align*}
PPMI_{ij}=\max(0, log\frac{r_{ij} \times \sum r_{\cdot\cdot}}{\sum r_{i \cdot} \times \sum r_{\cdot j}}) \tag{6}
\end{align*}

```python
def PPMI(m):
    A = m - np.diag(np.diag(m))  # zeroing diagonal
    A = A / A.sum(axis=1, keepdims=1)  # row-wise scaling
    row_sum = A.sum(axis=1, keepdims=1)
    col_sum = A.sum(axis=0, keepdims=1)
    D = col_sum.sum()
    ppmi = np.maximum(np.log(D * A / row_sum / col_sum), 0)
    ppmi[ppmi == np.inf] = 0
    return ppmi
```

####  Stacked Denoising Autoencoder
To investigate the construction of high quality low-dimensional vector representations for nodes from the PPMI matrix that conveys essential structural information of the network, we use a stacked denoising autoencoder (SDAE), which is a popular model used in deep learning, to generate compressed, low-dimensional vectors from the original high-dimensional node vectors. This process essentially performs dimension reduction that maps data from a high dimensional space into a lower dimensional space. Denoising autoencoders partially corrupt the input data before taking the training step, adding noise helps a SDAE to learn features that are robust to partial corruption of input data. Each hidden layer in the SDAE is pre-trained by a denoising autoencoder, whose hidden layer output becomes the input of the next dimenion reduction hidden layer of the SDAE.

```python
class DAE(object):
    ACTIVATION = {
        "SIGMOID": torch.nn.Sigmoid,
        "TANH"   : torch.nn.Tanh,
        "RELU"   : torch.nn.ReLU,
    }
    OPTIMIZER = {
        "SGD"     : torch.optim.SGD,
        "ADAM"    : torch.optim.Adam,
        "ADAGRAD" : torch.optim.Adagrad,
        "ADADELTA": torch.optim.Adadelta,
        "RMSPROP" : torch.optim.RMSprop,
    }

    def __init__(self, n_in, n_hidden, nonlinear="RELU", noise=0.05, device=None,
                 solver="ADAGRAD", lr=1e-3, decay=1e-5, epoch=10, batch_size=64):
        super().__init__()
        self.n_in = n_in
        self.n_hidden = n_hidden
        self.noise = noise
        self.device = torch.device("cpu") if device is None else device
        self.model = torch.nn.Sequential(
            torch.nn.Linear(n_in, n_hidden),
            self.ACTIVATION[nonlinear](),
            torch.nn.Linear(n_hidden, n_in)
        )
        self.model.to(self.device)
        self.lr = lr
        self.decay = decay
        self.optimizer = self.OPTIMIZER[solver](params=self.model.parameters(), lr=lr, weight_decay=decay)
        self.epoch = epoch
        self.batch_size = batch_size
        self.loss_fn = torch.nn.MSELoss()

    def fit(self, x: torch.Tensor):
        self.model.train()
        dataloader = DataLoader(TensorDataset(x), batch_size=self.batch_size, shuffle=True)
        for epoch in range(1, self.epoch + 1):
            total_loss = 0
            for batch, (data,) in enumerate(dataloader):
                data_ = data.to(self.device)
                if self.noise:
                    data_ += self.noise * torch.randn(*data.shape).to(self.device)
                h = self.model(data_)
                loss = self.loss_fn(h, data_)
                self.optimizer.zero_grad()
                loss.backward()
                self.optimizer.step()
                total_loss += loss.item() * data.size(0)
            print("EPOCH: %s  MSE: %.6f" % (epoch, total_loss / x.shape[0]))
        return self

    def transform(self, x: torch.Tensor):
        self.model.eval()
        dataloader = DataLoader(TensorDataset(x), batch_size=self.batch_size, shuffle=False)
        encoded = torch.empty(x.shape[0], self.n_hidden)
        with torch.no_grad():
            for batch, (data,) in enumerate(dataloader):
                data_ = data.to(self.device)
                start = self.batch_size * batch
                end = start + data.size(0)
                encoded[start:end] = self.model[1](self.model[0](data_)).to(device="cpu").detach()
        return encoded

    def fit_transform(self, x: torch.Tensor):
        return self.fit(x).transform(x)

    def evaluate(self, x: torch.Tensor):
        self.model.eval()
        dataloader = DataLoader(TensorDataset(x), batch_size=self.batch_size, shuffle=False)
        decoded = torch.empty(*x.shape)
        with torch.no_grad():
            for batch, (data,) in enumerate(dataloader):
                data_ = data.to(self.device)
                start = self.batch_size * batch
                end = start + data.size(0)
                decoded[start:end] = self.model(data_).to(device="cpu").detach()
        print("Evaluation MSE: %.6f" % self.loss_fn(decoded, x).item())
        return decoded

def SDAE(mat, dimensions=(512, 256, 128), *args, **kwargs):
    mat = torch.from_numpy(mat).to(torch.float)
    for n_in, n_hidden in zip([mat.shape[1], *dimensions], dimensions):
        print("Reducing from %s to %s dimensions" % (n_in, n_hidden))
        model = DAE(n_in, n_hidden, *args, **kwargs)
        model.fit(mat)
        model.evaluate(mat)
        mat = model.transform(mat)
    return mat.numpy()
```

### Framework - Network Proximity

This section implements the methods described in
[Network-based prediction of drug combinations](https://www.nature.com/articles/s41467-019-09186-x) by Cheng F, Kovács IA, Barabási AL. Nature Communications (2019) 10:1197
[Network-based approach to prediction and population-based validation of in silico drug repurposing](https://www.nature.com/articles/s41467-018-05116-5). Cheng F *et al*. Nature Communications (2018) 9:2691
[A genome-wide positioning systems network algorithm for in silico drug repurposing](https://www.nature.com/articles/s41467-019-10744-6). Cheng F *et al*. Nature Communications (2019) 10:3476

The following packages are used:

```python
import random
import numpy as np
import numpy.ma as ma
import networkx as nx
import matplotlib.pyplot as plt
```

####  Human Protein Interactome
We first need to load the human protein interactome from the adjacency file `adj`. Each row in the `adj` file is space-separated gene ids, with the first one being the source, and all others being the targets. Self links are removed.

See **[My Personal Mutanome](https://mutanome.lerner.ccf.org/)** for more details of the human protein interactome.

```python
def Interactome(adj):
    G = nx.read_adjlist(adj)
    G.remove_edges_from(nx.selfloop_edges(G))
    return G
```

####  Disease Gene Network Modularity
After disease module (a list of disease-associated/causing genes) or any gene module is obtained (`mod`), we can check whether these genes are connected. See later section for the significance test of the modularity.

```python
def CC(interactome, mod):
    return sorted(nx.connected_components(interactome.subgraph(mod)), key=len, reverse=True)

def LCC(interactome, mod):
    return len(CC(interactome, mod)[0])

def PlotNet(net, labels, dtn):
    targetable = set()
    for d in dtn:
        targetable |= set(dtn[d])
    plt.figure(figsize=(10, 10))
    nx.draw_kamada_kawai(
        net,
        node_color=["#85dcffc0" if n in targetable else "#96dd49c0" for n in net.nodes()],
        node_size=1000,
        width=3,
        edge_color="#bcc3cb80",
        labels=labels)
    plt.show()
    plt.close()
```

####  Network Proximity
Here we show the implementation of four metrics, distance (asymmetrical), shortest, closest (default), and kernel for the network proximity of two modules `mod1` and `mod2`. To speed up the procedure, we can instead precalculate all the shortest distances in the interactome and save them in a numpy array file.

\begin{align}
Distance - d_{YT} &= \frac{1}{||T||}\sum_{t \in T}\min_{y \in Y}d(y,t) \tag{7} \\
Shortest - d^S_{YT} &= \frac{1}{||Y||\times||T||}\sum_{y \in Y, t \in T}d(y,t) \tag{8} \\
Closest - d^C_{YT} &= \frac{1}{||Y||+||T||}(\sum_{y \in Y}\min_{t \in T}d(y,t)+\sum_{t \in T}\min_{y \in Y}d(y,t)) \tag{9} \\
Kernel - d^K_{YT} &= \frac{-1}{||Y||+||T||}(\sum_{y \in Y}\log\sum_{t \in T}\frac{e^{-d(y,t)-1}}{||T||}+\sum_{t \in T}\log\sum_{y \in Y}\frac{e^{-d(y,t)-1}}{||Y||}) \tag{10} \\
\end{align}

```python
def Proximity(interactome, mod1, mod2, method="CLOSEST"):
    SD = np.zeros((len(mod1), len(mod2)))
    for row, source in enumerate(mod1):
        for col, target in enumerate(mod2):
            try:
                SD[row, col] = nx.shortest_path_length(interactome, source, target)
            except nx.NetworkXNoPath:
                SD[row, col] = 9999
    SD = ma.masked_values(SD, 9999)
    if method == "DISTANCE":
        return SD.min(1).mean()
    if method == "SHORTEST":
        return SD.mean()
    if method == "CLOSEST":
        row_min = SD.min(1)
        col_min = SD.min(0)
        return (row_min.sum() + col_min.sum()) / sum(SD.shape)
    if method == "KERNEL":
        exp = ma.exp(-SD)
        alb = ma.log(exp.sum(1))
        bla = ma.log(exp.sum(0))
        nA = alb.count()
        nB = bla.count()
        return (nA * np.log(nB) + nB * np.log(nA) - alb.sum() - bla.sum()) / (nA + nB) + 1
```

####  Random Sampling and Z-Score
We use permutation test to test the significance of the LCC (`LCC_Z`) or the proximity (`Proximity_Z`). By default the random experiments are performed `repeat=1000` times.

The `Plot` function plots the lcc and the proximity analyses results.

```python
def SampleNetwork(interactome, n):
    nodes = interactome.nodes()
    while True:
        yield random.sample(nodes, n)

def Z_Score(real, background, right=False):
    m = background.mean()
    s = background.std(ddof=1)
    z = (real - m) / s
    p = np.mean(background < real)
    if right:
        p = 1 - p
    return z, p

def LCC_Z(interactome, mod, repeat=1000):
    sampler = SampleNetwork(interactome, len(mod))
    lcc = LCC(interactome, mod)
    background = []
    for _ in range(repeat):
        background.append(LCC(interactome, next(sampler)))
    z, p = Z_Score(lcc, np.array(background), right=True)
    return {"TYPE": "LCC", "L": lcc, "Z": z, "P": p, "B": background}

def Proximity_Z(interactome, mod1, mod2, method="CLOSEST", repeat=1000):
    sampler1 = SampleNetwork(interactome, len(mod1))
    sampler2 = SampleNetwork(interactome, len(mod2))
    proximity = Proximity(interactome, mod1, mod2, method=method)
    background = []
    for _ in range(repeat*2):
        v = Proximity(interactome, next(sampler1), next(sampler2), method=method)
        if not ma.is_masked(v):
            background.append(v)
        if len(background) == repeat:
            break
    z, p = Z_Score(proximity, np.array(background), right=False)
    return {"TYPE": "PROXIMITY", "D": proximity, "Z": z, "P": p, "B": background}

def Plot(result, pathOut=None, bins=20, colors=("#c8c8c8", "#ff0000"), labelX=0.7):
    if result["TYPE"] == "PROXIMITY":
        x = result["D"]
        x_text = "$Z$ = %.2f" % result["Z"]
        x_label = "Proximity"
        y_label = "Frequency"
    elif result["TYPE"] == "LCC":
        x = result["L"]
        x_text = "$S$ = %s" % x
        x_label = "Observed module size ($S$)"
        y_label = "Frequency of $S$"
    else:
        raise Exception("Unknown result type")
    p_text = ("$P$ = %.3f" % result["P"]) if result["P"] >= 0.001 else "$P$ < 0.001"
    fig = plt.figure()
    ax = fig.add_subplot(111)
    plt.hist(result["B"], bins=bins, color=colors[0], label="Random", weights=np.ones(len(result["B"])) / len(result["B"]))
    plt.axvline(x=x, c=colors[1], ymax=0.5)
    plt.text(labelX, 0.8, x_text, fontsize=18, transform=ax.transAxes)
    plt.text(labelX, 0.7, p_text, fontsize=18, transform=ax.transAxes)
    plt.legend(fontsize=18, frameon=False, markerscale=1.5)
    plt.tick_params(labelsize=18)
    plt.xlabel(x_label, fontsize=18)
    plt.ylabel(y_label, fontsize=18)
    plt.subplots_adjust(left=0.18, bottom=0.15, right=0.98, top=0.98)
    if pathOut is not None:
        plt.savefig(pathOut, dpi=300)
    else:
        plt.show()
    plt.close()
```

####  Network Proximity-based Drug Screening
To screen the drugs for a disease module, we need to first load the drug target network (`DrugTargetNetwork`) from a tab-separated file containing drugs (column 1) and targets (column 2). Then, the proximity and its significance of each drug and the disease module is computed (`DrugScreen`).

See **[AlzGPS](https://alzgps.lerner.ccf.org/)** for more details of the drug target network.

```python
def DrugTargetNetwork(filename):
    dtn = {}
    with open(filename) as fi:
        for line in fi:
            drug, target = line.strip("\n").split("\t")
            dtn.setdefault(drug, []).append(target)
    return dtn

def DrugScreen(interactome, dtn, mod, method="CLOSEST", repeat=1000):
    result = []
    for drug, targets in dtn.items():
        targets = [t for t in targets if t in interactome.nodes()]
        if targets:
            out = Proximity_Z(interactome, mod, targets, method=method, repeat=repeat)
            result.append({"DRUG": drug, **out})
    result.sort(key=lambda x: x["Z"])
    return result
```

## Example Workflow

This part of the notebook show the example usages of the frameworks. All packages/functions should be loaded/defined at this point and are ready to use.

### Example - Genome/Transcriptome Data Reticulotyping

In this subsection, we will use the smoothing gene score generated by random walk with restart (RWR) as input.
In this example, we build 10 raw modules, and assemble the top 2% ranked modules to form the final disease module.

```python
module_score, net_LCC = GPSnet("data/node_score.txt", "data/nnG.p", "data/ID2Symbol.txt", 5e-2, 10, 0.2, 0.5)
```

    .........................................................................................
    --- Experiment 0 takes 5.0 seconds ---
    ................................................................................................................
    --- Experiment 1 takes 10.1 seconds ---
    ...................................................................................................
    --- Experiment 2 takes 4.0 seconds ---
    .......................................................
    --- Experiment 3 takes 1.9 seconds ---
    .............................................
    --- Experiment 4 takes 1.6 seconds ---
    ............................
    --- Experiment 5 takes 0.3 seconds ---
    .................................................................................................
    --- Experiment 6 takes 3.8 seconds ---
    .................................................................................
    --- Experiment 7 takes 3.0 seconds ---
    ....................
    --- Experiment 8 takes 0.1 seconds ---
    .................................................................................................
    --- Experiment 9 takes 4.8 seconds ---

Let's visualize the disease module.

```python
plt.figure(figsize=(10, 10))
nx.draw_kamada_kawai(
    net_LCC,
    node_color="#96dd49c0",
    node_size=500,
    width=2,
    edge_color="#bcc3cb80",
    labels={i:i for i in net_LCC.nodes()})
plt.show()
plt.close()
```

### Example - Network Proximity
In this section, we will use the disease gene list downloaded from Open Targets as an example to show the usages of the Network Proximity framework.

Several things to prepare first:
* load human protein interactome
* load drug-target network
* load gene id to symbol mapping

```python
random.seed(1024)
Int = Interactome("data/human_interactome.adj")
Dtn = DrugTargetNetwork("data/drug_target_network.txt")
ID2Symbol = dict([line.split() for line in open("data/ID2Symbol.txt").read().splitlines()])
```

The disease associated gene list for PD can be downloaded from https://www.targetvalidation.org/disease/EFO_0002508/associations. Genes with a score of 1 are used for the downstream analyses.

```python
PD_genes = open("data/PD.txt").read().splitlines()
PD_genes = [g for g in PD_genes if g in Int.nodes()]
print("Number of genes:", len(PD_genes))
```

    Number of genes: 104

We look at whether these 104 genes form a significantly connected module in the protein interactome. The result show that 52 genes are connected in the largest connected component of theses genes, with a *P* value < 0.05.

```python
result = LCC_Z(Int, PD_genes)
Plot(result)
```

Let's visualize the LCC network. Blue indicates these nodes can be targeted by at least one drug from the drug target network.

```python
lcc = CC(Int, PD_genes)[0]
net = Int.subgraph(lcc)
labels = {n: ID2Symbol[n] for n in net.nodes()}
PlotNet(net, labels, Dtn)
```

Next we want to prioritize the drugs for the PD module to reveal potentially relavent drugs. There are more than 2,900 drugs in our drug target network, which takes some time to run completely. As an example, we will only test the first 20 drugs with permutation test of 100 repeats.

```python
dtn = {d: Dtn[d] for d in sorted(Dtn)[:20]}
result = DrugScreen(Int, dtn, PD_genes, repeat=100)

drugbank2name = dict([line.strip("\n").split("\t") for line in open("data/drug_name.txt").read().splitlines()])

for r in result:
    print(drugbank2name[r["DRUG"]].ljust(30, " "), " D: %.1f  Z: %s%.3f  P: %.3f" % (r["D"], (" ", "")[int(r["Z"] < 0)], r["Z"], r["P"]))
```

    Cetuximab                       D: 2.0  Z: -2.472  P: 0.000
    Urokinase                       D: 2.1  Z: -1.264  P: 0.080
    Erythropoietin                  D: 2.4  Z: -0.773  P: 0.340
    Darbepoetin alfa                D: 2.4  Z: -0.635  P: 0.370
    Sargramostim                    D: 2.2  Z: -0.631  P: 0.230
    Denileukin diftitox             D: 2.3  Z: -0.465  P: 0.300
    Interferon alfa-n3              D: 2.4  Z: -0.405  P: 0.500
    Peginterferon alfa-2a           D: 2.4  Z: -0.375  P: 0.500
    Interferon alfa-n1              D: 2.4  Z: -0.318  P: 0.560
    Reteplase                       D: 2.3  Z: -0.172  P: 0.460
    Alteplase                       D: 2.3  Z: -0.160  P: 0.460
    Pegfilgrastim                   D: 2.4  Z: -0.150  P: 0.620
    Secretin human                  D: 2.8  Z:  0.130  P: 0.510
    Etanercept                      D: 2.1  Z:  0.134  P: 0.540
    Salmon Calcitonin               D: 2.8  Z:  0.336  P: 0.620
    Lepirudin                       D: 2.9  Z:  0.391  P: 0.630
    Bivalirudin                     D: 2.9  Z:  0.535  P: 0.700
    Goserelin                       D: 2.8  Z:  1.109  P: 0.840
    Leuprolide                      D: 3.3  Z:  1.736  P: 0.930
    Sermorelin                      D: 3.8  Z:  2.390  P: 0.980

Cetuximab and Urokinase achieved small *P* values and *Z* scores. Literature search showed potential relations between these drugs and PD: https://ascopubs.org/doi/abs/10.1200/JCO.2019.37.15_suppl.3595, https://www.ncbi.nlm.nih.gov/pubmed/32035284

Let's take a closer look at the proximity between Urokinase and the PD module. This time we repeat the permutation test 1000 times. Note that the values may change slightly due to different random state. We then plot the proximity result.

```python
result = Proximity_Z(Int, PD_genes, dtn["DB00013"], repeat=1000)
Plot(result, labelX=0.05)
```


```python
import theme
theme.load_style()
```

# Introduction to Nonlinearity

This lecture by Tim Fuller is licensed under the
Creative Commons Attribution 4.0 International License.  All code examples are also licensed under the [MIT license](http://opensource.org/licenses/MIT).

```python
import numpy as np
import sympy as sp
%pylab inline
sp.init_printing("latex")
```

    Populating the interactive namespace from numpy and matplotlib

Nonlinearity arises naturally in physical systems and can be modeled by finite elements in one (or more) of several ways.

# Types of Nonlinearity

They types of nonlinearity encountered in static analysis can be classified as either material nonlinearity or geometric nonlinearity.

- **Material Nonlinearity**: Nonlinear relationship between stress and strain
- **Geometric Nonlinearity**: Nonlinear relationship between strain and stretch

To illustrate, consider the case of nonlinear elasticity,
$$
\sigma = f\left(\epsilon\right)
$$

where $\epsilon$ is a strain determined from the stretch $\lambda=L/L_0$

$$
\epsilon = g\left(\lambda\right)
$$

Here, $f$ and $g$ are nonlinear functions.  Combined, we get

$$
\sigma = f\left(g\left(\lambda\right)\right)
$$

Suppose laboratory experiments show

```python
lam = np.linspace(.1, 5)
sig = 38 * np.log(lam)
plt.xlabel(r"$\lambda$")
plt.ylabel(r"$\sigma$")
plot(lam, sig);
```

There are many ways we can express the $f$ and $g$ functions:

$$\begin{align}
\sigma &= f\left(\epsilon\right) = 38 \epsilon \\
\epsilon &= g\left(\lambda\right) = \ln{\lambda}
\end{align}$$

or

$$\begin{align}
\sigma &= f\left(\epsilon\right) = 38 \ln{\left(\epsilon + 1\right)} \\
\epsilon &= g\left(\lambda\right) = \lambda - 1
\end{align}$$

or

$$\begin{align}
\sigma &= f\left(\epsilon\right) = 19 \ln{\left(2\epsilon + 1\right)} \\
\epsilon &= g\left(\lambda\right) = \frac{1}{2}\left(\lambda^2 - 1\right)
\end{align}$$

etc.

When combined, all of these choices give

$$
\sigma = f\left(g\left(\lambda\right)\right) = 38 \ln{\lambda}
$$

Choice 1 has a linear stress-strain relationship but a nonlinear strain-stretch relationship.  This is called **geometric nonlinearity**.

Choice 2 has a nonlinear stress-strain relationship but a linear strain-stretch relationship.  This is called **material nonlinearity**.

Choice 3 has both material and geometric nonlinearity.

No matter the choice of where to put nonlinearity, the end result of stress as a function of stretch should be the same.  Thus, the terms
material nonlinearity and geometric nonlinearity are somewhat arbitrary.  They *do* indicate differences in the FEM implementation, but not differences in the final answer.

# Governing Equations of a 1D Elastic Solid

In solid mechanics, static equilibrium requires that

$$
\frac{d\sigma\left[u\right]}{dx} + b = 0
$$

where we have now that $\sigma\left[u\right]$ is a (not necessarily linear) function of the displacement $u$ and and $b$ is a force per unit volume.

The weak form is derived in the usual way

$$\begin{align}
&\int_{\Omega}\left(\frac{d\sigma\left[u\right]}{dx} + b\right)w\,dx = 0, \qquad \forall w \\
&\int_{\Omega}\left(\sigma\left[u\right]\frac{dw}{dx} - bw\right)\,dx - \sigma\left[u\right] w\Bigg|_{\partial \Omega} = 0
\end{align}
$$

Because stress is not necessarily linear in $u$, $u$ must be found iteratively.

## Finite Element Equations

The finite element solution procedure for the nonlinear problem follows nearly identical that of the linear problem, namely, the isplacement field is approximated by interpolating polynomials possessing the property of compact support (shape functions):

$$
u = \sum_{j=1}^n u_j N_j
$$

and the weighting functions as

$$
w = \sum_{i}^n w_i N_i
$$

The stress corresponding to a given displacement is found by computing the strain

$$
\epsilon = \frac{du}{dx} = \sum_{j=1}^{n}u_j \frac{dN_j}{dx}
$$

and then passing the strain to the material model $\sigma = \sigma\left[\epsilon\right]$

Upon substitution of the preceding equations in to the weak form we arrive at

$$
\int_{\Omega}\left(\sigma\left[\epsilon(u_j)\right]\frac{dN_i}{dx} - bN_i\right)\,dx - \sigma\left[\epsilon(u_j)\right] N_i\Bigg|_{\partial \Omega} = 0, \quad \forall x_i \text{ on } \partial_\sigma \Omega
$$
$$
u_i = u_i^*\left(x_i\right), \quad \forall x_i \text{ on }\partial_u\Omega
$$

This is a set of $n$ equations for the $n$ unknown nodal displacements $u_i$.  This set of equations is similar to those in the linear case, but because the the consititutive law relating stress and strain is not necessarily linear, it must be solved numerically.

### Newton-Raphson Iteration

One method of solving the finite element equations is by Newton-Raphson iteration whereby we
*linearize* the finite element equations about some guess for the $u_j$ and obtain successfully better approximations by iteration.  The linearization of $\sigma$ about $u_j$ is

$$
\sigma\left[u_k\right] \approx \sigma\left[u_k^0\right] + \frac{d\sigma}{du_k}du_k =  \sigma\left[u_k^0\right] + \frac{d\sigma}{d\epsilon}\frac{d\epsilon}{du_k}du_k
$$

recalling that

$$
\epsilon = \sum_{k=1}^n u_k \frac{dN_k}{dx} \rightarrow
\frac{d\epsilon}{du_j} = \sum_{k=1}^n \frac{du_k}{du_j} \frac{dN_k}{dx}
= \sum_{k=1}^n \delta_{kj} \frac{dN_k}{dx} = \frac{dN_j}{dx}
$$

we can write

$$
\int_{\Omega}\left(\frac{d\sigma}{d\epsilon}\frac{dN_i}{dx}\frac{dN_j}{dx}\,dx\right) du_j +
\int_{\Omega}\sigma\frac{dN_i}{dx}\, dx -
\int_{\Omega}bN_i\,dx -
\sigma N_i\Bigg|_{\partial \Omega} = 0
$$

or

$$
K_{ij}du_{j} = F_{i} - R_{i}
$$

where
$$
K_{ij} =
\int_{\Omega}\frac{d\sigma}{d\epsilon}\frac{dN_i}{dx}\frac{dN_j}{dx}\,dx, \quad
R_{i} = \int_{\Omega}\sigma\frac{dN_i}{dx}\, dx, \quad
F_{i} = \int_{\Omega}bN_i\,dx + \sigma N_i\Bigg|_{\partial \Omega}
$$

This expression is nearly identical to the linear case with the addition of two terms:

- The strain dependent tangent material stiffness $\frac{d\sigma}{dx}$
- An element "residual" $R$

## Finite Element Algorithm

 **STEP 0** Initial guess for $u_j$ ($u_j=0$ is customary)
 **STEP 1** Compute global $K_{ij}$, $R_i$, and $F_i$
 **STEP 2** Enforce boundary conditions
 **STEP 3** Solve $K_{ij}du_j = F_i - R_i$
 **STEP 4** Set $u_j \leftarrow u_j + du_j$
 **STEP 5** If $\lVert du_j\rVert>$ TOL: GO TO STEP 1
 **STEP 6** Write output, postprocess, etc.

### Element Residual Calculation

Let us investigate what additional information we will need to compute the element residual.  The element residual is given by

$$
r_k^e = \int_{x_A}^{x_B}\sigma \frac{dN_k}{dx} dx
$$

Using Gauss-Legendre quadrature

$$
r_k^e = \int_{-1}^{1}\sigma \frac{1}{J} \frac{dN_k}{d\xi} Jd\xi
      = \sum_{l=1}^{n}\sigma\frac{dN_k\left(\xi_l\right)}{d\xi} w_l
$$

Computing the residual will require computing the stress at a Gauss point and, implicitly, the strain since $\sigma = f(\epsilon)$.  Recall tha the strain is given by

$$
\epsilon = \sum_{i=1}^{N} u_i \frac{dN_i}{dx} = \sum_{i=1}^{N} \frac{1}{J} u_i \frac{dN_i}{d\xi}
$$

#### Element Residual Update Algorithm

**data**
- Initial coordinates: $X$
- Current displacement: $u$

**external functions**
- Material constitutive model: $f$

**procedure**
- Step 1: Update nodal coordinates
  $$x_k=X_k+u_k$$

- Step 2: Initialize the element residual
  $$r_k^e=0$$

- Step 3: Perform Gauss integration
  **for** $n$ from 1 to number of Gauss points **do**

  * Step a: Compute shape function derivative wrt to $\xi$ and the Jacobian
    $$\frac{dN_k}{d\xi} = \frac{dN_k\left(\xi_n\right)}{d\xi}$$
    $$J = J\left(x_k, \xi_n\right)$$

  * Step b: Compute the strain at $\xi_n$
    $$\epsilon = \sum_k \frac{1}{J} u_k \frac{dN_k\left(\xi_n\right)}{d\xi}$$

  * Step c: Compute the stress at $\xi_n$
    $$\sigma = f\left(\epsilon\right)$$

  * Step d: Update element residual
    **for** $j$ from 1 to number of element nodes **do**
      $$r^e_j = r^e_j + \sigma\frac{dN_j}{d\xi} w_n$$
    **end for**

  **end for**

## Element Stiffness Calculation

The element stiffness calculation follows as before, except that instead of a constant element stiffness, the stiffness may change with deformation

### Element Stiffness Update Algorithm

**data**
- Initial coordinates: $X$
- Current displacement: $u$

**external functions**
- Material constitutive model: $f$

**procedure**
* Step 1: Update nodal coordinates
  $$x_k=X_k+u_k$$

* Step 2: Initialize the element stiffness
  $$k_{ij}^e=0$$

* Step 3: Perform Gauss integration
  **for** $n$ from 1 to number of Gauss points **do**

  - Step a: Compute shape function derivative wrt to $\xi$ and the Jacobian
    $$\frac{dN_k}{d\xi} = \frac{dN_k\left(\xi_n\right)}{d\xi}$$
    $$J = J\left(x_k, \xi_n\right)$$

  - Step b: Compute the strain at $\xi_n$
    $$\epsilon = \sum_k \frac{1}{J} u_k \frac{dN_k\left(\xi_n\right)}{d\xi}$$

  - Step c: Compute the element stiffness at $\xi_n$
    $$\frac{d\sigma}{d\epsilon} = f\left(\epsilon\right)$$

  - Step d: Update element stiffness
    **for** $i$ from 1 to number of element nodes **do**
          **for** $j$ from 1 to number of element nodes **do**
      $$k_{ij}^e = k_{ij}^e +
               \frac{1}{J}\frac{d\sigma}{d\epsilon}\frac{dN_i}{d\xi}\frac{dN_i}{d\xi} w_n$$
          **end for**
    **end for**

  **end for**

## Neo Hookean Material Model

An example of a nonlinear material model, widely used in the study of polymer-like materials subjected to moderate strains, is the single parameter Neo-Hookean material model whereby the stress is related to the stretch by

```python
L, L0, lam, e, E = sp.symbols("L L0 lambda epsilon E")
s = E / 2 * (lam - 1 / lam)
s
```

where $E$ is Young's modulus.  In terms of engineering strain

$$
\epsilon = \frac{L - L_0}{L_0} = \lambda - 1 \rightarrow \lambda = \epsilon + 1
$$

the stress is

```python
s = s.subs({lam: e + 1})
s.simplify()
```

The stiffness, $\frac{d\sigma}{d\epsilon}$ is given by

```python
dsde = sp.diff(s, e)
dsde.simplify()
```

Let's make sure that the stiffness at $\epsilon=0$ is $E$

```python
dsde.subs({e:0})
```

# Finite Element Implementation: Suggestions

- Implement a Newton solver using a linear elastic material model, verify it gives the same answer as the direct solver.

- Doing so will require calling an external material model whenever the stress and/or material stiffness are needed.  To reduce complications and repeated code, write a function like:

        def update_material_state(props, strain, mat_id=1):
            if mat_id == 1:
                # linear elastic
                E = props[0]
                stress = E * strain
                stiff = E
            elif mat_id == ...
        return stress, stiff

  Different material models can then be implemented by giving them a mat_id, i.e., mat_id will now be a model input.  The FEModel constructor should also make sure that the props array is set up correctly for the material.

  When element stiffness and/or stress are needed, just call update_material_state as

         el_stress, el_stiff = update_material_id(props, strain, mat_id)

  Of course, you'll need to make sure that props, strain, and mat_id are correct.