```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)

```

Toggle cell visibility here.

## Internal stability

The concept of stability captures the behaviour of a system state evolution when this is perturbed from an equilibrium condition: stability describes if the state evolution arising after a perturbation from an equilibrium point diverges or not.

### Definition
Given a time invariant dynamic system described by the state vector $x(t)\in \mathbb{R}^n$, a point of equilibrium $x_e$, an initial state $x_0$ and an initial time $t_0$ if
$$
\forall \, \epsilon \in \mathbb{R}, \, \epsilon > 0 \quad \exists \delta \in \mathbb{R}, \, \delta > 0 : \quad ||x_0-x_e|| < \delta \, \Rightarrow  \, ||x(t)-x_e|| < \epsilon \quad \forall t \ge t_0
$$
that could be read as: if a small enough initial perturbation $\delta$ from the equilibrium point exists so that the state evolution $x(t)$ from the perturbed point does not get too far (more than $\epsilon$) from the equilibrium itself,
then the equilibrium point is stable.

If it also happens that $\lim_{t\to\infty}||x(t)-x_e|| = 0$, that can be read as: the state evolution returns back to the equilibrium point, then the equilibrium is said to be asymptotically stable.

In the case of linear time invariant systems of the type:
\begin{cases}
\dot{x} = Ax +Bu \\
y = Cx + Du,
\end{cases}

it is possible to prove that stability of one equilibrium point implies stability of all equilibrium points, thus we can talk about stability of the system even if, in general, the stability property is related to an equilibrium point. This linear system's peculiar feature is due to the fact that the evolution of this type of systems is strictly related to the eigenvalues of the dynamics matrix $A$, which are rotation-, translation-, initial-condition- and time-invariant.

Recall what is explained in example [Modal Analysis](SS-02-Modal_Analysis.ipynb):

> The closed form solution of the differential equation, from initial time $t_0$, with initial condition $x(t_0)$ is:
$$
x(t) = e^{A(t-t_0)}x(t_0).
$$ The matrix $e^{A(t-t_0)}x(t_0)$ is composed of linear combinations of functions of time $t$, each one of the type: $$e^{\lambda t},$$ where the $\lambda$(s) are the eigenvalues of the matrix $A$; these functions are the modes of the system.

thus:
- a linear dynamic system is stable if and only if all its modes are not divergent,
- a linear dynamic system is asymptotically stable if and only if all its modes are convergent,
- a linear dynamic system is unstable if it has at least one divergent mode.

and, with respect to dynamic matrix eigenvalues, these happen if:
- all the eigenvalues of $A$ belong to the closed left half complex plane (that is they have negative or zero real part) and, in case they have zero real part, their algebraic multiplicity is the same of the geometric multiplicity, or, equivalently, they have scalar blocks in the Jordan form;
- all the eigenvalues belong to the open left half imaginary plane, that is they have strictly negative real part;
- at least one eigenvalue has positive real part, or eigenvalues with zero real part and non-scalar Jordan blocks exist.

This interactive example presents an editable dynamic matrix $A$ and shows the system free response with the corresponding eigenvalues.

### How to use this notebook?
- Try to change the eigenvalues and the initial condition $x_0$ and see how the response changes.

```python
%matplotlib inline
#%matplotlib notebook
import control as control
import numpy
import sympy as sym
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    def dummychangecallback(self,change):
        pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(control.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        control.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

```python
# Preparatory cell

A = numpy.matrix([[0,1],[-2/5,-1/5]])
X0 = numpy.matrix('5; 3')

Aw = matrixWidget(2,2)
Aw.setM(A)
X0w = matrixWidget(2,1)
X0w.setM(X0)
```

```python
# Misc

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)
```

```python
# Main cell

def main_callback(A, X0, DW):
    sols = numpy.linalg.eig(A)
    sys = sss(A,[[1],[0]],[0,1],0)
    pole = control.pole(sys)
    if numpy.real(pole[0]) != 0:
        p1r = abs(numpy.real(pole[0]))
    else:
        p1r = 1
    if numpy.real(pole[1]) != 0:
        p2r = abs(numpy.real(pole[1]))
    else:
        p2r = 1
    if numpy.imag(pole[0]) != 0:
        p1i = abs(numpy.imag(pole[0]))
    else:
        p1i = 1
    if numpy.imag(pole[1]) != 0:
        p2i = abs(numpy.imag(pole[1]))
    else:
        p2i = 1

    print('A\'s eigenvalues are:',round(sols[0][0],4),'and',round(sols[0][1],4))

    #T = numpy.linspace(0, 60, 1000)
    T, yout, xout = control.initial_response(sys,X0=X0,return_x=True)

    fig = plt.figure("A's eigenvalues", figsize=(16,16))
    ax = fig.add_subplot(311,title='Poles (Real vs Imag)')
    #plt.axis(True)
    # Move left y-axis and bottim x-axis to centre, passing through (0,0)
    # Eliminate upper and right axes
    ax.spines['left'].set_position(('data',0.0))
    ax.spines['bottom'].set_position(('data',0.0))
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.set_xlim(-max([p1r+p1r/3,p2r+p2r/3]),
                max([p1r+p1r/3,p2r+p2r/3]))
    ax.set_ylim(-max([p1i+p1i/3,p2i+p2i/3]),
                max([p1i+p1i/3,p2i+p2i/3]))

    plt.plot([numpy.real(pole[0]),numpy.real(pole[1])],[numpy.imag(pole[0]),numpy.imag(pole[1])],'o')
    plt.grid()

    ax1 = fig.add_subplot(312,title='Free response')
    plt.plot(T,xout[0])
    plt.grid()
    ax1.set_xlabel('time [s]')
    ax1.set_ylabel('$x_1$')
    ax1.axvline(x=0,color='black',linewidth='0.8')
    ax1.axhline(y=0,color='black',linewidth='0.8')
    ax2 = fig.add_subplot(313)
    plt.plot(T,xout[1])
    plt.grid()
    ax2.set_xlabel('time [s]')
    ax2.set_ylabel('$x_2$')
    ax2.axvline(x=0,color='black',linewidth='0.8')
    ax2.axhline(y=0,color='black',linewidth='0.8')

    #plt.show()

alltogether = widgets.HBox([widgets.VBox([widgets.Label('$A$:',border=3),
                                          Aw]),
                                          widgets.Label('   ',border=3),
                            widgets.VBox([widgets.Label('$X_0$:',border=3),
                                          X0w]),
                                          START])
out = widgets.interactive_output(main_callback, {'A':Aw, 'X0':X0w, 'DW':DW})
out.layout.height = '1000px'
display(out, alltogether)
```

    Output(layout=Layout(height='1000px'))

    HBox(children=(VBox(children=(Label(value='$A$:'), matrixWidget(children=(HBox(children=(FloatText(value=0.0, …


```python
%autosave 0
```

# MCPC rehearsal problem Oct 25 2017 at UCSY

## Problem E: Stacking Plates

### Input format

- 1st Line: 1 integer, Number of Test Case, each Test Case has following data
   + 1 Line: 1 integer, **n**(Number of Stacks)
   + **n** Lines: first integer: **h** (Number of Plates), and **h** integers (Plate size)

### Output format

Case: (test case number): Number of Operations

### Sample Input

```
3
2
3 1 2 4
2 3 5
3
4 1 1 1 1
4 1 1 1 1
4 1 1 1 1
2
15 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
15 1 1 1 1 1 2 2 2 2 2 3 3 3 3 3
```

### Sample Output

```
Case 1:5
Case 2:2
Case 3:5
```

### Explanation of sample I/O

- 3 test cases
    + Stack of (1,2,4) and (3,5)
    + Stack of 3 (1,1,1,1)
    + Stack of 2 (1,1,1,1,1,2,2,2,2,2,3,3,3,3,3)

- 1st case:
Split between 2 and 4, 3 and 5, Move 4 on 5, 3 on 4, (1,2) on 3  ==> Total 5 operations

- 2nd case:
Move 1st stack (1,1,1,1,1) on 2nd stack, move (1st+2nd stack) on 3rd stack  ==> Total 2 operations

- 3rd case:
Split between 1 and 2 of 1st stack, between 2 and 3 of 2nd stack, move (2,2,2,2,2,3,3,3,3,3) of 1st stack on (3,3,3,3,3) of 2nd, move (1,1,1,1,1,2,2,2,2,2) of 2nd stack on it, move (1,1,1,1,1) on top  ==> Total 5 operations

### Specific vs Abstract,  Find a General Rule from Detail

When solving problems and puzzles that you can not find how to solve, thinking specificlly then abstractly is important. Finding general rules from specific examples give you a path to the answer.

- Think about simple cases
- Find general pattern (idea, rule) from there
- Proove the rule (if possible)
- Extend the rule to more complex cases

### How to calculate number of operation(movement)

If there are N stacks and each contains just 1 piece (Split is not necessary), (N-1) operations are reuired. (N-1) is a minimum number of oprations.

For each Split operation, Join operation is required to create single stack. Total number of operation increases by 2 for each Split operations (S). The order of Split and Join does not affect total number of movement. (Split-Split-Join-Join) = (Split-Join-Split-Join) \begin{equation} Nmber Of Movement = 2S + (N-1) \end{equation}

Same size of pieces in original stacks (Case 2 and Case 3) can be considered to be same as single piece. Case 2: 3 stack of (1), Case 3: 2 stack of (1,2,3)

### Optimized movement

Reverse-Thinking is sometimes very effective. Create Final Stack and check the boundary. If the combination of the boundary exists in original stacks, it can be used (not necessary to split). **Stack ID needs to be checked, as for detail see later**.

$S = (Maximum Number Of Split) - (Number Of Reused Boundary)$

- Case 1: [1,2,3,4,5] is final form. Boundary of [1,2] exist in Original Stack-1, $S=(2+1)-1=2, Movement=2*2+(2-1)=5$
- Case 2: Convert original stacks to [1], $Movement=2*0+(3-1)=2$
- Case 3: Convert original stacks to [1,2,3], Final form is [1,1,2,2,3,3]. Boundary of [1,2] and [2,3] exists. $S=(2+2)-2=2$

### Sample I/O gives hint

Sample Input/Output often gives great hint to solve problems. Same number in original stack cause problem in above idea, but same number can be considered to be 1 digit, so convert input data to eliminate duplicate number.

### Stack ID checking

- Assign stack ID
- Merge every plates and sort by radius (size of plate)
- Manage the list of candidate for boundary reuse (top and bottom)
- Boundary assignment can be done greedy, if there is only 1 combination between top of stack and next, use it

```python
#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
    2017 MCPC at UCSY
    Problem-E: Stacking Plates
'''
import sys

class TestCase():
    pass

class Plates():
    # Groupt of same radius plates in merged stack
    # id_list: list of stack ID
    def __init__(self, radius, id_list):
        self.radius = radius
        self.id_list = id_list
        self.top = None
        self.bottom = None

    def match_prev(self, prev_bottom):
        self.top = list()
        for stack_id in self.id_list:
            if stack_id in prev_bottom:
                self.top.append(stack_id)
        self.bottom = self.id_list.copy()
        if len(self.top) == 1 and len(self.bottom) != 1:
            self.bottom.remove(self.top[0])

        return

    def __repr__(self):
        return ('Plates {}: {}, top: {} bottom: {}'.format(self.radius, self.id_list, self.top, self.bottom))

def parse_tc(tc):
    '''
        Input: Test Case
        Update:
        Return: None
    '''

    tc.n = int(tc.stdin.readline())
    tc.stacks = list()

    for i in range(tc.n):
        stack = tc.stdin.readline().split()[1:]  # 2d List, 1st=len
        tc.stacks.append(stack)

    return

def reform_stack(org):
    '''
        Input: tc.stacks
        Output: cosolidated stacks (no prefix, no duplicate)
    '''

    stacks = list()
    stack_id = 0

    for stack in org:
        prev_radius = None
        new_stack = list()

        for radius in stack:
            if radius != prev_radius:
                new_stack.append((radius, stack_id))
            prev_radius = radius

        stacks.append(new_stack)
        stack_id += 1

    return stacks

def merge(stacks):
    '''
        stacks: 2D List of tuple(radius, id)
        Return: 1D sorted List
    '''

    merged_stack = list()

    for stack in stacks:
        merged_stack.extend(stack)

    merged_stack.sort()

    return merged_stack

def stack2plates(merged_stack):
    '''
    merged_stack: List of Tuple(radius, id)
    return: List of Plates
    '''

    plates_list = list()
    id_list = list()
    prev_size = None

    for plate in merged_stack:
        radius, plate_id = plate
        if radius != prev_size:
            if id_list:
                plates_list.append(Plates(prev_size, id_list))
            id_list = [plate_id]
        else:
            id_list.append(plate_id)

        prev_size = radius

    if id_list:
        plates_list.append(Plates(radius, id_list))

    return plates_list

def max_reuse(plates_list):

    reuse = 0
    prev_bottom = list()

    for plates in plates_list:
        plates.match_prev(prev_bottom)
        if plates.top: reuse += 1
        prev_bottom = plates.bottom
        #print(plates, file=sys.stderr)

    return reuse

def solve(tc):
    '''
        Input: Test Case
        Return: Numper of movement
    '''

    parse_tc(tc)
    stacks = reform_stack(tc.stacks)
    #print(stacks)
    num_merge = len(stacks) - 1  ## Join Stacks
    for stack in stacks:
        num_merge += (len(stack) - 1) * 2  ## Split and Join

    merged_stack = merge(stacks)
    plates_list = stack2plates(merged_stack)  # list of Plates

    #return (num_merge - check_bound(merged_stack, stack_bound) * 2)
    return (num_merge - max_reuse(plates_list) * 2)

```

```python
### Main routine

infile = open('reh_e.in', 'r')

tc = TestCase()
tc.stdin = infile
tc.t = int(tc.stdin.readline())

for i in range(tc.t):
    print('Case {}:{}'.format(i+1, solve(tc)))
```

    Case 1:5
    Case 2:2
    Case 3:5
    Case 4:8
    Case 5:3

```python

```


+ This notebook is part of lecture 10 *The four fundamental subspaces* in the OCW MIT course 18.06 by Prof Gilbert Strang [1]
+ Created by me, Dr Juan H Klopper
    + Head of Acute Care Surgery
    + Groote Schuur Hospital
    + University Cape Town
    + Email me with your thoughts, comments, suggestions and corrections

Linear Algebra OCW MIT18.06 IPython notebook [2] study notes by Dr Juan H Klopper is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

+ [1] OCW MIT 18.06
+ [2] Fernando Pérez, Brian E. Granger, IPython: A System for Interactive Scientific Computing, Computing in Science and Engineering, vol. 9, no. 3, pp. 21-29, May/June 2007, doi:10.1109/MCSE.2007.53. URL: http://ipython.org

```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'>

```python
#import numpy as np
from sympy import init_printing, Matrix, symbols
#import matplotlib.pyplot as plt
#import seaborn as sns
#from IPython.display import Image
from warnings import filterwarnings

init_printing(use_latex = 'mathjax') # Pretty Latex printing to the screen
#%matplotlib inline
filterwarnings('ignore')
```

# The four fundamental subspaces
# Introducing the matrix space

## The four fundamental subspaces

* Columnspace, C(A)
* Nullspace, N(A)
* Rowspaces
    * All linear combinations of rows
    * All the linear combinations of the colums of AT, C(AT)
* Nullspace of AT, N(AT) (the left nullspace of A)

## Where are these spaces for a matrix Am×n?

* C(A) is in ℝm
* N(A) is in ℝn
* C(AT) is in ℝn
* N(AT) is in ℝm

## Calculating basis and dimension

### For C(A)

* The bases are the pivot columns
* The dimension is the rank *r*

### For N(A)

* The bases are the special solutions (one for every free variable, *n* - *r*)
* The dimension is *n* - *r*

### For C(AT)

* If A undergoes row reduction to row echelon form (R), then C(R) ≠ C(A), but rowspace(R) = rowspace(A) (or C(RT) = C(AT))
* A basis for the rowspace of A (or R) is the first *r* rows of R
    * So we row reduce A and take the pivot rows and transpose them
* The dimension is also equal to the rank *r*

### For N(AT)

* It is also called the left, because it ends up on the left (as seen below)
* Here we have AT**y** = **0**
    * **y**T(AT)T = **0**T
    * **y**TA = **0**T
    * This is (again) the pivot columns of AT (after row reduction)
* The dimension is *m* - *r*

## Example problems

### Consider this example matrix and calculate the bases and dimension for all four fundamental spaces

```python
A = Matrix([[1, 2, 3, 1], [1, 1, 2, 1], [1, 2, 3, 1]]) # We note that rows 1 and three are identical and that
# columns 3 is the addtion of columns 1 and 2 and column 1 equals column 4
A
```

$$\left[\begin{matrix}1 & 2 & 3 & 1\\1 & 1 & 2 & 1\\1 & 2 & 3 & 1\end{matrix}\right]$$

#### Columnspace

```python
A.rref() # Remember that the columnspace contains the pivot columns as a basis
```

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

* The basis is thus:
$$ \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \end{bmatrix} $$
* It is indeed in ℝ3 (rows of A = *m* = 3, i.e. each column vector is in 3-space or has 3 components)

* The rank (no of columns with pivots) are 2, thus dim(A) = 2

#### Nullspace

```python
A.nullspace() # Calculating the nullspace vectors
```

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

* So, indeed the basis is in ℝ4 (A has *n* = 4 columns)

```python
A.rref() # No pivots for columns 3 and 4
```

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

* The dimension is two (there are 2 column vectors, which is indeed *n* - *r* = 4 - 2 = 2)

#### Rowspace C(AT)

* So we are looking for the pivot columns of AT

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

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

* The pivot rows are rows 1 and 2
* We take them and transpose them
$$ \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \\ 1 & 0 \end{bmatrix} $$

* As stated above, it is in ℝ4

* The dimension is *n* - *r* = 4 - 2 = 2

#### Nullspace of AT

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

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

* Which is indeed in ℝ3

* The dimension is 1, since *m* - *r* = 3 - 2 = 1 (remember that the rank is the number of pivot columns)

### Consider this example matrix (in LU form) and calculate the bases and dimension for all four fundamental spaces

```python
L = Matrix([[1, 0, 0], [2, 1, 0], [-1, 0, 1]])
U = Matrix([[5, 0, 3], [0, 1, 1], [0, 0, 0]])
A = L * U
L, U, A
```

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & 0\\2 & 1 & 0\\-1 & 0 & 1\end{matrix}\right], & \left[\begin{matrix}5 & 0 & 3\\0 & 1 & 1\\0 & 0 & 0\end{matrix}\right], & \left[\begin{matrix}5 & 0 & 3\\10 & 1 & 7\\-5 & 0 & -3\end{matrix}\right]\end{pmatrix}$$

#### Columnspace of A

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

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & \frac{3}{5}\\0 & 1 & 1\\0 & 0 & 0\end{matrix}\right], & \begin{bmatrix}0, & 1\end{bmatrix}\end{pmatrix}$$

* The basis is thus:
$$ \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 0 & 0 \end{bmatrix} $$
* Another basis would be the pivot columns of L:
$$ \begin{bmatrix} 1 & 0 \\ 2 & 1 \\ -1 & 0 \end{bmatrix} $$
* It is in ℝ3, since *m* = 3
* It has a rank of 2 (two pivot columns)
* Since the dimension of the columnspace is equal to the rank, dim(A) = 2
    * Note that it is also equal to the number of pivot columns in U

#### Nullspace of A

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

$$\begin{bmatrix}\left[\begin{matrix}- \frac{3}{5}\\-1\\1\end{matrix}\right]\end{bmatrix}$$

* The nullspace is in ℝ3, since *n* = 3
* The basis is the special solution(s), which is one column vector for every free variable
    * Since we only have a single free variable, we have a single nullspace column vector
    * This fits in with the fact that it needs to be *n* - *r*
    * It can also be calculated by taking U, setting the free variable to 1 and solving for the other rows by setting each equal to zero
* The dimension of the nullspace is also 1 (*n* - *r*, i.e. a single column)
    * It is also the number of free variables

#### The rowspace

* This is the columnspace of AT
* Don't take the transpose first!
* Row reduce, identify the rows with pivots and transpose them

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

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & \frac{3}{5}\\0 & 1 & 1\\0 & 0 & 0\end{matrix}\right], & \begin{bmatrix}0, & 1\end{bmatrix}\end{pmatrix}$$

* The basis is can also be written down by identifying the rows with pivots in U and writing them down as columns (getting their transpose)
$$ \begin{bmatrix} 5 & 0 \\ 0 & 1 \\ 3 & 1 \end{bmatrix} $$
* It is in ℝ3, since *n* = 3
* The rank *r* = 2, which is equal to the dimension, i.e. dim(AT) = 2

#### The nullspace of AT

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

$$\begin{bmatrix}\left[\begin{matrix}1\\0\\1\end{matrix}\right]\end{bmatrix}$$

* It is indeed in ℝ3, since *m* = 3
* A good way to do it is to take the inverse of L, such that L-1A = U
    * Now the free variable row in U is row three
    * Take the corresponding row in L-1 and transpose it
* The dimension in *m* - 2 = 3 - 2 = 1

## The matrix space

* A square matrix is also a 'vector' space, because they obey the vector space rules of addition and scalar multiplication
* Subspaces (of same) would include
    * Upper triangular matrices
    * Symmetric matrices

```python

```


```python
from IPython.core.display import display_html
from urllib.request import urlopen

cssurl = 'http://j.mp/1DnuN9M'
display_html(urlopen(cssurl).read(), raw=True)
```

<link href='http://fonts.googleapis.com/css?family=EB+Garamond' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Lato:100,100italic' 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');}

        div.cell{
	        max-width:    800px;
	        margin-left:  auto;
	        margin-right: auto;}

	h1 {
        	font-family: 'Lato', sans-serif;}

	h2 {
        	font-family: 'Lato', serif;}

	h3{
		font-family:   'Lato', serif;
        	margin-top:    12px;
		margin-bottom: 3px;}

        h4{
		font-family: 'Lato', serif;}

	h5 {
        	font-family: 'Lato', serif;}

	div.text_cell_render{
        	font-family:  'EB Garamond', 'Computer Modern', 'Helvetica Neue', Arial, Helvetica, Geneva, sans-serif;
	        line-height:  140%;
	        font-size:    14pt;
		max-width:    650px;
		margin-left:  auto;
        	margin-right: auto;}

	.CodeMirror{
        	font-family: 'Consolas';}

	.text_cell_render h1 {
	        font-weight:   100;
	        font-size:     48pt;
		line-height:   100%;
	        color:         #CD2305;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.text_cell_render h2 {
        	font-weight:   100;
        	font-size:     36pt;}

	.text_cell_render h3 {
        	font-weight:   100;
        	font-size:     21pt;}

	.text_cell_render h4 {
        	font-weight:   100;
        	font-size:     18pt;}

	.text_cell_render h5 {
	        font-weight:   100;
	        font-size:     16pt;
	        color:         #CD2305;
	        font-style:    italic;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.text_cell_render h6 {
	        font-weight:   100;
	        font-size:     14pt;
	        color:         #CD2305;
	        font-style:    italic;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.warning{
	    	background-color: #FFFFE0;
	    	border-color: #FFCC99;
	    	border-left: 5px solid #FFCC99;
	    	padding: 0.5em;}

	.error{
	    	background-color: #fcf2f2;
	    	border-color: #dFb5b4;
	    	border-left: 5px solid #dfb5b4;
	    	padding: 0.5em;}

</style>

# Examen Final

## Modelado de Carro Pendulo

### Variables de estado, posiciones y velocidades

Utilizaremos el enfoque de Euler-Lagrange, el cual nos dice que el primer paso para conseguir el modelo matemático es calcular el Lagrangiano $L$ del sistema, definido por:

$$
L = K - U
$$

en donde $K$ es la energía cinética del sistema y $U$ es la energia potencial del sistema.

El estado del sistema estará descrito por una distancia $x$ del centro del carro a un marco de referencia y el angulo $\theta$ del pendulo con respecto a la horizontal.

$$
q = \begin{pmatrix} x \\ \theta \end{pmatrix} = \begin{pmatrix} q_1 \\ q_2 \end{pmatrix} \implies
\dot{q} = \begin{pmatrix} \dot{x} \\ \dot{\theta} \end{pmatrix} = \begin{pmatrix} \dot{q}_1 \\ \dot{q}_2 \end{pmatrix} \implies
\ddot{q} = \begin{pmatrix} \ddot{x} \\ \ddot{\theta} \end{pmatrix} = \begin{pmatrix} \ddot{q}_1 \\ \ddot{q}_2 \end{pmatrix}
$$

### Energía cinética

Para calcular la energía cinética del sistema, obtenemos $K_1$ y $K_2$ asociadas al carro y al pendulo, en donde $K_i = \frac{1}{2} m_i v_i^2$, por lo que tenemos:

$$
K_1 = \frac{1}{2} m_1 v_1^2 = \frac{1}{2} m_1 \dot{x}^2 = \frac{1}{2} m_1 \dot{q}_1^2
$$

$$
K_2 = \frac{1}{2} m_2 v_2^2 = \frac{1}{2} m_2 \left[ \left( \dot{x} + \dot{x}_2 \right)^2 + \dot{y}_2^2 \right]
$$

con $x_2 = l \cos{\theta}$ y $y_2 = l \sin{\theta}$, por lo que sus derivadas son $\dot{x}_2 = -\dot{\theta} l \sin{\theta}$ y $\dot{y}_2 = \dot{\theta} l \cos{\theta}$, por lo que $K_2$ queda:

$$
\begin{align}
K_2 &= \frac{1}{2} m_2 \left[ \left( \dot{x} -\dot{\theta} l \sin{\theta} \right)^2 + \left( \dot{\theta} l \cos{\theta} \right)^2 \right] \\
&= \frac{1}{2} m_2 \left[ \left( \dot{x} -\dot{\theta} l \sin{\theta} \right)^2 + \left( \dot{\theta} l \cos{\theta} \right)^2 \right] \\
&= \frac{1}{2} m_2 \left[ \left( \dot{q}_1 -\dot{q}_2 l \sin{q_2} \right)^2 + \left( \dot{q}_2 l \cos{q_2} \right)^2 \right]
\end{align}
$$

Entonces la energía cinética será:

$$
K = \frac{1}{2} \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 l \sin{q_2} \dot{q}_1 \dot{q}_2 \right]
$$

Lo cual puede ser escrito como una forma matricial cuadratica:

$$
K = \frac{1}{2}
\begin{pmatrix}
\dot{q}_1 & \dot{q}_2
\end{pmatrix}
\begin{pmatrix}
m_1 + m_2 & -m_2 l \sin{q_2} \\
-m_2 l \sin{q_2} & m_2 l^2
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
$$

En este punto, introducimos una variable intermedia, tan solo para reducir un poco la notación:

$$
\lambda = l \sin{q_2}
$$

y su derivada con respecto al tiempo, la representamos como:

$$
\dot{\lambda} = \lambda' \dot{q}_2 \implies \lambda' = l \cos{q_2}
$$

por lo que la energía cinetica queda como:

$$
K = \frac{1}{2}
\begin{pmatrix}
\dot{q}_1 & \dot{q}_2
\end{pmatrix}
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
$$

en donde el termino matricial es la matriz de masa $M(q)$ y $K(q, \dot{q}) = \dot{q}^T M(q) \dot{q}$.

### Energía potencial

Por otro lado, para calcular la energía potencial del sistema, tenemos que:

$$
U_1 = m_1 g h_1 = 0
$$

$$
U_2 = m_2 g h_2 = m_2 g l \sin{q_1} = m_2 g \lambda
$$

por lo que la energía potencial del sistema será:

$$
U = m_2 g \lambda
$$

### Lagrangiano

El Lagrangiano del sistema, esta dado por la expresión:

$$
L = K - U
$$

por lo que solo queda sumar las dos expresiones y obtener las condiciones de optimalidad para la energía del sistema por medio de la ecuación de Euler-Lagrange

### Euler-Lagrange

Cuando aplicamos la primer condición de optimalidad al Lagrangiano $L(t, q(t), \dot{q}(t)) = K(q(t), \dot{q}(t)) - U(q)$, tenemos la ecuación de Euler-Lagrage, la cual nos dice que:

$$
\frac{d}{dt} L_{\dot{q}} - L_q = 0
$$

por lo que debemos encontrar la derivada del Lagrangiano, con respecto a $q$, $\dot{q}$ y derivar esta ultima con respecto al tiempo. Empecemos con la derivada con respecto a $q$:

$$
L_q = K_q - U_q
$$

en donde:

$$
\begin{align}
K_q &= \frac{\partial}{\partial q} \left\{ \frac{1}{2} \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\} \\
&= \frac{1}{2}
\begin{pmatrix}
\frac{\partial}{\partial q_1} \left\{ \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\} \\
\frac{\partial}{\partial q_2} \left\{ \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\}
\end{pmatrix} \\
&= \frac{1}{2}
\begin{pmatrix}
0 \\
- 2 m_2 \lambda' \dot{q}_1 \dot{q}_2
\end{pmatrix} = - m_2 \lambda'
\begin{pmatrix}
0 & 0 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
\end{align}
$$

y la derivada de la energía potencial con respecto a $q$:

$$
U_q = \frac{\partial}{\partial q} \left\{ m_2 g \lambda \right\} =
\begin{pmatrix}
\frac{\partial}{\partial q_1} \left\{ m_2 g \lambda \right\} \\
\frac{\partial}{\partial q_2} \left\{ m_2 g \lambda \right\}
\end{pmatrix} =
\begin{pmatrix}
0 \\
m_2 g \lambda'
\end{pmatrix}
$$

Ahora obtenemos la derivada con respecto a $\dot{q}$:

$$
L_{\dot{q}} = K_{\dot{q}} - U_{\dot{q}}
$$

en donde:

$$
K_{\dot{q}} = \frac{1}{2} \frac{\partial}{\partial \dot{q}} \left\{ \dot{q}^T M(q) \dot{q} \right\} = M(q) \dot{q}
$$

$$
U_{\dot{q}} = 0
$$

Derivando con respeto al tiempo estas ultimas expresiones, obtenemos:

$$
\frac{d}{dt} K_{\dot{q}} = \dot{M}(q, \dot{q}) \dot{q} + M(q) \ddot{q}
$$

en donde

$$
\begin{align}
\dot{M}(q, \dot{q}) &= \frac{d}{dt} M(q) = \frac{d}{dt}
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix} \\
&=
\begin{pmatrix}
0 & -m_2 \lambda' \dot{q}_2 \\
-m_2 \lambda' \dot{q}_2 & 0
\end{pmatrix} = -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
\dot{q}_2 & 0
\end{pmatrix}
\end{align}
$$

y ya tenemos todos los elementos que integran nuestra ecuación de Euler-Lagrange:

$$
\begin{align}
\frac{d}{dt} L_{\dot{q}} - L_q &= 0 \\
M(q) \ddot{q} + \dot{M}(q, \dot{q}) \dot{q} - K_q + U_q &= 0
\end{align}
$$

Tan solo cabe recalcar que el segundo y tercer termino se pueden reducir a uno solo:

$$
\begin{align}
\dot{M}(q, \dot{q}) \dot{q} - K_q &= -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} + m_2 \lambda'
\begin{pmatrix}
0 & 0 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} \\
&= -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
0 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} = C(q, \dot{q})\dot{q}
\end{align}
$$

Por lo que finalmente para el sistema libre tenemos:

$$
M(q) \ddot{q} + C(q, \dot{q}) \dot{q} + U_q = 0
$$

con:

$$
M(q) =
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix} \quad
C(q, \dot{q}) = -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
0 & 0
\end{pmatrix} \quad
U_q =
\begin{pmatrix}
0 \\
m_2 g \lambda'
\end{pmatrix}
$$

### Señales de control

Y para el sistema bajo control tan solo tenemos que agregar la señal de control asociada al primer grado de libertad, por lo que tendremos:

$$
M(q) \ddot{q} + C(q, \dot{q}) \dot{q} + U_q = G u
$$

con $G = \begin{pmatrix} 1 \\ 0 \end{pmatrix}$.

### Simulación

```python
def f(estado, tiempo):
    from numpy import zeros, matrix, sin, cos

    m1 = 0.2
    m2 = 0.4
    g = 9.81
    l = 0.6

    q1, q2, q1p, q2p = estado

    q = matrix([[q1], [q2]])
    qp = matrix([[q1p], [q2p]])

    λ = l*sin(q2)
    λ̇ = l*cos(q2)

    M = matrix([[m1 + m2, -m2*λ], [-m2*λ, m2*l**2]])
    C = -m2*λ̇*matrix([[0, q2p], [0, 0]])
    U = matrix([[0], [m2*g*λ̇]])

    qpp = M.I*(-C*qp - U)

    dydx = zeros(4)

    dydx[0] = q1p
    dydx[1] = q2p
    dydx[2] = qpp[0]
    dydx[3] = qpp[1]

    return dydx
```

```python
from scipy.integrate import odeint
from numpy import linspace, arange, sin, cos
from matplotlib.pyplot import figure, style, plot
from matplotlib import animation
from matplotlib.patches import Rectangle, Circle
```

```python
ts = linspace(0, 4, 100)
estado_inicial = [0, 0.1, 0, 0]
```

```python
estados = odeint(f, estado_inicial, ts)
q1, q2 = estados[:, 0], estados[:, 1]
```

```python
fig = figure(figsize=(8, 6))

ax = fig.add_subplot(111, autoscale_on=False,
                     xlim=(-0.8333, 1.8333), ylim=(-1.25, 0.75))

ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)

ax.axes.spines["right"].set_color("none")
ax.axes.spines["left"].set_color("none")
ax.axes.spines["top"].set_color("none")
ax.axes.spines["bottom"].set_color("none")

ax.set_axis_bgcolor('#F2F1EC')

linea, = ax.plot([], [], 'o-', lw=1.5, color='#393F40')
carro = Rectangle((10,10), 0.8, 0.35, lw=1.5, fc='#E5895C')
guia = Rectangle((10, 10), 2.6666, 0.1, lw=1.5, fc='#A4B187')
pendulo = Circle((10, 10), 0.125, lw=1.5, fc='#F3D966')

def init():
    linea.set_data([], [])
    guia.set_xy((-0.8333, -0.05))
    carro.set_xy((-0.4, -0.175))
    pendulo.center = (1, 0)
    ax.add_patch(guia)
    ax.add_patch(carro)
    ax.add_patch(pendulo)
    return linea, carro, pendulo

def animate(i):
    xs = [q1[i], q1[i] + cos(q2[i])]
    ys = [0, sin(q2[i])]

    linea.set_data(xs, ys)
    carro.set_xy((xs[0] - 0.4, ys[0] - 0.175))
    pendulo.center = (xs[1], ys[1])
    return linea, carro, pendulo

ani = animation.FuncAnimation(fig, animate, arange(1, len(q1)), interval=25,
                              blit=True, init_func=init)

ani.save('./imagenes/carropendulolibre.gif', writer='imagemagick');
```

Espero te hayas divertido con esta larga explicación y al final sepas un truco mas.

Si deseas compartir este Notebook de IPython utiliza la siguiente dirección:

http://bit.ly/1M2tenc

o bien el siguiente código QR:

```python
# Codigo para generar codigo :)
from qrcode import make
img = make("http://bit.ly/1M2tenc")
img.save("./codigos/carropendulo.jpg")
```

IPythonNotebooks/Control Optimo/Examen Final/Carro Pendulo.ipynb


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Promijeni vidljivost <a href="javascript:code_toggle()">ovdje</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Promijeni vidljivost ovdje.

```python
# Examples:
# Factored form: 1/(x**2*(x**2 + 1))
# Expanded form: 1/(x**4+x**2)

import sympy as sym
from IPython.display import Latex, display, Markdown, Javascript, clear_output
from ipywidgets import widgets, Layout # Interactivity module
```

## Rastav na parcijalne razlomke

Kada se Laplaceova transformacija koristi za analizu sustava, Laplaceova transformacija izlaznog signala dobiva se u obliku umnoška prijenosne funkcije i Laplaceove transformacije ulaznog signala. Rezultat ovog množenja često je složen za shvatiti. Da bismo izvršili inverznu Laplaceovu transformaciju, prvo se izvodi postupak rastava na parcijalne razlomke. Ovaj primjer demonstrira taj postupak.

---

### Kako koristiti ovaj interaktivni primjer?
Izaberite jednu između ponuđenih opcija: *Unos funkcije* ili *Unos koeficijenata polinoma*.

1. *Unos funkcije*:
 * Primjer: Za unos funkcije $\frac{1}{x^2(x^2 + 1)}$ (faktorizirani oblik) upišite 1/(x\*\*2\*(x\*\*2 + 1)); za unos iste funkcije u proširenom obliku ($\frac{1}{x^4+x^2}$) upišite 1/(x\*\*4+x\*\*2).

2. *Unos koeficijenata polinoma*:
 * Pomoću klizača odaberite red brojnika i nazivnika željene funkcije.
 * Upišite koeficijente za brojnik i nazivnik u odgovarajuća unosna polja te kliknite na *Potvrdi*.

```python
## System selector buttons
style = {'description_width': 'initial'}
typeSelect = widgets.ToggleButtons(
    options=[('Unos funkcije', 0), ('Unos koeficijenata polinoma', 1),],
    description='Odaberi: ',style={'button_width':'230px'})

btnReset=widgets.Button(description="Reset")

# function
textbox=widgets.Text(description=('Unos funkcije:'),style=style)
btnConfirmFunc=widgets.Button(description="Potvrdi") # ex btnConfirm

# poly
btnConfirmPoly=widgets.Button(description="Potvrdi") # ex btn

display(typeSelect)

def on_button_clickedReset(ev):
    display(Javascript("Jupyter.notebook.execute_cells_below()"))

def on_button_clickedFunc(ev):
    eq = sym.sympify(textbox.value)

    if eq==sym.factor(eq):
        display(Markdown('Ulazna funkcija $%s$ je zapisana u faktoriziranom obliku. ' %sym.latex(eq) + 'Njezina proširena verzija je $%s$.' %sym.latex(sym.expand(eq))))

    else:
        display(Markdown('Ulazna funkcija $%s$ je zapisana u proširenom obliku. ' %sym.latex(eq) + 'Njezin faktorizirani oblik je $%s$.' %sym.latex(sym.factor(eq))))

    display(Markdown('Rezultat rastava na parcijalne razlomke: $%s$' %sym.latex(sym.apart(eq)) + '.'))
    display(btnReset)

def transfer_function(num,denom):
    num = np.array(num, dtype=np.float64)
    denom = np.array(denom, dtype=np.float64)
    len_dif = len(denom) - len(num)
    if len_dif<0:
        temp = np.zeros(abs(len_dif))
        denom = np.concatenate((temp, denom))
        transferf = np.vstack((num, denom))
    elif len_dif>0:
        temp = np.zeros(len_dif)
        num = np.concatenate((temp, num))
        transferf = np.vstack((num, denom))
    return transferf

def f(orderNum, orderDenom):
    global text1, text2
    text1=[None]*(int(orderNum)+1)
    text2=[None]*(int(orderDenom)+1)
    display(Markdown('2. Upišite koeficijente za brojnik.'))
    for i in range(orderNum+1):
        text1[i]=widgets.Text(description=(r'a%i'%(orderNum-i)))
        display(text1[i])
    display(Markdown('3. Upišite koeficijente za nazivnik.'))
    for j in range(orderDenom+1):
        text2[j]=widgets.Text(description=(r'b%i'%(orderDenom-j)))
        display(text2[j])
    global orderNum1, orderDenom1
    orderNum1=orderNum
    orderDenom1=orderDenom

def on_button_clickedPoly(btn):
    clear_output()
    global num,denom
    enacbaNum=""
    enacbaDenom=""
    num=[None]*(int(orderNum1)+1)
    denom=[None]*(int(orderDenom1)+1)
    for i in range(int(orderNum1)+1):
        if text1[i].value=='' or text1[i].value=='Upišite koeficijent':
            text1[i].value='Upišite koeficijent'
        else:
            try:
                num[i]=int(text1[i].value)
            except ValueError:
                if text1[i].value!='' or text1[i].value!='Upišite koeficijent':
                    num[i]=sym.var(text1[i].value)

    for i in range (len(num)-1,-1,-1):
        if i==0:
            enacbaNum=enacbaNum+str(num[len(num)-i-1])
        elif i==1:
            enacbaNum=enacbaNum+"+"+str(num[len(num)-i-1])+"*x+"
        elif i==int(len(num)-1):
            enacbaNum=enacbaNum+str(num[0])+"*x**"+str(len(num)-1)
        else:
            enacbaNum=enacbaNum+"+"+str(num[len(num)-i-1])+"*x**"+str(i)

    for j in range(int(orderDenom1)+1):
        if text2[j].value=='' or text2[j].value=='Upišite koeficijent':
            text2[j].value='Upišite koeficijent'
        else:
            try:
                denom[j]=int(text2[j].value)
            except ValueError:
                if text2[j].value!='' or text2[j].value!='Upišite koeficijent':
                    denom[j]=sym.var(text2[j].value)

    for i in range (len(denom)-1,-1,-1):
        if i==0:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])
        elif i==1:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])+"*x"
        elif i==int(len(denom)-1):
            enacbaDenom=enacbaDenom+str(denom[0])+"*x**"+str(len(denom)-1)
        else:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])+"*x**"+str(i)

    funcSym=sym.sympify('('+enacbaNum+')/('+enacbaDenom+')')

    DenomSym=sym.sympify(enacbaDenom)
    NumSym=sym.sympify(enacbaNum)
    DenomSymFact=sym.factor(DenomSym);
    funcFactSym=NumSym/DenomSymFact;

    if DenomSym==sym.expand(enacbaDenom):
        if DenomSym==DenomSymFact:
            display(Markdown('Zadana funkcija je: $%s$. Brojnik se ne može faktorizirati.' %sym.latex(funcSym)))
        else:
            display(Markdown('Zadana funkcija je: $%s$. Brojnik se ne može faktorizirati. Ista funkcija s faktoriziranim nazivnikom može se zapisati kao: $%s$.' %(sym.latex(funcSym), sym.latex(funcFactSym))))

    if sym.apart(funcSym)==funcSym:
        display(Markdown('Nemoguće je napraviti rastav na parcijalne razlomke.'))
    else:
        display(Markdown('Rezultat rastava na parcijalne razlomke je: $%s$' %sym.latex(sym.apart(funcSym)) + '.'))

    btnReset.on_click(on_button_clickedReset)
    display(btnReset)

def partial_frac(index):

    if index==0:
        x = sym.Symbol('x')
        display(widgets.HBox((textbox, btnConfirmFunc)))
        btnConfirmFunc.on_click(on_button_clickedFunc)
        btnReset.on_click(on_button_clickedReset)

    elif index==1:
        display(Markdown('1. Definirajte red brojnika (orderNum) i nazivnika (orderDenom).'))
        widgets.interact(f, orderNum=widgets.IntSlider(min=0,max=10,step=1,value=0),
                 orderDenom=widgets.IntSlider(min=0,max=10,step=1,value=0));
        btnConfirmPoly.on_click(on_button_clickedPoly)
        display(btnConfirmPoly)

input_data=widgets.interactive_output(partial_frac,{'index':typeSelect})
display(input_data)
```

    ToggleButtons(description='Odaberi: ', options=(('Unos funkcije', 0), ('Unos koeficijenata polinoma', 1)), sty…

    Output()


+ This notebook is part of lecture 9 *Independence, basis and dimension* in the OCW MIT course 18.06 by Prof Gilbert Strang [1]
+ Created by me, Dr Juan H Klopper
    + Head of Acute Care Surgery
    + Groote Schuur Hospital
    + University Cape Town
    + Email me with your thoughts, comments, suggestions and corrections

Linear Algebra OCW MIT18.06 IPython notebook [2] study notes by Dr Juan H Klopper is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

+ [1] OCW MIT 18.06
+ [2] Fernando Pérez, Brian E. Granger, IPython: A System for Interactive Scientific Computing, Computing in Science and Engineering, vol. 9, no. 3, pp. 21-29, May/June 2007, doi:10.1109/MCSE.2007.53. URL: http://ipython.org

```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'>

```python
#import numpy as np
from sympy import init_printing, Matrix, symbols
#import matplotlib.pyplot as plt
#import seaborn as sns
#from IPython.display import Image
from warnings import filterwarnings

init_printing(use_latex = 'mathjax') # Pretty Latex printing to the screen
#%matplotlib inline
filterwarnings('ignore')
```

# Independence
# Spanning
# Basis
# Dimension

## Independence

* Vectors are linearly independent if
    * No combination of these vectors results in the zero vector (except the zero combinations)
$$ { c }_{ 1 }{ x }_{ 1 }+{ c }_{ 2 }{ x }_{ 2 }+\dots +{ c }_{ n }{ x }_{ n }\neq 0,\quad { c }_{ i }\neq 0 $$
    * In 2-space, this means that they should noy be on the same line through the origin
    * In 3-space they should not be on the same line through the origin or on a plane through the origin
    * In higher-dimensional space they should not be on the same line through the origin or a hyperplane through the origin

* If they are independent by the constraints above, only a zero combination of them will results in zero
* If there are vectors in the nullspace (apart from the zero vector), then (there is a linear combination that will give zero and) then the vectors are not linearly independent

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

$$\left[\begin{matrix}1 & 2 & 4\\3 & 1 & 4\end{matrix}\right]$$

* Here we will have a rank of 2 (2 pivots) and 3 unknowns and 2 rows
* Thus, *r* = *m* (full row rank)
* We are left with *n* - *r* freen variable, i.e. 3 - 2 = 1 and will have one vector in the nullspace

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

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & \frac{4}{5}\\0 & 1 & \frac{8}{5}\end{matrix}\right], & \begin{bmatrix}0, & 1\end{bmatrix}\end{pmatrix}$$

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

$$\begin{bmatrix}\left[\begin{matrix}- \frac{4}{5}\\- \frac{8}{5}\\1\end{matrix}\right]\end{bmatrix}$$

### Another way to state independence

* Consider the columns of the matrix A as vectors v1, v2, ..., vn

* If *r* = *n* then the nullspace only contains the zero vector and the column vectors are linearly independent

## Spanning

* If we have a set of linearly independent vector that all their linear combinations (including zero) span a subspace ( in this instance a column space)

* We are particularly interested in a set of (column) vectors (in a matrix) that are linearly independent and span a subspace
* This leads us to the next topic, **basis**

## Basis

* A set of vectors (in a space *W*) with the properties
    * They are linearly independent
    * They span the space (linear combinations of them fill the space)

* Up until now we looked at columns in a matrix A
* It is more common in textbooks to look at a space first and ask about basis vectors, spanning vectors, dimension, etc

* So let's look at ℝ3
* The obvious set of basis vectors are
$$ \hat {i}, \quad \hat {j}, \quad \hat {k} $$

* What about
$$ \begin{bmatrix} 1 \\ 1 \\ 2 \end{bmatrix},\quad \begin{bmatrix} 2 \\ 2 \\ 5 \end{bmatrix} $$
* So, are they linearly independent and do they span ℝ3?|

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

$$\left[\begin{matrix}1 & 2\\1 & 2\\2 & 5\end{matrix}\right]$$

* Here we will have *r* = 2, *n* = 2 and thus a (*n* - *r* = 0) zero nullspace

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

$$\begin{bmatrix}\end{bmatrix}$$

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

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

* For now, our intuition is that they will not span ℝ3
* This intuition is correct, because all their linear combinations will only fill a plane through the origin
* The zero combination does result in the zero vector, though, so they do fill a subspace of ℝ3
* Some textbooks refer to this as *V* = ℝn, with *W* a subspace of *V*

* If we added a column vector that is a linear combination of these, it will also fall in the plane and thus not be linearly independent (there will be a vector in the nullspace other than the zero vector

```python
A = Matrix([[1, 2, 3], [1, 2, 3], [2, 5, 7]])
A.nullspace()
```

$$\begin{bmatrix}\left[\begin{matrix}-1\\-1\\1\end{matrix}\right]\end{bmatrix}$$

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

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

* Indeed, we have a column without a pivot and thus a free variable

* Let's add another, such that we have

```python
A = Matrix([[1, 2, 3], [1, 2, 3], [2, 5, 8]])
A.rref()
```

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

* Again, a column without a pivot and sure enough, we'll find a vector (other than the zero vector) in the nullspace

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

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

### The special case of a square matrix

* If we now end up with a square matrix, we need only look at it's determinant, i.e., is it invertible

```python
A.det() # .det() calculates the determinant
```

$$0$$

* Indeed the determinant is zero as expected

## Dimension

* Given a (sub)space, every basis for that (sub)space has the same number of vectors (there are usally more than one basis for every (sub)space)
* This called the dimension of a (sub)space

## Important point to remember

* The (sub)space which a set of (column) vectors (matrix of coefficients, A) span, is the set of possible **b**-values

## More examples

#### Example problem

* Consider the column space

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

```python
A
```

$$\left[\begin{matrix}1 & 2 & 3 & 1\\1 & 1 & 2 & 1\\1 & 2 & 3 & 1\end{matrix}\right]$$

* There are *n* = 4 unknowns, *m* = 3 unknowns
* We note that column 1 = column 4
* We note that with 4 unknowns we are dealing with ℝ4
* In essence, there are at most three independent columns, thus the matrix cannot be a basis for ℝ4
* It is possible for them to span the column space (don't get confused by column space (ℝ4) and matrix here)

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

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

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

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

* As we can see here (columns three and four have free variables, i.e. no pivots)
$$ { x }_{ 1 }+0{ x }_{ 2 }+{ x }_{ 3 }+{ x }_{ 4 }=0\\ 0{ x }_{ 1 }+1{ x }_{ 2 }+{ x }_{ 3 }+{ 0 }_{ 4 }=0\\ { x }_{ 4 }={ c }_{ 2 }\\ { x }_{ 3 }={ c }_{ 1 }\\ \therefore \quad { x }_{ 2 }=-{ c }_{ 1 }\\ \therefore \quad { x }_{ 1 }=-{ c }_{ 1 }-{ c }_{ 2 }\\ \begin{bmatrix} { x }_{ 1 } \\ { x }_{ 2 } \\ { x }_{ 3 } \\ { x }_{ 4 } \end{bmatrix}=\begin{bmatrix} -{ c }_{ 1 }-{ c }_{ 2 } \\ -{ c }_{ 1 } \\ { c }_{ 1 } \\ { c }_{ 2 } \end{bmatrix}=\begin{bmatrix} -{ c }_{ 1 } \\ -{ c }_{ 1 } \\ { c }_{ 1 } \\ 0 \end{bmatrix}+\begin{bmatrix} -{ c }_{ 2 } \\ 0 \\ 0 \\ { c }_{ 2 } \end{bmatrix}={ c }_{ 1 }\begin{bmatrix} -1 \\ -1 \\ 1 \\ 0 \end{bmatrix}+{ c }_{ 2 }\begin{bmatrix} -1 \\ 0 \\ 0 \\ 1 \end{bmatrix} $$

* The rank of the matrix is two (it is the number of pivot columns)
* This matrix space thus have two basis vectors (column vectors 1 and 2) and we say the dimension of this space is two
* Remember, a matrix has a rank, which is the dimension of a column space (the column space representing the space 'produced' by the column vectors)
* We talk about the rank of a matrix, rank(A) and the column space of a matrix, C(A)

* In summary we have two basis above (they span a space)
    * Any two vectors that are not linearly dependent will also span this space, they can't help but to
    * dimC(A)= *r*
    * The nullspace will have *n* - *r* vectors (the dimension of the null space equal the number of free variables)

```python

```


Tài liệu này mang giấy phép Creative Commons Attribution (CC BY). (c) Nguyễn Ngọc Sáng, Zhukovsky 06/2019.

[@SangVn](https://github.com/SangVn) [@VnCFD](https://vncfdgroup.wordpress.com/)

*Thực hành CFD với Python!*

## Phần 3: Hệ phương trình Euler hai chiều, vncfd_2D
`Nếu không đào bới thì còn gì là thú vị!`

Bạn hãy tưởng tượng việc `nghiên cứu CFD` giống như hành trình `đi tìm kho báu`. Đứng trong một khu rừng nhiệt đới rậm rạp, trên bãi biển của một hòn đảo hoang sơ hay đứng trước đại dương rộng lớn, ta tự hỏi không biết nên làm những gì, đi đến đâu... Chúng ta cần những lời chỉ dẫn, cần `một chiếc bản đồ kho báu`. Những bài báo, những quyển sách về lưu chất, phương pháp tính, về CFD chính là tờ bản đồ kho báu đó. Chúng ta đọc, nghiên cứu và đi theo, chúng ta đã tới được `những nơi được đánh dấu` trên bản đồ. Thế nhưng, trong tay ta `không có một tấc sắt`, không có một `chiếc xẻng` để đào bới... `Chương trình CFD` chính là chiếc xẻng để ta khám phá kho báu, đào hay chôn. Mà `nếu không đào bới thì còn gì là thú vị!`. Chúng ta cần rèn một cái xẻng. Nó có thể được rèn bằng Python, bằng C++, bằng FORTRAN, MATLAB... hay một `vật liệu` nào khác. Khi chúng ta đã biết cách rèn xẻng, biết cách sử dụng một dạng vật liệu thì việc chuyển sang rèn bằng một vật liệu khác không quá phức tạp.

Tóm lại là ta cần một **công cụ** để đào sâu nghiên cứu CFD.

Khóa học **Thực hành CFD với Python!** sẽ cho bạn những lời chỉ dẫn, một mảnh của tấm bản đồ và cách rèn xẻng cho hành trình `đi tìm kho báu`.

Chúng ta chỉ mới đi được 2 bước: ở **[phần I](https://github.com/SangVn/CFD_Notebook_P1) và [phần II](https://github.com/SangVn/CFD_Notebook_P2)** ta đã tìm hiểu về phương pháp tính, ứng dụng giải các phương trình, hệ phương trình đặc trưng từ đơn giản tới phức tạp, ta đã dừng lại ở hệ phương trình Euler một chiều. Còn đó hệ phương trình Euler hai chiều, ba chiều; hệ phương trình Navier-Stokes; các mô hình rối Spalart-Allmaras, k-omega, k-epsilon, SST, LES, DNS....

Thực ra 3D so với 2D thì `chỉ là` thêm 1D, Navier-Stokes hay các mô hình rối so với Euler thì chỉ là thêm một biến, hai biến. Tất nhiên trong cái từ `chỉ là` ấy còn nhiều khác biệt, nhưng khi ta đã có nền tảng để phát triển ta có thể làm được nhiều thứ.

Phần 3 `Thực hành CFD với Python!` sẽ xoay quanh việc xây dựng chương trình giải hệ phương trình Euler 2D. Việc chuyển từ 1D sang 2D là một bước đi quan trọng và thú vị. Phần này tập trung giải thích cấu trúc chương trình, cấu trúc dữ liệu, nhiều phần lý thuyết chỉ dừng lại ở việc giới thiệu tài liệu để bạn đọc tự tham khảo. Chương trình sử dụng **phương pháp thể tích hữu hạn; phương pháp tính dòng Godunov, Roe; sơ đồ hiện, bậc một theo thời gian; tái cấu trúc nghiệm bậc một Godunov, lưới có cấu trúc** và được viết bằng ngôn ngữ `python2.7`. Ta cũng sẽ làm quen với phần mềm `ParaView` để biểu diễn và xử lý kết quả. Các bài toán ví dụ bao gồm: **dòng chảy trên âm qua dốc; dòng chảy bao hình trụ, NACA profile và tàu vũ trụ Crew Dragon**.

Để chuẩn bị cho phần 3, các bạn hãy cài đặt và học cách sử dụng **PyCharm** để viết code và **ParaView** để biểu diễn và xử lý dữ liệu CFD.

**Tài liệu tham khảo:**
- Eleuterio F. Toro `Riemann Solvers and Numerical Methods for Fluid Dynamics`
- Randall J. Leveque `Finite-Volume Methods for Hyperbolic Problems`
- H. K. Versteeg, W. Malalasekera `An introduction to Computational Fluid Dynamics. The Finite Volume Method`
- Katake Masatsuka `I do like CFD. Governing Equations and Exact Solutions`
- F. Moukalled, L. Mangani, M. Darwish `The Finite Volume Method in Computational Fluid Dynamics. An Advanced Introduction with OpenFOAM and Matlab`

# Bài 18. Hệ phương trình Euler hai chiều, phương pháp giải

Các kiến thức cơ bản đã được giới thiệu trong phần II, sau đây ta tóm tắt một số điểm cơ bản.

# 1. Hệ phương trình Euler hai chiều (2D)

Đã được giới thiệu trong [bài 16, phần II](https://nbviewer.jupyter.org/github/SangVn/CFD_Notebook_P2/blob/master/Bai_16.ipynb)

Dòng chảy của khí lý tưởng trong hai chiều không gian được mô tả bởi hệ phương trình Euler 2D:
$$\frac{\partial U}{\partial t} + \frac{\partial F}{\partial x} + \frac{\partial G}{\partial y} = 0\qquad(1)$$
với
\begin{align}
U & = \begin{pmatrix} \rho \\ \rho u \\ \rho v \\ \rho e\end{pmatrix}, &
F & = \begin{pmatrix} \rho u \\ \rho u^2 + p \\ \rho u v \\ \rho uh\end{pmatrix} &
G & = \begin{pmatrix} \rho v \\ \rho uv \\ \rho v^2 + p \\ \rho vh\end{pmatrix} &
\end{align}
và $h = e + \frac{p}{\rho}$, $e = \varepsilon + \frac{u^2+v^2}{2}$, $\varepsilon = \frac{p}{(\gamma-1)\rho}$;

trong đó: $\rho$ - khối lượng riêng; $u, v$ - vận tốc theo phương x, y; $p$ - áp suất; $e$ - năng lượng một đơn vị khối lượng chất khí; $\varepsilon$ - nội năng; $h$ - enthalpy.

# 2. Phương pháp thể tích hữu hạn

Đã được giới thiệu trong [bài 11, phần II](https://nbviewer.jupyter.org/github/SangVn/CFD_Notebook_P2/blob/master/Bai_11.ipynb)

Xét `thể tích hữu hạn` ABCD (hình phải) không nằm trên biên, có các `bề mặt` AB, BC, CD, DA; lấy tích phân phương trình (1):

$$\int_{ABCD} \left(\frac{\partial U}{\partial t} + \frac{\partial F}{\partial x} + \frac{\partial G}{\partial y} \right)dxdy = 0$$

áp dụng định lý Green ta thu được phương trình dạng tích phân:
$$\frac{d}{dt}\int U dV + \oint_{ABCD} \vec F. \vec n ds = 0 \qquad (2)$$
với $\vec F = (F, G)$, $\vec n$ - vector pháp tuyến đơn vị, $\vec n ds = (dy, -dx)$.

Phương trình (2) mô tả định luật `bảo toàn` khối lượng, động lượng và năng lượng. Nó gồm hai thành phần tương ứng sự biến đổi theo thời gian và dòng đi qua các bề mặt.

### Sơ đồ sai phân
Sơ đồ sai phân của phương trình (2) với xấp xỉ thời gian bậc một có dạng:
$$\frac{U^{n+1} - U^n}{\Delta t} + \frac{1}{V_{ABCD}} (\vec F.\vec S_{AB} + \vec F.\vec S_{BC} + \vec F.\vec S_{CD} + \vec F.\vec S_{DA}) = 0 \qquad (3)$$
với:

$U^n = \frac{1}{V_{ABCD}}\int U dV$ - giá trị trung bình của U trong thể tích đang xét tại thời điểm $t$, $V_{ABCD}$ - thể tích hữu hạn (trường hợp 2D - diện tích ABCD);

$\vec S = \vec n . S$ - vector pháp tuyến bề mặt S, có độ lớn bằng diện tích bề mặt (2D - độ dài AB, BC, CD, DA).

Ta có công thức xác định $U^{n+1}$ tại thời điểm $t+\Delta t$:

$$U^{n+1} = U^n + \frac{\Delta t}{V_{ABCD}}\sum {\vec F.\vec S_n} \qquad (4)$$
với $\vec S_n = -\vec n . S = -(nx, ny).S$

### Công thức tính dòng qua bề mặt
Công thức tính dòng qua một đơn vị diện tích bề mặt:
\begin{align}
Flux = \vec F.(\vec n) = (F, G).(nx,ny) & = \begin{pmatrix} \rho(un_x+vn_y) \\ \rho u(un_x+vn_y) + pn_x \\ \rho v(un_x+vn_y) + pn_y \\ \rho h(un_x+vn_y)\end{pmatrix}, &
\end{align}

Trong đó $un_x+vn_y = V_n$ - vận tốc của dòng chảy theo phương vuông góc với bề mặt. Ta có:

\begin{align}
Flux & = \begin{pmatrix} \rho V_n \\ \rho uV_n + pn_x \\ \rho vV_n + pn_y \\ \rho hV_n\end{pmatrix}, &
\end{align}

## Các điểm chú ý

So với phương pháp giải hệ phương trình Euler 1D ở phần 2, khi giải hệ trong không gian hai chiều (2D) chúng ta sẽ cần phải quan tâm kỹ tới các vấn đề: đặc tính hình học; lưới; cấu trúc dữ liệu; điều kiện biên; biểu diễn, xử lý kết quả.

Ta sẽ bắt đầu thực hành với việc tạo lưới cho bài toán dòng chảy trên âm ở bài sau.

# [Bài 19. Dựng lưới có cấu trúc ](Bai_19.ipynb)